存档
CSDN上有篇帖子http://topic.csdn.net/u/20100223/15/644e6212-9fdc-42de-81ad-785d28ed71d3.html 讨论查询计划读取索引页的问题。
主要问题是: 在一张建有聚集索引(没有其它非聚集索引)的表执行select count(*) 。查询显示扫描了所有的索引中间层叶。从理论上讲,由于每个数据页都记录着它的上一个page和下一个page,那么最小的io读取应该是:
读根页--->读最小的中间页--->读数据叶子---->依次往后读全部数据页.
但实际上却是读取全部的索引中间页。
帖子中比较精彩的回复:
1 2 3 4 5 | 首先楼主的说法不够准确,根据我的测试结果来看,更准确的说法应该是sql server在做索引全扫(INDEX FULL scan)时,会先扫描全部的level 1的节点,然后在扫描处于level 0的叶节点。也就是说一次索引全扫的顺序是(这个顺序对于clustered INDEX和普通INDEX都是一样的): 读根页--->读最小的中间页(level 1以上的层)--->依次读level 1的所有节点的索引页---->读全部数据页 上述顺序可以在将数据表索引层数增加到3层以上时得到验证。 实际上我觉得sql server这样做是很有道理的,楼主认为的是所读的页越少,io就越小,这是不对的。对于sql server来说,一次io并不一定只读取一个页,很多时候特别是做表全扫或索引全扫时,sql server都会尽量的让一次io读取尽可能多的数据页,这样才能较少io的次数。为达到这个目的,sql server首先就要知道它到底需要读取那些页,知道要读取页的分布之后,就可以安排io调度器尽可能的将临近的数据页用一次io读取上来,而要知道需要读取数据页的分布就需要先读取到level 1上的所有页,以找出要读取io页的分布情况,然后再做最优的io读取安排。 同样的道理,如果sql server是先定位到叶节点的起始数据页,然后从依次的读取所有的数据页的话,那sql server就只能一次io读取一个数据页,然后找出对应的下一页的指针,再去读取下一个数据页,这样实际上花费的io会远比批量读取数据页多很多的。这就好比我们在做表的lookup时,如果需要lookup的行非常之多的话sql server会转而选择表扫描来达到目的的。 |
预读
我觉得SQL Server内部存在这样一个机制:
它会尽量将当前使用的及使用最频繁的表的数据都带到buffer中来,这跟buffer的算法有一定的关系。
清掉buffer後,你不做任何动作,隔几秒钟,sql会自动将master库中一些比较重要的表都带到buffer中,或者
你select * from tb where 1 <>1 ,尽管该查询不会扫描任何数据,但sql还是会自动将tb的所有索引页到带到
buffer中去,因为它可能觉得tb这个表後续可能会使用到.
关于计算逻辑读的数量
IAM页是否会计算在逻辑读中我都是持怀疑态度的。
比如说,一个堆表扫描时肯定是从IAM页开始的,然而用set statistics io on看到的逻辑读数量却只是表所占用的总页数,并没有加上相应的IAM页数量。
而索引扫描却更有意思,就像楼主说的那样,会比我们计算的数据页加索引页的页数还要多1个,但这1个却不知是从何而来的。按照堆表扫描的计算方法,我认为更不可能是IAM页,再说索引扫描也不会用到IAM页。
一直没有看到有描述逻辑读怎么计算的资料。
在没有详细资料的情况下要研究的这么精确,不纠结才怪
实际上: 是sql server 内部引擎设计上的问题。sql server 利用预读机制来改善IO。通过读取所有的索引中间层叶sql server能够知道预读哪些页面,而不是利用数据页面上的前后链接指针那样一页一页的读。 sql server 最大一次IO可以读取64页。
以下是从msdn上摘下来的帮助文档。
SQL Server 2008 联机丛书(2009 年 7 月)
SQL Server 数据库引擎实例的 I/O 包括逻辑读取和物理读取。每次数据库引擎从缓冲区高速缓存 请求页时都会发生逻辑读取。如果页当前不在缓冲区高速缓存中,物理读取将首先将页从磁盘复制到缓存中。
数据库引擎实例生成的读取请求由关系引擎控制,并由存储引擎优化。关系引擎决定最有效的访问方法(例如,表扫描、索引扫描或键读取);存储引擎的访问方法和缓冲区管理器组件确定要执行的读取的常规模式,并对实现访问方法所需的读取进行优化。执行批处理的线程将安排读取。
数据库引擎支持称为“预读”的性能优化机制。预读首先预测执行查询执行计划所需的数据和索引页,然后在查询实际使用这些页之前将它们读入缓冲区高速缓存。这样可以让计算和 I/O 重叠进行,从而充分利用 CPU 和磁盘。
预 读机制允许数据库引擎从一个文件中读取最多 64 个连续页 (512KB)。该读取作为缓冲区高速缓存中相应数量(可能是非相邻的)缓冲区的一次散播-聚集读取来执行。如果此范围内的任何页在缓冲区高速缓存中已存 在,当读取完成时,所读取的相应页将被放弃。如果相应页在缓存中已存在,也可以从任何一端“裁剪”页的范围。
有两种类型的预读:一种用于数据页,一种用于索引页。
读取数据页
用 于读取数据页的表扫描在数据库引擎中非常有效。SQL Server 数据库中的索引分配映射 (IAM) 页列出了表或索引使用的区。存储引擎可以读取 IAM 以生成必须读取的磁盘地址的排序列表。这使得存储引擎能够根据要读取的磁盘位置,将其 I/O 操作优化为按顺序执行的大型顺序读取。有关 IAM 页的详细信息,请参阅管理对象使用的空间 。
读取索引页
存储引擎按键的顺序依次读取索引页。例如,下图显示了一组叶级页的简化表示法,该组叶级页包含映射叶级页的键集和中间索引节点。有关索引中页的结构的详细信息,请参阅聚集索引结构 。
存 储引擎使用高于叶级的中间索引页上的信息为包含键的页安排序列预读。如果请求针对的是 ABC 到 DEF 之间的所有键,则存储引擎将首先读取高于叶级页的索引页,但它并不是仅仅按顺序读取页 504 到页 556(即指定范围内的包含键的最后一页)之间的每个数据页。相反,存储引擎将扫描中间索引页并生成必须要读取的叶级页的列表。然后,存储引擎会按键的顺 序安排所有读取。存储引擎还会识别出页 504/505 以及页 527/528 是相邻页,并执行一次散播读取,从而在单个操作中检索这些相邻页。如果在一个序列操作中要检索许多页,则存储引擎将一次安排一个读取块。完成这些读取子集 后,存储引擎将安排同等数量的新读取,直到安排完所需的全部读取。
存储引擎使用预提取加快非聚集索引的基表查找。 非聚集索引的叶级行包含指针,指向含有每个特定键值的数据行。存储引擎浏览非聚集索引的叶级页时,它也会开始计划异步读取已检索了其指针的数据行。这可以 使存储引擎在完成非聚集索引的扫描之前从基础表中检索数据行。无论表是否有聚集索引,都会使用预提取。SQL Server Enterprise 比 SQL Server 其他版本使用更多的预提取,可以预读更多页。在任何版本中都无法配置预提取的级别。有关非聚集索引的详细信息,请参阅非聚集索引结构 。
在 SQL Server Enterprise 中,高级扫描功能使得多项任务可以共享完全表扫描。如果 Transact-SQL 语句的执行计划需要扫描表中的数据页,并且数据库引擎检测到其他执行计划正在扫描该表,则数据库引擎会在第二个扫描的当前位置将第二个扫描加入第一个扫 描。数据库引擎会一次读取一页,并将每一页的行传递给这两个执行计划。此操作将一直持续到该表的结尾处。
此时,第一个执行 计划已有完整的扫描结果,而第二个执行计划仍必须检索在它加入正在进行的扫描之前读取的数据页。然后,第二个执行计划中的扫描将绕回到表的第一个数据页, 并从这里向前扫描到它加入第一个扫描时所处的位置。可以按这种方式组合任意数量的扫描。数据库引擎将循环遍历数据页,直到完成所有扫描。这种机制也称为 “走马灯式扫描”,说明了为何在没有 ORDER BY 子句的情况下无法保证 SELECT 语句所返回结果的顺序。
例 如,假设某个表有 500,000 页。UserA 执行了一条 Transact-SQL 语句,要求对该表进行扫描。当扫描已处理了 100,000 页时,UserB 执行了另一条 Transact-SQL 语句,要对同一个表进行扫描。数据库引擎将为页 100,001 之后的页安排一组读取请求,并将每页中的行同时传递回两个扫描。当扫描到页 200,000 时,UserC 执行了另一条 Transact-SQL 语句,要对同一个表进行扫描。则从页 200,001 开始,数据库引擎将把它读取的每一页中的行传递回所有三个扫描。当数据库引擎读取完第 500,000 行之后,UserA 的扫描就完成了,而 UserB 和 UserC 的扫描将绕回到页 1 开始读取。当数据库引擎到达页 100,000 时,UserB 的扫描就完成了。然后 UserC 的扫描将继续进行,直到它读取完页 200,000。此时,所有扫描便均已完成。
在没有高级扫描的情况下,每个用户都必须要争用缓冲区空间并因此导致磁盘臂争用。然后,会分别为每个用户读取一次相同的页,而不是一次读取并由多个用户共享,这样会降低性能并加重资源负担。
The read requests generated by an instance of Microsoft® SQL Server™ 2000 are controlled by the relational engine and further optimized by the storage engine. The access method used to read pages from a table, such as a table scan, an index scan, or a keyed read, determines the general pattern of reads that will be performed. The relational engine determines the most effective access method. This request is then given to the storage engine, which optimizes the reads required to implement the access method. The thread executing the batch schedules the reads.
Table scans are extremely efficient in SQL Server 2000. The IAM pages in a SQL Server 2000 database list the extents used by a table or index. The storage engine can read the IAM to build a sorted list of the disk addresses that must be read. This allows SQL Server 2000 to optimize its I/Os as large sequential reads that are done in sequence based on their location on the disk. SQL Server 2000 issues multiple serial read-ahead reads at once for each file involved in the scan. This takes advantage of striped disk sets. SQL Server 2000 Enterprise Edition dynamically adjusts the maximum number of read ahead pages based on the amount of memory present; it is fixed in all other editions of SQL Server 2000.
One part of the SQL Server 2000 Enterprise Edition advanced scan feature allows multiple tasks to share full table scans. If the execution plan of a SQL statement calls for a scan of the data pages in a table, and the relational database engine detects that the table is already being scanned for another execution plan, the database engine joins the second scan to the first, at the current location of the second scan. The database engine reads each page once and passes the rows from each page to both execution plans. This continues until the end of the table is reached. At that point, the first execution plan has the complete results of a scan, but the second execution plan must still retrieve the data pages that occur before the point at which it joined the in-progress scan. The scan for second execution plan then wraps back to the first data page of the table and scans forward to the point at which it joined the first scan. Any number of scans can be combined in this way, the database engine will keep looping through the data pages until it has completed all the scans.
For example, assume that you have a table with 500,000 pages. UserA executes a SQL statement that requires a scan of the table. When that scan has processed 100,000 pages, UserB executes another SQL statement that scans the same table. The database engine will schedule one set of read requests for pages after 100,001, and passes the rows from each page back to both scans. When the scan reaches the 200,000th page, UserC executes another SQL statement that scans the same table. Starting with page 200,001, the database engine passes the rows from each page it reads back to all three scans. After reading the 500,000th row, the scan for UserA is complete, and the scans for UserB and UserC wrap back and start reading pages starting with page 1. When the database engine gets to page 100,000, the scan for UserB is complete. The scan for Userc then keeps going alone until it reads page 200,000, at which point all the scans have been completed.
Reading Index Pages
SQL Server 2000 reads index pages serially in key order. For example, this illustration shows a simplified representation of a set of leaf pages containing a set of keys and the intermediate index node mapping the leaf pages.
SQL Server 2000 uses the information in the intermediate index page above the leaf level to schedule serial read-ahead I/Os for the pages containing the keys. If a request is made for all the keys from 'ABC' to 'DEF', the instance of SQL Server 2000 first reads the index page above the leaf page. It does not, however, simply read each individual data page in sequence from page 504 to page 556, the last one with keys in the desired range. Instead, the storage engine scans the intermediate index page and builds a list of the leaf pages that must be read. The storage engine then schedules all the I/Os in key order. The storage engine also recognizes that pages 504/505 and 527/528 are contiguous, and performs a single scatter-gather read to retrieve the adjacent pages in one operation. When there are many pages to be retrieved in a serial operation, SQL Server schedules a block of reads at a time. When a subset of these reads is completed, SQL Server schedules an equal number of new reads until all the needed reads have been scheduled.
SQL Server 2000 uses pre-fetching to speed the processing of non-clustered indexes. The leaf rows of a non-clustered index contain pointers to the data rows containing each specific key value. As the database engine reads through the leaf pages of the non-clustered index, it also starts scheduling asynchronous reads for the data rows whose pointers have already been retrieved. This allows the database engine to start retrieving rows before it has completed the scan of the non-clustered index. This process is followed regardless of whether or not the table has a clustered index. SQL Server 2000 Enterprise Edition uses more pre-fetching than other editions of SQL Server, and the level of pre-fetching is not configurable in any edition.
-- 创建测试数据库
CREATE DATABASE Db
GO
-- 对数据库进行备份
BACKUP DATABASE Db TO DISK = ' c:\db.bak ' WITH FORMAT
GO
-- 创建测试表
CREATE TABLE Db.dbo.TB_test(ID int )
-- 延时1秒钟,再进行后面的操作(这是由于SQL Server的时间精度最大为百分之三秒,不延时的话,可能会导致还原到时间点的操作失败)
WAITFOR DELAY ' 00:00:01 '
GO
-- 假设我们现在误操作删除了 Db.dbo.TB_test 这个表
DROP TABLE Db.dbo.TB_test
-- 保存删除表的时间
SELECT dt = GETDATE () INTO #
GO
-- 在删除操作后,发现不应该删除表 Db.dbo.TB_test
-- 下面演示了如何恢复这个误删除的表 Db.dbo.TB_test
-- 首先,备份事务日志(使用事务日志才能还原到指定的时间点)
BACKUP LOG Db TO DISK = ' c:\db_log.bak ' WITH FORMAT
GO
-- 接下来,我们要先还原完全备份(还原日志必须在还原完全备份的基础上进行)
RESTORE DATABASE Db FROM DISK = ' c:\db.bak ' WITH REPLACE ,NORECOVERY
GO
-- 将事务日志还原到删除操作前(这里的时间对应上面的删除时间,并比删除时间略早
DECLARE @dt datetime
SELECT @dt = DATEADD (ms, - 20 ,dt) FROM # -- 获取比表被删除的时间略早的时间
RESTORE LOG Db FROM DISK = ' c:\db_log.bak ' WITH RECOVERY,STOPAT = @dt
GO
-- 查询一下,看表是否恢复
SELECT * FROM Db.dbo.TB_test
/* --结果:
ID
-----------
(所影响的行数为 0 行)
-- */
-- 测试成功
GO
-- 最后删除我们做的测试环境
DROP DATABASE Db
DROP TABLE #
在sybase表上建立聚集索引可以提高键列的检索速度。这是索引的主要功能所在。
可是,聚集索引对于统计表上的行数count(*)有没有改善呢? 答案是否定的。
请看我下面的测试代码!
建立一张临时表test3
1 | create table test3(id int not null,name varchar(30) null) |
向表中插入测试数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | insert into test3 select 1,'liu' go insert into test3 select 2,'zhang' go insert into test3 select 3,'wang' go insert into test3 select 4,'li' go 循环插入 insert into test4 select count(*)+id,name from test4 go 18 1> select count(*) from test4 2> go ----------- 524288 (1 row affected) 循环插入了524288条记录! |
打开查询计划和统计查询计划时间的选项
1 2 3 4 | set showplan on go set statistics time on go |
表上没有加任何索引的情况下。
select count(*) from test4 的查询计划为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | 1> select count(*) from test4 2> go QUERY PLAN FOR STATEMENT 1 (at line 1). STEP 1 The type of query is SELECT. 2 operator(s) under root |ROOT:EMIT Operator (VA = 2) | | |SCALAR AGGREGATE Operator (VA = 1) | | Evaluate Ungrouped COUNT AGGREGATE. | | | | |SCAN Operator (VA = 0) | | | FROM TABLE | | | test4 | | | Table Scan. | | | Forward Scan. | | | Positioning at start of table. | | | Using I/O Size 32 Kbytes for data pages. | | | With MRU Buffer Replacement Strategy for data pages. Parse and Compile Time 0. Adaptive Server cpu time: 0 ms. ----------- 524288 Execution Time 1. Adaptive Server cpu time: 100 ms. Adaptive Server elapsed time: 156 ms. (1 row affected) |
select count(1) from test4 的查询计划为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 1> select count(1) from test4 2> go QUERY PLAN FOR STATEMENT 1 (at line 1). STEP 1 The type of query is SELECT. 2 operator(s) under root |ROOT:EMIT Operator (VA = 2) | | |SCALAR AGGREGATE Operator (VA = 1) | | Evaluate Ungrouped COUNT AGGREGATE. | | | | |SCAN Operator (VA = 0) | | | FROM TABLE | | | test4 | | | Table Scan. | | | Forward Scan. | | | Positioning at start of table. | | | Using I/O Size 32 Kbytes for data pages. | | | With MRU Buffer Replacement Strategy for data pages. Parse and Compile Time 0. Adaptive Server cpu time: 0 ms. ----------- 524288 Execution Time 1. Adaptive Server cpu time: 100 ms. Adaptive Server elapsed time: 220 ms. |
可以看出,count(*) 和count(1) 的执行计划是相同的。都执行了表扫描。
由于表上没有任何索引可供使用,select count(id) 和 select count(name) 都是执行了表扫描。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 1> select count(id) from test4 2> go QUERY PLAN FOR STATEMENT 1 (at line 1). STEP 1 The type of query is SELECT. 2 operator(s) under root |ROOT:EMIT Operator (VA = 2) | | |SCALAR AGGREGATE Operator (VA = 1) | | Evaluate Ungrouped COUNT AGGREGATE. | | | | |SCAN Operator (VA = 0) | | | FROM TABLE | | | test4 | | | Table Scan. | | | Forward Scan. | | | Positioning at start of table. | | | Using I/O Size 32 Kbytes for data pages. | | | With MRU Buffer Replacement Strategy for data pages. Parse and Compile Time 0. Adaptive Server cpu time: 0 ms. ----------- 524288 Execution Time 1. Adaptive Server cpu time: 100 ms. Adaptive Server elapsed time: 140 ms. (1 row affected) 1> select count(name) from test4 2> go QUERY PLAN FOR STATEMENT 1 (at line 1). STEP 1 The type of query is SELECT. 2 operator(s) under root |ROOT:EMIT Operator (VA = 2) | | |SCALAR AGGREGATE Operator (VA = 1) | | Evaluate Ungrouped COUNT AGGREGATE. | | | | |SCAN Operator (VA = 0) | | | FROM TABLE | | | test4 | | | Table Scan. | | | Forward Scan. | | | Positioning at start of table. | | | Using I/O Size 32 Kbytes for data pages. | | | With MRU Buffer Replacement Strategy for data pages. Parse and Compile Time 0. Adaptive Server cpu time: 0 ms. ----------- 524288 Execution Time 1. Adaptive Server cpu time: 100 ms. Adaptive Server elapsed time: 236 ms. (1 row affected) 1> |
下面考虑加入主键(聚集索引)pk_test4_id
1 2 | alter table test4 add constraint pk_test4_id primary key (id) go |
再次执行select count(*) from test4 和 select count(1) from test4
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | 1> select count(*) from test4 2> go QUERY PLAN FOR STATEMENT 1 (at line 1). STEP 1 The type of query is SELECT. 2 operator(s) under root |ROOT:EMIT Operator (VA = 2) | | |SCALAR AGGREGATE Operator (VA = 1) | | Evaluate Ungrouped COUNT AGGREGATE. | | | | |SCAN Operator (VA = 0) | | | FROM TABLE | | | test4 | | | Table Scan. | | | Forward Scan. | | | Positioning at start of table. | | | Using I/O Size 32 Kbytes for data pages. | | | With MRU Buffer Replacement Strategy for data pages. Parse and Compile Time 0. Adaptive Server cpu time: 0 ms. ----------- 524288 Execution Time 2. Adaptive Server cpu time: 200 ms. Adaptive Server elapsed time: 736 ms. (1 row affected) 1> select count(1) from test4 2> go QUERY PLAN FOR STATEMENT 1 (at line 1). STEP 1 The type of query is SELECT. 2 operator(s) under root |ROOT:EMIT Operator (VA = 2) | | |SCALAR AGGREGATE Operator (VA = 1) | | Evaluate Ungrouped COUNT AGGREGATE. | | | | |SCAN Operator (VA = 0) | | | FROM TABLE | | | test4 | | | Table Scan. | | | Forward Scan. | | | Positioning at start of table. | | | Using I/O Size 32 Kbytes for data pages. | | | With MRU Buffer Replacement Strategy for data pages. Parse and Compile Time 0. Adaptive Server cpu time: 0 ms. ----------- 524288 Execution Time 0. Adaptive Server cpu time: 0 ms. Adaptive Server elapsed time: 500 ms. (1 row affected) 1> |
由上可以看出,聚集索引对于select count(*) 几乎没有扫描影响。堆表和聚集索引表上的count是没有什么区别的,甚至于聚集索引表上的IO还要多2(这是因为多了两个聚集索引的数据块造成的)。其 实聚集索引并没有单独的保留所有索引列的信息,而只是将表中的行的物理顺序按照聚集索引列的顺序整理了一下,因此对聚集索 引的扫描和对堆表的扫描是一样的,没有什么本质上的区别。
添加id列上的非聚集索引idx_test4_id
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | 1> create index idx_test4_id on test4(id) 2> go 1> sp_help test4 2> go Name Owner Object_type Create_date ----- ----- ----------- ------------------- test4 dbo user table Feb 25 2010 3:44PM (1 row affected) Column_name Type Length Prec Scale Nulls Default_name Rule_name Access_Rule_name Computed_Column_object Identity ----------- ------- ------ ---- ----- ----- ------------ --------- ---------------- ---------------------- ---------- id int 4 NULL NULL 0 NULL NULL NULL NULL 0 name varchar 30 NULL NULL 1 NULL NULL NULL NULL 0 Object has the following indexes index_name index_keys index_description index_max_rows_per_page index_fillfactor index_reservepagegap index_created index_local ------------ ---------- ----------------- ----------------------- ---------------- -------------------- ------------------- ------------ pk_test4_id id clustered, unique 0 0 0 Feb 25 2010 4:04PM Global Index idx_test4_id id nonclustered 0 0 0 Feb 25 2010 4:52PM Global Index (2 rows affected) index_ptn_name index_ptn_seg ----------------------- ------------- pk_test4_id_1399673003 default idx_test4_id_1399673003 default (2 rows affected) No defined keys for this object. name type partition_type partitions partition_keys ----- ---------- -------------- ---------- -------------- test4 base table roundrobin 1 NULL (1 row affected) partition_name partition_id pages row_count segment create_date ---------------- ------------ ----- --------- ------- ------------------- test4_1399673003 1399673003 2132 524288 default Feb 25 2010 4:04PM Partition_Conditions -------------------- NULL Avg_pages Max_pages Min_pages Ratio(Max/Avg) Ratio(Min/Avg) ----------- ----------- ----------- --------------------------- --------------------------- 2132 2132 2132 1.000000 1.000000 Lock scheme Allpages The attribute 'exp_row_size' is not applicable to tables with allpages lock scheme. The attribute 'concurrency_opt_threshold' is not applicable to tables with allpages lock scheme. exp_row_size reservepagegap fillfactor max_rows_per_page identity_gap ascinserts ------------ -------------- ---------- ----------------- ------------ ----------- 0 0 0 0 0 0 (1 row affected) concurrency_opt_threshold optimistic_index_lock dealloc_first_txtpg ------------------------- --------------------- ------------------- 0 0 0 (return status = 0) |
此时再次执行select count(*) 和select count(1)。查询计划如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | 1> select count(*) from test4 2> go QUERY PLAN FOR STATEMENT 1 (at line 1). STEP 1 The type of query is SELECT. 2 operator(s) under root |ROOT:EMIT Operator (VA = 2) | | |SCALAR AGGREGATE Operator (VA = 1) | | Evaluate Ungrouped COUNT AGGREGATE. | | | | |SCAN Operator (VA = 0) | | | FROM TABLE | | | test4 | | | Index : idx_test4_id | | | Forward Scan. | | | Positioning at index start. | | | Index contains all needed columns. Base table will not be read. | | | Using I/O Size 32 Kbytes for index leaf pages. | | | With MRU Buffer Replacement Strategy for index leaf pages. Parse and Compile Time 0. Adaptive Server cpu time: 0 ms. ----------- 524288 Execution Time 0. Adaptive Server cpu time: 0 ms. Adaptive Server elapsed time: 703 ms. (1 row affected) 1> select count(1) from test4 2> go QUERY PLAN FOR STATEMENT 1 (at line 1). STEP 1 The type of query is SELECT. 2 operator(s) under root |ROOT:EMIT Operator (VA = 2) | | |SCALAR AGGREGATE Operator (VA = 1) | | Evaluate Ungrouped COUNT AGGREGATE. | | | | |SCAN Operator (VA = 0) | | | FROM TABLE | | | test4 | | | Index : idx_test4_id | | | Forward Scan. | | | Positioning at index start. | | | Index contains all needed columns. Base table will not be read. | | | Using I/O Size 32 Kbytes for index leaf pages. | | | With MRU Buffer Replacement Strategy for index leaf pages. Parse and Compile Time 0. Adaptive Server cpu time: 0 ms. ----------- 524288 Execution Time 1. Adaptive Server cpu time: 100 ms. Adaptive Server elapsed time: 93 ms. (1 row affected) |
可以看出查询引擎使用了非聚集索引idx_test4_id ,执行时间明显减少。因为计算行数这个操作对于全表扫描或是非聚集索引的扫描结果是一样的,而相对来说非聚集索引的数据量是肯定会比表的数据量小很多的,同样的做一次全部扫描所花费的IO也就要少很多了。
select count(id) 也是利用了非聚集索引 idx_test4_id。
结论:
count(*)和count(1)执行的效率是完全一样的。
如果是对特定的列做count的话建立这个列的非聚集索引能对count有很大的帮助。
————————————————————————————————————
——— 本文为andkylee个人原创,请在尊重作者劳动成果的前提下进行转载;
——— 转载务必注明原始出处 : http://www.dbainfo.net
——— 关键字: sybase 聚簇索引 非聚簇索引 查询计划 行数 count
————————————————————————————————————