1. 程式人生 > >使用索引的註意事項及常見場景、案例

使用索引的註意事項及常見場景、案例

nio tid 自己 穩定 rac comm options jna xxx

索引的原理與作用,各種書籍和網絡上的介紹可以說是鋪天蓋地,基本上主流數據庫系統的也都是一致的。選擇索引字段的原則,比如外鍵字段、數據類型較小的字段、經常用於查詢或排序的字段、表關聯的字段等等,在此不做贅述。本人在工作中見到過很多人創建的索引,回想自己以前也會有理論知識空洞的體會,總感覺理論知識無法與具體的工作問題相匹配。在此僅以工作學習中積累的一點經驗和問題場景整理以饗讀者。先把常見的註意事項整理如下:

  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
技術分享

使用索引的註意事項及常見場景、案例