索引的原理与作用,各种书籍和网络上的介绍可以说是铺天盖地,基本上主流数据库系统的也都是一致的。选择索引字段的原则,比如外键字段、数据类型较小的字段、经常用于查询或排序的字段、表关联的字段等等,在此不做赘述。本人在工作中见到过很多人创建的索引,回想自己以前也会有理论知识空洞的体会,总感觉理论知识无法与具体的工作问题相匹配。在此仅以工作学习中积累的一点经验和问题场景整理以飨读者。先把常见的注意事项整理如下:

  1. 索引应该建在选择性高的字段上(键值唯一的记录数/总记录条数),选择性越高索引的效果越好、价值越大,唯一索引的选择性最高;
  2. 组合索引中字段的顺序,选择性越高的字段排在最前面;
  3. where条件中包含两个选择性高的字段时,可以考虑分别创建索引,引擎会同时使用两个索引(在OR条件下,应该说必须分开建索引);
  4. 不要重复创建彼此有包含关系的索引,如index1(a,b,c) 、index2(a,b)、index3(a);
  5. 组合索引的字段不要过多,如果超过4个字段,一般需要考虑拆分成多个单列索引或更为简单的组合索引;

最后需要提醒的是,不要滥用索引。因为过多的索引不仅仅会增加物理存储的开销,对于插入、删除、更新操作也会增加处理上的开销,而且会增加优化器在选择索引时的计算代价。

因此太多的索引与不充分、不正确的索引对性能都是毫无益处的。一言以蔽之,索引的建立必须慎重,对每个索引的必要性都应该经过仔细分析,要有建立的依据。

举下面一个场景的例子,创建这样的索引是有效的吗?

select  *
from t1, t2
where t1.col_1 = t2.ab and t1.col_2 in (12, 38); -- 创建索引如下
create index idx_t1_query on t1(col_1, col_2);
 
-- 或者仅创建索引如下
create index idx_t1_col2 on t1(col_2);
 

再比如,该表最常使用的SQL场景有以下两种类型,应该如何创建索引?

select  *
from t1
where t1.PartId = 'xxxx' and t1.STATE = 2 and t1.PROCID = 'yyyy' select *
from t1
where (t.PartId = 'xxxx' or t1.ActualPartId = 'xxxx' ) and t1.STATE = 2 and t1.PROCID = 'yyyy' -- 创建一个“全覆盖的索引”,把查询条件都包含的索引
create index idx_t1_query on t1(partId, actualpartId, state, procid); -- 还是分开创建如下两个索引
create index idx_t1_PartId on t1(partId, state, procid)
create index idx_t1_actualPartId on t1(actualpartId, state, procid)
 

以执行计划和逻辑IO的统计数据显示,两个场景的测试结果都是后者索引有明显的效果,大家有兴趣可以自己测试验证一下。当然,生产环境远比这些要复杂,各表的数据量及数据分布情况也会影响引擎的执行方式,引擎对索引选择与要求也会不一样,此处仅以简单语句做示例进行说明。

组合索引查询的各种场景:

组合索引 Index (A, B, C)

  • 下面条件可以用上该组合索引查询:

    • A>5
    • A=5 AND B>6
    • A=5 AND B=6 AND C=7
    • A=5 AND B=6 AND C IN (2, 3)
  • 下面条件将不能用上组合索引查询:
    • B>5                                           ——查询条件不包含组合索引首列字段
    • B=6 AND C=7                            ——理由同上
  • 下面条件将能用上部分组合索引查询:
    • A>5 AND B=2                            ——当范围查询使用第一列,查询条件仅仅能使用第一列
    • A=5 AND B>6 AND C=2             ——范围查询使用第二列,查询条件仅仅能使用前二列
    • A=5 AND B IN (2, 3) AND C=2   ——理由同上

组合索引排序的各种场景:

组合索引 Index(A, B)

  • 下面条件可以用上组合索引排序:

    • ORDER BY A                   ——首列排序
    • A=5 ORDER BY B            ——第一列过滤后第二列排序
    • ORDER BY A DESC, B DESC      ——注意,此时两列以相同顺序排序
    • A>5 ORDER BY A            ——数据检索和排序都在第一列
  • 下面条件不能用上组合索引排序:
    • ORDER BY B                   ——排序在索引的第二列
    • A>5 ORDER BY B            ——范围查询在第一列,排序在第二列
    • A IN(1,2) ORDER BY B    ——理由同上
    • ORDER BY A ASC, B DESC       ——注意,此时两列以不同顺序排序

索引合并的简单说明:

  • 数据库能同时使用多个索引

    • SELECT * FROM TB WHERE A=5 AND B=6

      • 能分别使用索引(A) 和 (B);
      • 对于这个语句来说,创建组合索引(A,B) 更好;
      • 最终是采用组合索引,还是两个单列索引?主要取决于应用系统中是否存在这类语句:SELECT * FROM TB WHERE B=6
    • SELECT * FROM TB WHERE A=5 OR B=6
      • 组合索引(A, B)不能用于此查询(目前的数据库也很智能,部分OR条件也能够使用组合索引,但效果不是很稳定);
      • 很明显,分别创建索引(A) 和 (B)会更好;
  • 删除无效的冗余索引

    • TB表有两个索引(A, B) 和 (A),对应两种SQL语句:SELECT * FROM TB WHERE A=5 AND B=6 和 SELECT * FROM TB WHERE A=5

      • 执行时,并不是WHERE A=5 就用 (A); WHERE A=5 AND B=6  就用 (A, B);
      • 其查询优化器会使用其中一个以前常用索引,要么都用(A, B), 要么都用 (A)。
      • 所以应该删除索引(A),它已经被(A, B)包含了,没有任何存在的必要。

附1,查询指定表的数据量与索引定义情况:

--Sqlserver:
sp_helpindex 'tableName';
sp_spaceused 'tableName';
dbcc ShowContig('tableName') with all_indexes; select t.name, t.indid, t.rowcnt, t.*
from sysindexes t
where t.id = OBJECT_ID('tkk0107') and t.indid in (0, 1); -- t.status != 8388672 select t2.name tabName, t3.name indName, t4.name colName, t1.*
from sys.index_columns t1
join sys.tables t2 on t1.object_id = t2.object_id
join sys.indexes t3 on t2.object_id = t3.object_id and t1.index_id = t3.index_id
join sys.columns t4 on t2.object_id = t4.object_id and t1.column_id = t4.column_id
where t2.name = 'tableName'
order by t3.name, t1.index_column_id --Oracle:
select t.NUM_ROWS, t.BLOCKS, t.Logging, t.TEMPORARY, t.last_analyzed, t.*
from user_tables t
where t.TABLE_NAME = upper('tkk0107'); select t.index_name, t.distinct_keys, t.num_rows, t.sample_size, t.last_analyzed
, t.blevel, t.leaf_blocks, t.status, t.*
from user_indexes t
where t.table_name = upper('tkk0107')
order by t.index_name; select t.*
from user_ind_columns t
where t.TABLE_NAME = upper('tkk0107')
order by t.INDEX_NAME, t.COLUMN_POSITION;
 

附2,借助性能视图,查询数据表的SQL访问方式

--Oracle,根据共享池中的数据,统计指定表的访问SQL
with sh as
(
select max(t.sql_id) sql_id, substring(t.SQL_TEXT, 0, 100) sql_text, count(1) usecounts --sum(executions)
from v$sql t
where t.SQl_text like '%table_name%'
group by substring(t.SQL_TEXT, 0, 100)
) select sh.*, t.SQL_FULLTEXT
from sh join v$sql t on sh.sql_id = t.sql_id
order by sh.usecounts desc;
--sqlserver
with sh as (
select cp.cacheobjtype, cp.objtype, max(cp.plan_handle) plan_handle
, left(dt.text, 100) sql_text, sum(cp.usecounts) usecounts
from sys.dm_exec_cached_plans cp
cross apply sys.dm_exec_sql_text(cp.plan_handle) dt
where dt.text like '%workitem%'
group by cp.cacheobjtype, cp.objtype, left(dt.text, 100)
) select sh.*, dt.text as sql_fulltext
from sh cross apply sys.dm_exec_sql_text(sh.plan_handle) dt
order by sh.usecounts desc;
-- Sqlserver Identifying  Unused Indexes
SELECT OBJECT_NAME(t.object_id) as objName
, s.name, s.indid, s.dpages*8/1024 mb, t.*
FROM sys.dm_db_index_usage_stats t
join sysindexes s on t.object_id = s.id and t.index_id = s.indid
where t.user_seeks = 0
--and t.user_scans = 0
--and t.user_lookups = 0
order by t.object_id, t.index_id -- 2005分区后的准确大小
SELECT *
FROM sys.dm_db_index_physical_stats(DB_ID(), OBJECT_ID ('LCGS609999.workitem'), 21, null, null)
-- Oracle 提供如下方式,对索引进行有效性分析,经过分析的索引信息存储在index_stats数据字典
analyze index IDX_WORKITEM_PARTICIPANT validate structure; -- 当删除率大于15%时,考虑索引重建
select t.name, t.del_lf_rows, t.lf_rows - t.del_lf_rows as lf_rows_used
, to_char((t.del_lf_rows/t.lf_rows) * 100, '999.999') as ratio, t.*
from index_stats t
where t.name = upper('index_name'); -- 监视索引的使用情况,但此种方法仅能知道该索引有没有被使用,不知道使用的频率
alter index IDX_WORKITEM_PARTICIPANT monitoring usage;
alter index IDX_WORKITEM_PARTICIPANT nomonitoring usage; select * from v$object_usage t where t.table_name = upper(''); -- 获得索引使用频率的脚本
WITH Q AS (
SELECT
S.OWNER A_OWNER,
TABLE_NAME A_TABLE_NAME,
INDEX_NAME A_INDEX_NAME,
INDEX_TYPE A_INDEX_TYPE,
SUM(S.BYTES)/1024/1024 A_MB
FROM DBA_SEGMENTS S
JOIN DBA_INDEXES I ON I.INDEX_NAME = S.SEGMENT_NAME
WHERE S.OWNER = USER AND I.OWNER = USER
GROUP BY S.OWNER, TABLE_NAME, INDEX_NAME, INDEX_TYPE
HAVING SUM(S.BYTES) > (1024 * 1024 * 100) --超过100M的索引
)
SELECT /*+ NO_QUERY_TRANSFORMATION(S) */
A_OWNER OWNER,
A_TABLE_NAME TABLE_NAME,
A_INDEX_NAME INDEX_NAME,
A_INDEX_TYPE INDEX_TYPE,
A_MB MB,
DECODE (OPTIONS, null, ' -', OPTIONS) INDEX_OPERATION,
COUNT(OPERATION) NR_EXEC
FROM Q
LEFT JOIN DBA_HIST_SQL_PLAN D ON Q.A_OWNER = D.OBJECT_OWNER AND Q.A_INDEX_NAME = D.OBJECT_NAME
WHERE ROWNUM < 10
GROUP BY A_OWNER, A_TABLE_NAME, A_INDEX_NAME, A_INDEX_TYPE
, A_MB, DECODE (OPTIONS, null, ' -', OPTIONS)
ORDER BY A_OWNER, A_TABLE_NAME, A_INDEX_NAME, A_INDEX_TYPE
, A_MB DESC, NR_EXEC DESC;

附3,索引重建示例

--查出系统中数据量较大的表,重建索引、收集更新统计信息
--a) Sqlserver:
select OBJECT_NAME(t.id) AS tableName, t.rows, t.*
from sys.sysindexes t
where t.indid in (0, 1)
order by t.rows desc; -- 查看统计信息
sp_helpstats 'tkk0107'
dbcc show_statistics('tkk0107', 'columnName'); -- 重建索引、更新统计信息
ALTER INDEX ALL|indexName ON tableName REBUILD WITH(ONLINE=ON, MAXDOP=16);
UPDATE STATISTICS tableName; --b) Oracle:
select t.table_name, t.num_rows, t.*
from user_tables t
where t.num_rows > 0
order by t.num_rows desc; -- 查看统计信息
SELECT t.*
FROM dba_tab_col_statistics t
WHERE t.table_name = upper('tkk0107') and t.owner=user; -- 重建索引、更新统计信息
ALTER INDEX indexName REBUILD ONLINE NOLOGGING PARALLEL 4;
ANALYZE TABLE tableName COMPUTE STATISTICS;
ANALYZE INDEX indexName COMPUTE STATISTICS; --全库重建索引的方法:
--Sqlserver:
exec sp_msforeachtable 'DBCC DBREINDEX(''?'')' --Oracle:
DECLARE CURSOR myCur IS
select INDEX_NAME from user_indexes
WHERE TABLE_NAME='GSPAURESULT' AND INDEX_TYPE='NORMAL';
v_cname myCur% rowtype;
vsSql varchar2(256);
begin
open myCur;
loop
fetch myCur into v_cname;
exit when myCur% notfound;
vsSql:='ALTER INDEX ' || v_cname.INDEX_NAME || ' REBUILD ONLINE NOLOGGING PARALLEL 4';
EXECUTE IMMEDIATE vsSql;
end loop;
close myCur;
end;

补充:

Where条件中Or的两组条件如果分别落在两个数据表上,即使对应的字段都已创建索引,引擎也是无法使用索引的。如下SQL,此语句实际上仅返回一条数据,但对于TRFKZL和TRHBZL来说,Oracle、SqlServer都是进行全表扫描。

SELECT *
FROM TRFKZL
LEFT JOIN TRFKSQ ON TRFKZL_SQDNM = TRFKSQ_NM
INNER JOIN TRYWLX ON TRFKZL_YWLX = TRYWLX_NM
INNER JOIN ZWJSFS ON TRFKZL_JSFS = ZWJSFS_ID
INNER JOIN LSWBZD ON TRFKZL_HB = LSWBZD_ID
INNER JOIN TRZH FK ON TRFKZL_FKZH = FK.TRZH_NM
LEFT JOIN TRZH SK ON TRFKZL_SKZH = SK.TRZH_NM
LEFT JOIN YSYSXM ON TRFKZL_SZXM = YSYSXM_XMUID
LEFT JOIN TRHBZL ON TRFKZL_NM = TRHBZL_ZLNM
WHERE ( TRFKZL_SQDNM IN ('d1c01251-bc0a-4ecb-8ed0-742614245bdd')
OR TRHBZL_SQDNM IN ('d1c01251-bc0a-4ecb-8ed0-742614245bdd')
)
AND TRFKZL_YWLY = 0

按照建议更改SQL写法,走索引查找,响应时间在1秒以内。当然,从原始语句的筛选条件也能够感觉到怪怪的,根本上来讲应该是个设计问题。

SELECT *
FROM TRFKZL
LEFT JOIN TRFKSQ ON TRFKZL_SQDNM = TRFKSQ_NM
INNER JOIN TRYWLX ON TRFKZL_YWLX = TRYWLX_NM
INNER JOIN ZWJSFS ON TRFKZL_JSFS = ZWJSFS_ID
INNER JOIN LSWBZD ON TRFKZL_HB = LSWBZD_ID
INNER JOIN TRZH FK ON TRFKZL_FKZH = FK.TRZH_NM
LEFT JOIN TRZH SK ON TRFKZL_SKZH = SK.TRZH_NM
LEFT JOIN YSYSXM ON TRFKZL_SZXM = YSYSXM_XMUID
LEFT JOIN TRHBZL ON TRFKZL_NM = TRHBZL_ZLNM
WHERE TRFKZL_SQDNM IN ('d1c01251-bc0a-4ecb-8ed0-742614245bdd')
AND TRFKZL_YWLY = 0 UNION SELECT *
FROM TRFKZL
LEFT JOIN TRFKSQ ON TRFKZL_SQDNM = TRFKSQ_NM
INNER JOIN TRYWLX ON TRFKZL_YWLX = TRYWLX_NM
INNER JOIN ZWJSFS ON TRFKZL_JSFS = ZWJSFS_ID
INNER JOIN LSWBZD ON TRFKZL_HB = LSWBZD_ID
INNER JOIN TRZH FK ON TRFKZL_FKZH = FK.TRZH_NM
LEFT JOIN TRZH SK ON TRFKZL_SKZH = SK.TRZH_NM
LEFT JOIN YSYSXM ON TRFKZL_SZXM = YSYSXM_XMUID
LEFT JOIN TRHBZL ON TRFKZL_NM = TRHBZL_ZLNM
WHERE TRHBZL_SQDNM IN ('d1c01251-bc0a-4ecb-8ed0-742614245bdd')
AND TRFKZL_YWLY = 0
 

v使用索引的注意事项及常见场景、案例的更多相关文章

  1. mysql索引使用注意事项

    索引是快速搜索的关键.MySQL索引的建立对于MySQL的高效运行是很重要的.下面介绍几种常见的MySQL索引类型. 在数据库表中,对字段建立索引可以大大提高查询速度.假如我们创建了一个 mytabl ...

  2. solr与.net系列课程(八)solr中重跑索引的注意事项

    solr与.net系列课程(八)solr中重跑索引的注意事项 我们如果在项目中使用solr,那肯定就是把数据库中的数据跑进solr服务器中,solr有两种操作一种是新建索引,一种是增量索引,这里我们来 ...

  3. mysql使用索引的注意事项

    使用索引的注意事项 使用索引时,有以下一些技巧和注意事项: 1.索引不会包含有NULL值的列 只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索 ...

  4. MySQL索引失效的常见场景

    当然请记住,explain是一个好习惯! MySQL索引失效的常见场景 在验证下面的场景时,请准备足够多的数据量,因为数据量少时,MySQL的优化器有时会判定全表扫描无伤大雅,就不会命中索引了. 1. ...

  5. Httpd服务入门知识-Httpd服务常见配置案例之基于客户端来源地址实现访问控制

    Httpd服务入门知识-Httpd服务常见配置案例之基于客户端来源地址实现访问控制 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.Options  1>.OPTIONS指 ...

  6. Httpd服务入门知识-Httpd服务常见配置案例之虚拟主机

    Httpd服务入门知识-Httpd服务常见配置案例之虚拟主机 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.虚拟主机实现方案 1>.Apache httpd 有三种实现虚 ...

  7. Httpd服务入门知识-Httpd服务常见配置案例之Apache的工作做状态status页面

    Httpd服务入门知识-Httpd服务常见配置案例之Apache的工作做状态status页面 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.status功能概述 status页 ...

  8. Httpd服务入门知识-Httpd服务常见配置案例之ServerSignature指令选项

    Httpd服务入门知识-Httpd服务常见配置案例之ServerSignature指令选项 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.ServerSignature指令概述 ...

  9. Httpd服务入门知识-Httpd服务常见配置案例之实现用户家目录的http共享

    Httpd服务入门知识-Httpd服务常见配置案例之实现用户家目录的http共享 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.实现用户家目录的http共享前提 在配置家目录共 ...

随机推荐

  1. bzoj4594: [Shoi2015]零件组装机

    论静态查错的重要性...乱搞题真难调 首先这题看起来就是要分治检验了. 考虑对于区间[l,r],分成[l,p-1]和[p,r]使得这两个区间合并可以得到[l,r],并且要保证后面一个区间较大 设前一个 ...

  2. HDU4027 Can you answer these queries? —— 线段树 区间修改

    题目链接:https://vjudge.net/problem/HDU-4027 A lot of battleships of evil are arranged in a line before ...

  3. YTU 2922: Shape系列-8

    2922: Shape系列-8 时间限制: 1 Sec  内存限制: 128 MB 提交: 172  解决: 99 题目描述 小聪又想借用小强的Shape类了,但是不巧的是小强去考英语四级去了,但是小 ...

  4. java修改linux文件

    package vedio.test; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.Fil ...

  5. SQLite数据库框架--FMDB简单介绍

    1.什么是FMDB FMDB是iOS平台的SQLite数据库框架 FMDB以OC的方式封装了SQLite的C语言API 2.FMDB的优点 使用起来更加面向对象,省去了很多麻烦.冗余的C语言代码 对比 ...

  6. 并不对劲的bzoj3832: [Poi2014]Rally

    传送门-> 这题的原理看上去很神奇. 称拓扑图中入度为0的点为“起点”,出度为0的点为“终点”. 因为“起点”和“终点”可能有很多个,算起来会很麻烦,所以新建“超级起点”S,向所有点连边,“超级 ...

  7. 【SDOI 2011】染色

    [题目链接] 点击打开链接 [算法] 树链剖分 [代码] 本题,笔者求最近公共祖先并没有用树链剖分“往上跳”的方式,而是用倍增法.笔者认为这样比较好写,代码可读性 比较高 此外,笔者的线段树并没有用懒 ...

  8. Hibernate关联映射(多对一 --- many-to-one)

    转自:https://blog.csdn.net/fengxuezhiye/article/details/7369786?utm_source=blogxgwz9 Hibernate的关联映射关系有 ...

  9. java中jsp页面的css资源定位---备忘录

    1:同级目录下(应该是相对定位) web-inf下的内容是不对外开放的/安全的,不能通过url直接访问.已避免非法人员通过url自己操作一些比较私密的文件信息. 能在sevlet(或者spring的c ...

  10. element-ui公用模态框自定义样式与scoped样式生效问题解决方案

    //先插如效果图 里面内容均为传进来的.包括取消与确定按钮,因为每个页面的绑定事件不一样. //下面这个图片为初始样式 //拖动模态框指令需要插件.详情看我下一篇,以下是地址 https://www. ...