动作描述 | 使用聚集索引 | 使用非聚集索引 |
列经常被分组排序 | 应 | 应 |
返回某范围内的数据 | 应 | 不应--物理顺序不同 |
一个或极少不同值 | 不应 | 不应--selectivity小 |
小数目的不同值 | 应 | 不应 |
大数目的不同值 | 不应 | 应 |
频繁更新的列 | 不应 | 应 |
外键列 | 应 | 应 |
主键列 | 应 | 应 |
频繁修改索引列 | 不应 | 应 |
select a,b from table where c;
-- 2000中索引覆盖为
create index idx on t(c,a,b)
-- 2005中索引覆盖为
create index idx on t(c) include (a,b)
通过扫描C键值所在的索引上层结构快速找到where条件所需的边界,然后扫描子叶层;循环扫描到a,b的记录位置
-- ??我觉得这里有一个可以测试的地方就是到底是索引覆盖还是date字段上建立聚集索引好,上一篇文章中有一个查询性能比较:
1 . 返回行数较多:索引覆盖 > 聚集索引 > 表扫描 > 堆集的非聚集索引 > 聚集的非聚集索引
2 . 返回行数较少:索引覆盖 = 聚集索引 > 堆集的非聚集索引 > 聚集的非聚集索引 > 表扫描
-- SQL Server 2005 Performance Tuning性能调校 代码列表 6.14:通过各种索引,测试所花的 IO 页数.sql
USE Credit
GO
EXEC spCleanIdx ' Charge '
-- 要求返回 IO 的统计,也就是分页访问的数目
SET STATISTICS IO ON
-- 没有索引的页数
-- 表 'charge'。扫描计数 1,逻辑读取 584,实际读取 0,读取前读取 0,LOB 逻辑读取 0,LOB 实际读取 0,LOB 读取前读取 0。
SELECT charge_no FROM charge
WHERE charge_amt BETWEEN 20 AND 3000
-- 通过聚簇索引查询的页数
-- 表 'charge'。扫描计数 1,逻辑读取 419,实际读取 0,读取前读取 14,LOB 逻辑读取 0,LOB 实际读取 0,LOB 读取前读取 0。
CREATE CLUSTERED INDEX ix_charge_amt ON Charge(charge_amt)
SELECT charge_no FROM charge WHERE charge_amt BETWEEN 20 AND 3000
DROP INDEX Charge.ix_charge_amt
-- 强制通过非聚簇索引查询的页数,用错索引比不用索引糟糕很多倍
CREATE INDEX ix_charge_amt ON Charge(charge_amt)
-- 表 'charge'。扫描计数 5,逻辑读取 60198,实际读取 0,读取前读取 3,LOB 逻辑读取 0,LOB 实际读取 0,LOB 读取前读取 0。
-- 表 'Worktable'。扫描计数 0,逻辑读取 0,实际读取 0,读取前读取 0,LOB 逻辑读取 0,LOB 实际读取 0,LOB 读取前读取 0。
SELECT charge_no FROM charge WITH ( INDEX (ix_charge_amt)) WHERE charge_amt BETWEEN 20 AND 3000
DROP INDEX Charge.ix_charge_amt
-- 通过字段顺序不适用的覆盖索引查询的页数
CREATE INDEX ix_charge_amt ON Charge(charge_no,charge_amt)
-- 表 'charge'。扫描计数 1,逻辑读取 292,实际读取 0,读取前读取 0,LOB 逻辑读取 0,LOB 实际读取 0,LOB 读取前读取 0。
SELECT charge_no FROM charge WHERE charge_amt BETWEEN 20 AND 3000
DROP INDEX Charge.ix_charge_amt
-- 通过覆盖索引查询的页数
CREATE INDEX ix_charge_amt ON Charge(charge_amt,charge_no)
-- 表 'charge'。扫描计数 1,逻辑读取 175,实际读取 0,读取前读取 0,LOB 逻辑读取 0,LOB 实际读取 0,LOB 读取前读取 0。
SELECT charge_no FROM charge WHERE charge_amt BETWEEN 20 AND 3000
DROP INDEX Charge.ix_charge_amt
-- 通过字段顺序不适用的覆盖索引查询的页数
CREATE INDEX ix_charge_amt ON Charge(charge_no) INCLUDE(charge_amt)
-- 表 'charge'。扫描计数 1,逻辑读取 290,实际读取 0,读取前读取 0,LOB 逻辑读取 0,LOB 实际读取 0,LOB 读取前读取 0。
SELECT charge_no FROM charge WHERE charge_amt BETWEEN 20 AND 3000
DROP INDEX Charge.ix_charge_amt
-- 透过子叶层覆盖索引查询的页数
CREATE INDEX ix_charge_amt ON Charge(charge_amt) INCLUDE(Charge_no)
-- 表 'charge'。扫描计数 1,逻辑读取 174,实际读取 0,读取前读取 0,LOB 逻辑读取 0,LOB 实际读取 0,LOB 读取前读取 0。
SELECT charge_no FROM charge WHERE charge_amt BETWEEN 20 AND 3000
DROP INDEX Charge.ix_charge_amt
--是否值得建索引
无论在哪个数据库里都会有这样的疑问,但是这里永远有三个标准帮助我们来选择,他们是selectivity,density,distribution
select * from test where id = 1 -- 假定select count(*) from test 是10000 那么这个的选择性就是 1/10000,选择性很高,适合建立索引
select * from test where id > 1 -- 假定select count(*) from test 是10000 那么这个的选择性就是 9999/10000,选择性很低,不适合建立索引
除非在id字段是聚集索引,如果采用非聚集索引,反而变成需要读至少9999页以上,因为每读取一条记录时都要将整页读出,再从中取出目标记录,就算数据记录在同一页上也要读多次
select 1 / ( select count ( distinct id) from test)
-- 当结果越小也就是唯一性越高,就越合适建立索引,也可以使用以下方法检测看传回的All Density值
Create index idx_id on test(id)
DBCC Show_Statistics(test,idx_id)
select * from sys.dm_db_missing_index_groups
select * from sys.dm_db_missing_index_group_stats
select * from sys.dm_db_missing_index_details
SELECT mig. * , statement AS table_name,
column_id, column_name, column_usage
FROM sys.dm_db_missing_index_details AS mid
CROSS APPLY sys.dm_db_missing_index_columns (mid.index_handle)
INNER JOIN sys.dm_db_missing_index_groups AS mig ON mig.index_handle = mid.index_handle
ORDER BY mig.index_group_handle, mig.index_handle, column_id;
通过动态管理对象sys.dm_db_missing_index_details和sys.dm_db_missing_index_columns函数返回的结果呈现所需索引键数据行是相等(Equality),不相等(Inequality)或包容(Include)sys.dm_db_missing_index_details视图会在Equality_Columns,Inequality_Columns或Include_Columns等行返回这些信息sys.dm_db_missing_index_columns函数会在其column_usage数据行中返回此信息所以最后的规则就是将Equality_Columns放在最前边,Inequality_Columns随后,然后把Include_Columns放到Include子句中create index idx_test on test(Equality_Columns,Inequality_Columns) include (Include_Columns_1,Include_Columns_2)
--一些测试(这个是我看别人文章的总结,忘记出处了,抱歉)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi > '' 2004 - 1 - 1 ''
--用时:6343毫秒(提取100万条) 整年
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi > '' 2004 - 6 - 6 ''
--用时:3170毫秒(提取50万条) 半年
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi = '' 2004 - 9 - 16 ''
--用时:3326毫秒(和上句的结果一样.如果采集的数量一样,那么用大于号和等于号是一样的,和半年的数据量一样)
select gid,fariqi,neibuyonghu,reader,title from Tgongwen where fariqi > '' 2004 - 1 - 1 '' and fariqi < '' 2004 - 6 - 6 ''
--用时:3280毫秒 半年
--得出以上速度的方法是:在各个select语句前加:
declare @d datetime
set @d = getdate ()
-- SQL Query
select [ 语句执行花费时间(毫秒) ] = datediff (ms, @d , getdate ())
--碎片
USE Tempdb
-- 测试统计过期的结果
SET NOCOUNT ON
SET STATISTICS IO OFF
SET STATISTICS PROFILE OFF
CREATE TABLE tblTest(
UserId INT IDENTITY ( 1 , 1 ) PRIMARY KEY NONCLUSTERED ,
UserName NVARCHAR ( 20 ),
Gender NCHAR ( 1 ))
-- 一开始构造 100000 笔 '女' 一笔 '男' 的悬殊记录差异
INSERT tblTest VALUES ( ' Hello World ' , ' 男 ' )
DECLARE @int INT
SET @int = 1
WHILE @int < 100000
BEGIN
INSERT tblTest VALUES ( ' Hello ' + CONVERT ( NVARCHAR , @int ),
-- CASE WHEN @int%2 = 0 THEN '男' ELSE '女' END
' 女 '
)
SET @int = @int + 1
END
-- 执行计划建立,更新,删除命令
ALTER DATABASE SET
CREATE STATISTICS
DBCC SHOW_STATISTICS
sp_help ' et_order '
DBCC SHOW_STATISTICS ( ' et_order ' , idx_et_0);
DROP STATISTICS
sp_autostats
sp_createstats
UPDATE STATISTICS
-- 此时建立索引所同时产生的统计会记录如此悬殊的比值
CREATE INDEX idxGender ON tblTest(Gender)
EXEC sp_helpindex tblTest
-- 没有单独的统计数据
EXEC sp_helpstats tblTest
-- 统计是正确的,索引合用于当下的查询
SET STATISTICS IO ON
SELECT * FROM tblTest WHERE Gender = ' 男 '
-- 强迫表扫描
SELECT * FROM tblTest WITH ( INDEX ( 0 )) WHERE Gender = ' 男 '
SET STATISTICS IO OFF
-- 故意要求不要自动更新统计数据
-- EXEC sp_dboption 'Credit','Auto Update Statistics', { TRUE | FALSE} --针对整个表
EXEC sp_autostats ' tblTest ' , ' OFF ' ,idxGender
-- 将记录改成 1:1
UPDATE tblTest SET Gender = ' 男 ' WHERE UserID % 2 = 0
SELECT Gender, COUNT ( * ) FROM tblTest GROUP BY Gender
-- 比对一下用错索引时,两者的 I/O 差异
SET STATISTICS IO ON
-- 通过 SET STATISTICS PROFILE 输出的 Rows 和 EstimateRows
-- 可以比较真实与估计的记录数差异
SET STATISTICS PROFILE ON
SELECT * FROM tblTest WHERE Gender = ' 男 '
-- 强迫表扫描
SELECT * FROM tblTest WITH ( INDEX ( 0 )) WHERE Gender = ' 男 '
DBCC SHOW_STATISTICS(tblTest,idxGender) -- 这个是建立在统计信息基础上的,上边把统计信息停止后,这个返回的结果是错误的
-- 做完统计更新后,可以再试一次前述的范例
-- 但要先清除旧的运行计划
UPDATE STATISTICS tblTest
DBCC FREEPROCCACHE