v使用索引的注意事项及常见场景、案例
2017-11-12 20:13
736 查看
索引的原理与作用,各种书籍和网络上的介绍可以说是铺天盖地,基本上主流数据库系统的也都是一致的。选择索引字段的原则,比如外键字段、数据类型较小的字段、经常用于查询或排序的字段、表关联的字段等等,在此不做赘述。本人在工作中见到过很多人创建的索引,回想自己以前也会有理论知识空洞的体会,总感觉理论知识无法与具体的工作问题相匹配。在此仅以工作学习中积累的一点经验和问题场景整理以飨读者。先把常见的注意事项整理如下:
索引应该建在选择性高的字段上(键值唯一的记录数/总记录条数),选择性越高索引的效果越好、价值越大,唯一索引的选择性最高;
组合索引中字段的顺序,选择性越高的字段排在最前面;
where条件中包含两个选择性高的字段时,可以考虑分别创建索引,引擎会同时使用两个索引(在OR条件下,应该说必须分开建索引);
不要重复创建彼此有包含关系的索引,如index1(a,b,c)、index2(a,b)、index3(a);
组合索引的字段不要过多,如果超过4个字段,一般需要考虑拆分成多个单列索引或更为简单的组合索引;
最后需要提醒的是,不要滥用索引。因为过多的索引不仅仅会增加物理存储的开销,对于插入、删除、更新操作也会增加处理上的开销,而且会增加优化器在选择索引时的计算代价。
因此太多的索引与不充分、不正确的索引对性能都是毫无益处的。一言以蔽之,索引的建立必须慎重,对每个索引的必要性都应该经过仔细分析,要有建立的依据。
举下面一个场景的例子,创建这样的索引是有效的吗?
再比如,该表最常使用的SQL场景有以下两种类型,应该如何创建索引?
以执行计划和逻辑IO的统计数据显示,两个场景的测试结果都是后者索引有明显的效果,大家有兴趣可以自己测试验证一下。当然,生产环境远比这些要复杂,各表的数据量及数据分布情况也会影响引擎的执行方式,引擎对索引选择与要求也会不一样,此处仅以简单语句做示例进行说明。
组合索引查询的各种场景:
组合索引Index(A,B,C)
下面条件可以用上该组合索引查询:
A>5
A=5ANDB>6
A=5ANDB=6ANDC=7
A=5ANDB=6ANDCIN(2,3)
下面条件将不能用上组合索引查询:
B>5——查询条件不包含组合索引首列字段
B=6ANDC=7——理由同上
下面条件将能用上部分组合索引查询:
A>5ANDB=2——当范围查询使用第一列,查询条件仅仅能使用第一列
A=5ANDB>6ANDC=2——范围查询使用第二列,查询条件仅仅能使用前二列
A=5ANDBIN(2,3)ANDC=2——理由同上
组合索引排序的各种场景:
组合索引Index(A,B)
下面条件可以用上组合索引排序:
ORDERBYA——首列排序
A=5ORDERBYB——第一列过滤后第二列排序
ORDERBYADESC,BDESC——注意,此时两列以相同顺序排序
A>5ORDERBYA——数据检索和排序都在第一列
下面条件不能用上组合索引排序:
ORDERBYB——排序在索引的第二列
A>5ORDERBYB——范围查询在第一列,排序在第二列
AIN(1,2)ORDERBYB——理由同上
ORDERBYAASC,BDESC——注意,此时两列以不同顺序排序
索引合并的简单说明:
数据库能同时使用多个索引
SELECT*FROMTBWHEREA=5ANDB=6
能分别使用索引(A)和(B);
对于这个语句来说,创建组合索引(A,B)更好;
最终是采用组合索引,还是两个单列索引?主要取决于应用系统中是否存在这类语句:SELECT*FROMTBWHEREB=6
SELECT*FROMTBWHEREA=5ORB=6
组合索引(A,B)不能用于此查询(目前的数据库也很智能,部分OR条件也能够使用组合索引,但效果不是很稳定);
很明显,分别创建索引(A)和(B)会更好;
删除无效的冗余索引
TB表有两个索引(A,B)和(A),对应两种SQL语句:SELECT*FROMTBWHEREA=5ANDB=6和SELECT*FROMTBWHEREA=5
执行时,并不是WHEREA=5就用(A);WHEREA=5ANDB=6就用(A,B);
其查询优化器会使用其中一个以前常用索引,要么都用(A,B),要么都用(A)。
所以应该删除索引(A),它已经被(A,B)包含了,没有任何存在的必要。
附1,查询指定表的数据量与索引定义情况:
附2,借助性能视图,查询数据表的SQL访问方式
附3,索引重建示例
补充:
Where条件中Or的两组条件如果分别落在两个数据表上,即使对应的字段都已创建索引,引擎也是无法使用索引的。如下SQL,此语句实际上仅返回一条数据,但对于TRFKZL和TRHBZL来说,Oracle、SqlServer都是进行全表扫描。
按照建议更改SQL写法,走索引查找,响应时间在1秒以内。当然,从原始语句的筛选条件也能够感觉到怪怪的,根本上来讲应该是个设计问题。
分类:Oracle,SQLServer
索引应该建在选择性高的字段上(键值唯一的记录数/总记录条数),选择性越高索引的效果越好、价值越大,唯一索引的选择性最高;
组合索引中字段的顺序,选择性越高的字段排在最前面;
where条件中包含两个选择性高的字段时,可以考虑分别创建索引,引擎会同时使用两个索引(在OR条件下,应该说必须分开建索引);
不要重复创建彼此有包含关系的索引,如index1(a,b,c)、index2(a,b)、index3(a);
组合索引的字段不要过多,如果超过4个字段,一般需要考虑拆分成多个单列索引或更为简单的组合索引;
最后需要提醒的是,不要滥用索引。因为过多的索引不仅仅会增加物理存储的开销,对于插入、删除、更新操作也会增加处理上的开销,而且会增加优化器在选择索引时的计算代价。
因此太多的索引与不充分、不正确的索引对性能都是毫无益处的。一言以蔽之,索引的建立必须慎重,对每个索引的必要性都应该经过仔细分析,要有建立的依据。
举下面一个场景的例子,创建这样的索引是有效的吗?
select* fromt1,t2 wheret1.col_1=t2.abandt1.col_2in(12,38); --创建索引如下 createindexidx_t1_queryont1(col_1,col_2);
--或者仅创建索引如下
createindexidx_t1_col2ont1(col_2);
再比如,该表最常使用的SQL场景有以下两种类型,应该如何创建索引?
select*
fromt1
wheret1.PartId='xxxx'andt1.STATE=2andt1.PROCID='yyyy'
select*
fromt1
where(t.PartId='xxxx'ort1.ActualPartId='xxxx')andt1.STATE=2andt1.PROCID='yyyy'
--创建一个“全覆盖的索引”,把查询条件都包含的索引
createindexidx_t1_queryont1(partId,actualpartId,state,procid);
--还是分开创建如下两个索引
createindexidx_t1_PartIdont1(partId,state,procid)
createindexidx_t1_actualPartIdont1(actualpartId,state,procid)
以执行计划和逻辑IO的统计数据显示,两个场景的测试结果都是后者索引有明显的效果,大家有兴趣可以自己测试验证一下。当然,生产环境远比这些要复杂,各表的数据量及数据分布情况也会影响引擎的执行方式,引擎对索引选择与要求也会不一样,此处仅以简单语句做示例进行说明。
组合索引查询的各种场景:
组合索引Index(A,B,C)
下面条件可以用上该组合索引查询:
A>5
A=5ANDB>6
A=5ANDB=6ANDC=7
A=5ANDB=6ANDCIN(2,3)
下面条件将不能用上组合索引查询:
B>5——查询条件不包含组合索引首列字段
B=6ANDC=7——理由同上
下面条件将能用上部分组合索引查询:
A>5ANDB=2——当范围查询使用第一列,查询条件仅仅能使用第一列
A=5ANDB>6ANDC=2——范围查询使用第二列,查询条件仅仅能使用前二列
A=5ANDBIN(2,3)ANDC=2——理由同上
组合索引排序的各种场景:
组合索引Index(A,B)
下面条件可以用上组合索引排序:
ORDERBYA——首列排序
A=5ORDERBYB——第一列过滤后第二列排序
ORDERBYADESC,BDESC——注意,此时两列以相同顺序排序
A>5ORDERBYA——数据检索和排序都在第一列
下面条件不能用上组合索引排序:
ORDERBYB——排序在索引的第二列
A>5ORDERBYB——范围查询在第一列,排序在第二列
AIN(1,2)ORDERBYB——理由同上
ORDERBYAASC,BDESC——注意,此时两列以不同顺序排序
索引合并的简单说明:
数据库能同时使用多个索引
SELECT*FROMTBWHEREA=5ANDB=6
能分别使用索引(A)和(B);
对于这个语句来说,创建组合索引(A,B)更好;
最终是采用组合索引,还是两个单列索引?主要取决于应用系统中是否存在这类语句:SELECT*FROMTBWHEREB=6
SELECT*FROMTBWHEREA=5ORB=6
组合索引(A,B)不能用于此查询(目前的数据库也很智能,部分OR条件也能够使用组合索引,但效果不是很稳定);
很明显,分别创建索引(A)和(B)会更好;
删除无效的冗余索引
TB表有两个索引(A,B)和(A),对应两种SQL语句:SELECT*FROMTBWHEREA=5ANDB=6和SELECT*FROMTBWHEREA=5
执行时,并不是WHEREA=5就用(A);WHEREA=5ANDB=6就用(A,B);
其查询优化器会使用其中一个以前常用索引,要么都用(A,B),要么都用(A)。
所以应该删除索引(A),它已经被(A,B)包含了,没有任何存在的必要。
附1,查询指定表的数据量与索引定义情况:
--Sqlserver:
sp_helpindex'tableName';
sp_spaceused'tableName';
dbccShowContig('tableName')withall_indexes;
selectt.name,t.indid,t.rowcnt,t.*
fromsysindexest
wheret.id=OBJECT_ID('tkk0107')andt.indidin(0,1);--t.status!=8388672
selectt2.nametabName,t3.nameindName,t4.namecolName,t1.*
fromsys.index_columnst1
joinsys.tablest2ont1.object_id=t2.object_id
joinsys.indexest3ont2.object_id=t3.object_idandt1.index_id=t3.index_id
joinsys.columnst4ont2.object_id=t4.object_idandt1.column_id=t4.column_id
wheret2.name='tableName'
orderbyt3.name,t1.index_column_id
--Oracle:
selectt.NUM_ROWS,t.BLOCKS,t.Logging,t.TEMPORARY,t.last_analyzed,t.*
fromuser_tablest
wheret.TABLE_NAME=upper('tkk0107');
selectt.index_name,t.distinct_keys,t.num_rows,t.sample_size,t.last_analyzed
,t.blevel,t.leaf_blocks,t.status,t.*
fromuser_indexest
wheret.table_name=upper('tkk0107')
orderbyt.index_name;
selectt.*
fromuser_ind_columnst
wheret.TABLE_NAME=upper('tkk0107')
orderbyt.INDEX_NAME,t.COLUMN_POSITION;
附2,借助性能视图,查询数据表的SQL访问方式
--Oracle,根据共享池中的数据,统计指定表的访问SQL
withshas
(
selectmax(t.sql_id)sql_id,substring(t.SQL_TEXT,0,100)sql_text,count(1)usecounts--sum(executions)
fromv$sqlt
wheret.SQl_textlike'%table_name%'
groupbysubstring(t.SQL_TEXT,0,100)
)
selectsh.*,t.SQL_FULLTEXT
fromshjoinv$sqltonsh.sql_id=t.sql_id
orderbysh.usecountsdesc;
--sqlserver
withshas(
selectcp.cacheobjtype,cp.objtype,max(cp.plan_handle)plan_handle
,left(dt.text,100)sql_text,sum(cp.usecounts)usecounts
fromsys.dm_exec_cached_planscp
crossapplysys.dm_exec_sql_text(cp.plan_handle)dt
wheredt.textlike'%workitem%'
groupbycp.cacheobjtype,cp.objtype,left(dt.text,100)
)
selectsh.*,dt.textassql_fulltext
fromshcrossapplysys.dm_exec_sql_text(sh.plan_handle)dt
orderbysh.usecountsdesc;
--SqlserverIdentifyingUnusedIndexes
SELECTOBJECT_NAME(t.object_id)asobjName
,s.name,s.indid,s.dpages*8/1024mb,t.*
FROMsys.dm_db_index_usage_statst
joinsysindexessont.object_id=s.idandt.index_id=s.indid
wheret.user_seeks=0
--andt.user_scans=0
--andt.user_lookups=0
orderbyt.object_id,t.index_id
--2005分区后的准确大小
SELECT*
FROMsys.dm_db_index_physical_stats(DB_ID(),OBJECT_ID('LCGS609999.workitem'),21,null,null)
--Oracle提供如下方式,对索引进行有效性分析,经过分析的索引信息存储在index_stats数据字典
analyzeindexIDX_WORKITEM_PARTICIPANTvalidatestructure;
--当删除率大于15%时,考虑索引重建
selectt.name,t.del_lf_rows,t.lf_rows-t.del_lf_rowsaslf_rows_used
,to_char((t.del_lf_rows/t.lf_rows)*100,'999.999')asratio,t.*
fromindex_statst
wheret.name=upper('index_name');
--监视索引的使用情况,但此种方法仅能知道该索引有没有被使用,不知道使用的频率
alterindexIDX_WORKITEM_PARTICIPANTmonitoringusage;
alterindexIDX_WORKITEM_PARTICIPANTnomonitoringusage;
select*fromv$object_usagetwheret.table_name=upper('');
--获得索引使用频率的脚本
WITHQAS(
SELECT
S.OWNERA_OWNER,
TABLE_NAMEA_TABLE_NAME,
INDEX_NAMEA_INDEX_NAME,
INDEX_TYPEA_INDEX_TYPE,
SUM(S.BYTES)/1024/1024A_MB
FROMDBA_SEGMENTSS
JOINDBA_INDEXESIONI.INDEX_NAME=S.SEGMENT_NAME
WHERES.OWNER=USERANDI.OWNER=USER
GROUPBYS.OWNER,TABLE_NAME,INDEX_NAME,INDEX_TYPE
HAVINGSUM(S.BYTES)>(1024*1024*100)--超过100M的索引
)
SELECT/*+NO_QUERY_TRANSFORMATION(S)*/
A_OWNEROWNER,
A_TABLE_NAMETABLE_NAME,
A_INDEX_NAMEINDEX_NAME,
A_INDEX_TYPEINDEX_TYPE,
A_MBMB,
DECODE(OPTIONS,null,'-',OPTIONS)INDEX_OPERATION,
COUNT(OPERATION)NR_EXEC
FROMQ
LEFTJOINDBA_HIST_SQL_PLANDONQ.A_OWNER=D.OBJECT_OWNERANDQ.A_INDEX_NAME=D.OBJECT_NAME
WHEREROWNUM<10
GROUPBYA_OWNER,A_TABLE_NAME,A_INDEX_NAME,A_INDEX_TYPE
,A_MB,DECODE(OPTIONS,null,'-',OPTIONS)
ORDERBYA_OWNER,A_TABLE_NAME,A_INDEX_NAME,A_INDEX_TYPE
,A_MBDESC,NR_EXECDESC;
附3,索引重建示例
--查出系统中数据量较大的表,重建索引、收集更新统计信息
--a)Sqlserver:
selectOBJECT_NAME(t.id)AStableName,t.rows,t.*
fromsys.sysindexest
wheret.indidin(0,1)
orderbyt.rowsdesc;
--查看统计信息
sp_helpstats'tkk0107'
dbccshow_statistics('tkk0107','columnName');
--重建索引、更新统计信息
ALTERINDEXALL|indexNameONtableNameREBUILDWITH(ONLINE=ON,MAXDOP=16);
UPDATESTATISTICStableName;
--b)Oracle:
selectt.table_name,t.num_rows,t.*
fromuser_tablest
wheret.num_rows>0
orderbyt.num_rowsdesc;
--查看统计信息
SELECTt.*
FROMdba_tab_col_statisticst
WHEREt.table_name=upper('tkk0107')andt.owner=user;
--重建索引、更新统计信息
ALTERINDEXindexNameREBUILDONLINENOLOGGINGPARALLEL4;
ANALYZETABLEtableNameCOMPUTESTATISTICS;
ANALYZEINDEXindexNameCOMPUTESTATISTICS;
--全库重建索引的方法:
--Sqlserver:
execsp_msforeachtable'DBCCDBREINDEX(''?'')'
--Oracle:
DECLARECURSORmyCurIS
selectINDEX_NAMEfromuser_indexes
WHERETABLE_NAME='GSPAURESULT'ANDINDEX_TYPE='NORMAL';
v_cnamemyCur%rowtype;
vsSqlvarchar2(256);
begin
openmyCur;
loop
fetchmyCurintov_cname;
exitwhenmyCur%notfound;
vsSql:='ALTERINDEX'||v_cname.INDEX_NAME||'REBUILDONLINENOLOGGINGPARALLEL4';
EXECUTEIMMEDIATEvsSql;
endloop;
closemyCur;
end;
补充:
Where条件中Or的两组条件如果分别落在两个数据表上,即使对应的字段都已创建索引,引擎也是无法使用索引的。如下SQL,此语句实际上仅返回一条数据,但对于TRFKZL和TRHBZL来说,Oracle、SqlServer都是进行全表扫描。
SELECT*
FROMTRFKZL
LEFTJOINTRFKSQONTRFKZL_SQDNM=TRFKSQ_NM
INNERJOINTRYWLXONTRFKZL_YWLX=TRYWLX_NM
INNERJOINZWJSFSONTRFKZL_JSFS=ZWJSFS_ID
INNERJOINLSWBZDONTRFKZL_HB=LSWBZD_ID
INNERJOINTRZHFKONTRFKZL_FKZH=FK.TRZH_NM
LEFTJOINTRZHSKONTRFKZL_SKZH=SK.TRZH_NM
LEFTJOINYSYSXMONTRFKZL_SZXM=YSYSXM_XMUID
LEFTJOINTRHBZLONTRFKZL_NM=TRHBZL_ZLNM
WHERE(TRFKZL_SQDNMIN('d1c01251-bc0a-4ecb-8ed0-742614245bdd')
ORTRHBZL_SQDNMIN('d1c01251-bc0a-4ecb-8ed0-742614245bdd')
)
ANDTRFKZL_YWLY=0
按照建议更改SQL写法,走索引查找,响应时间在1秒以内。当然,从原始语句的筛选条件也能够感觉到怪怪的,根本上来讲应该是个设计问题。
SELECT*
FROMTRFKZL
LEFTJOINTRFKSQONTRFKZL_SQDNM=TRFKSQ_NM
INNERJOINTRYWLXONTRFKZL_YWLX=TRYWLX_NM
INNERJOINZWJSFSONTRFKZL_JSFS=ZWJSFS_ID
INNERJOINLSWBZDONTRFKZL_HB=LSWBZD_ID
INNERJOINTRZHFKONTRFKZL_FKZH=FK.TRZH_NM
LEFTJOINTRZHSKONTRFKZL_SKZH=SK.TRZH_NM
LEFTJOINYSYSXMONTRFKZL_SZXM=YSYSXM_XMUID
LEFTJOINTRHBZLONTRFKZL_NM=TRHBZL_ZLNM
WHERETRFKZL_SQDNMIN('d1c01251-bc0a-4ecb-8ed0-742614245bdd')
ANDTRFKZL_YWLY=0
UNION
SELECT*
FROMTRFKZL
LEFTJOINTRFKSQONTRFKZL_SQDNM=TRFKSQ_NM
INNERJOINTRYWLXONTRFKZL_YWLX=TRYWLX_NM
INNERJOINZWJSFSONTRFKZL_JSFS=ZWJSFS_ID
INNERJOINLSWBZDONTRFKZL_HB=LSWBZD_ID
INNERJOINTRZHFKONTRFKZL_FKZH=FK.TRZH_NM
LEFTJOINTRZHSKONTRFKZL_SKZH=SK.TRZH_NM
LEFTJOINYSYSXMONTRFKZL_SZXM=YSYSXM_XMUID
LEFTJOINTRHBZLONTRFKZL_NM=TRHBZL_ZLNM
WHERETRHBZL_SQDNMIN('d1c01251-bc0a-4ecb-8ed0-742614245bdd')
ANDTRFKZL_YWLY=0
分类:
相关文章推荐
- 使用索引的注意事项及常见场景、案例
- 使用索引的注意事项及常见场景、案例
- 使用索引的注意事项及常见场景、案例
- 使用索引的注意事项及常见场景、案例
- 数据库--使用索引的注意事项及常见场景
- 使用组合索引注意事项
- 常见对象_二分查找使用的注意事项
- MySQL索引类型总结和使用技巧以及注意事项
- winpcap使用注意事项及常见问题
- Mysql建立索引的时机,使用索引的注意事项和不足之处
- MySQL索引类型总结和使用技巧以及注意事项
- MySQL索引缺点&使用详细注意事项
- MySQL索引类型总结和使用技巧以及注意事项
- mysql 索引优化、使用原则及注意事项
- 【转】mysql索引使用技巧及注意事项
- Coolpy使用注意事项以及常见问题解决办法(持续更新中)
- Oracle使用强制索引的方法与注意事项
- 索引使用的注意事项
- MySQL索引类型总结和使用技巧以及注意事项
- MySQL索引类型总结和使用技巧以及注意事项