数据库--使用索引的注意事项及常见场景

一、

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

  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) / 1048576   A_MB  
                  FROM DBA_SEGMENTS S,  
                       DBA_INDEXES  I  
                 WHERE S.OWNER = USER
                   AND I.OWNER = USER
                   AND INDEX_NAME = SEGMENT_NAME  
                 GROUP BY S.OWNER, TABLE_NAME, INDEX_NAME, INDEX_TYPE  
                HAVING SUM(S.BYTES) > 1048576 * 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,  
               DBA_HIST_SQL_PLAN D  
         WHERE D.OBJECT_OWNER(+)= q.A_OWNER  
           AND D.OBJECT_NAME(+) = q.A_INDEX_NAME  
        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






二、

虽然使用索引的初衷是提高数据库性能,但有时也要避免使用它们。下面是使用索引的方针。

   1、索引不应该用于小规模的表。

   2、当字段用于WHERE子句作为过滤器会返回表里的大部分记录时,该字段就不适合设置索引。

   举例来说,图书里的索引不会包括像the或and这样的单词。

   3、经常会被批量更新的表可以具有索引,但批量操作的性能会由于索引而降低。

   对于经常会被加载或批量操作的表来说,可以在执行批量操作之前去除索引,在完成操作之后再重新创建索引。这是因为当表里插入数据时,索引也会被更新,从而增加了额外的开销。

   4、不应该对包含大量NULL值的字段设置索引。

   5、经常被操作的字段不应该设置索引,因为对索引的维护会变得很繁重。

 

   警告:对于特别长的关键字创建索引时要十分谨慎,因为大量I/O开销会不可避免地降低数据库性能。

   从下图可以看出,像性别这样的字段设置索引就没有什么好处。 

   举例来说,向数据库提交如下查询:

   select * from table_name    where gender = "FEMALE";

   何时应该避免使用索引-利用SQL索引改善性能之索引类型

   从上图可以看出,在运行上述这个查询时,表与索引之间有一个持续的行为。

 

   由于WHERE GENDER = 'FEMALE'(或'MALE')子句会返回大量记录,数据库服务程序必须持续地读取索引、然后读取表的内容、再读取索引、再读取表,如此反复。

 

   在这个范例里,由于表里的大部分数据肯定是要被读取的,所以使用全表扫描可能会效率更高。

   一般来说,当字段作为查询里的条件会返回表里的大部分数据时,我们不会对它设置索引。

   换句话说,不要对像性别这样只包含很少不同值的字段设置索引。这通常被称为字段的基数,或数据的惟一性。

   高基数意味着很高的惟一性,比如像身份号码这样的数据。低基数的惟一性不高,比如像性别这样的字段。

   提示:索引对于提高性能大有帮助,但在有些情况下也会降低性能。我们应该避免对只包含很少不同值的字段创建索引,比如性别、州名等。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 数据库索引设计与优化是数据库领域非常重要的一项工作。索引是对数据库表中一列或多列的值进行排序的结构,可以极大地提高数据的检索效率。 在进行索引设计,需要考虑数据库表的查询需求、表的大小、数量以及表结构的复杂性等因素。首先,需要确定哪些列需要创建索引,通常是频繁被查询的列。其次,需要选择合适的索引类型,如B树索引、哈希索引等。不同的索引类型有不同的适用场景和性能优劣。 在索引的优化过程中,可以通过以下方式提高索引的效率。首先,合理设置索引的列顺序,将最常用的列放在最前面。其次,使用覆盖索引,避免回表操作,提高查询效率。另外,定期对索引进行重建和优化,可以减少索引的碎片和提高查询性能。 此外,还可以通过使用分区表等方式来优化索引。分区表将大表按照某一列的值进行分区,可以减少索引的大小,提高查询效率。 总而言之,数据库索引设计与优化对于保证数据库的性能和可用性至关重要。通过合理的索引设计和优化,可以提高数据库查询效率,加快数据的检索速度,提升系统的响应能力。对于大型数据库来说,优化索引非常重要,可以有效地提升系统的整体性能。 ### 回答2: 数据库索引设计与优化是一本关于数据库索引的设计与优化的专题书籍。随着数据规模的不断增大,数据库的性能优化变得越来越重要。索引是提高查询速度和数据检索效率的重要手段。 该书从索引的基础知识入手,介绍了常见索引类型,包括B-Tree索引、哈希索引和全文索引等。并且详细介绍了索引的创建、使用和维护的方法。同,通过实际案例的分析和演示,详细说明了索引的优化技巧和策略,如选择合适的索引字段、使用联合索引和覆盖索引等。 在索引设计方面,该书提供了一些实用的指导原则。例如,在选择索引字段,需要考虑字段的频繁查询和过滤程度,选择最适合的字段作为索引。此外,还介绍了一些避免过多索引和重复索引注意事项,以避免索引对性能的负面影响。 在索引优化方面,该书提供了一些常见的优化建议。例如,可以通过适当增加缓存和调整数据库参数来提高索引的性能。此外,还介绍了如何使用索引查询计划和性能分析工具来定位和解决索引性能问题。 总的来说,数据库索引设计与优化是一本权威且实用的书籍,对于想要深入了解数据库索引原理和技术的人来说是一本不可多得的好书。无论是初学者还是有一定经验的数据库开发人员,都可以从中获得宝贵的知识和实践经验,提高数据库的查询速度和数据检索效率,优化数据库的性能。 ### 回答3: 数据库索引设计与优化是数据库系统中的重要话题,对于提高数据库查询性能有着至关重要的作用。 首先,数据库索引设计是根据业务需求和查询频率来确定哪些字段需要创建索引。一般来说,经常被搜索或者作为查询条件的字段应该优先考虑创建索引,例如主键、外键、频繁出现在WHERE子句中的字段等。索引还可以根据字段的区分度进行优化,选择合适的索引类型,例如B树索引、哈希索引、全文索引等。 其次,数据库索引的性能也需要进行优化。一方面,可以通过合理地设置索引的顺序来增加索引的效率。例如,可以将最常被搜索的字段放在联合索引的前面,或者将区分度高的字段放在B树索引的前面。另一方面,可以通过对索引的互补创建和删除来进行优化,避免冗余和过多的索引影响数据库查询性能。 此外,还可以通过一些技巧来进一步优化数据库索引。例如,可以使用覆盖索引来避免回表操作,提高查询效率。在查询语句中尽量避免使用不等于运算符和LIKE语句,因为这些操作会导致索引失效。另外,定期进行索引的重建和统计信息的更新也可以提高数据库查询性能。 总之,数据库索引设计和优化是提高数据库查询性能不可或缺的环节。通过合理地选择索引字段、设置索引顺序、优化索引类型以及使用一些技巧,可以有效地提高数据库的查询效率。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值