客户问:有张表的索引做了调整后,为什么这个SQL要执行3个小时?
SELECT
NULL,
M.SENDDATE,
T.RELISN,
...
FROM
xxx M, xxx T
WHERE T.RELISN = M.ISN
AND M.SUBTYPE = '1'
AND M.ZONENO NOT IN (SELECT D.DICTCODE FROM T_CDRI_DICT D WHERE D.DICTTYPE = 'CMBZONENO')
AND M.SDSTATUS IN ('007','010')
AND M.INKFLAG = '1'
AND M.SENDDATE IN (SELECT DATE_ADD(CTL.FILEDATE, INTERVAL 1 DAY)
FROM T_FEEDBACK_WFFDCTL_ICBC CTL WHERE CTL.FILETYPE IN ('A', 'B', 'C', 'D', 'E', 'F'))
AND EXISTS(
SELECT CODE FROM T_CPRI_NTHPADIC
WHERE NAME='BOPTCFLAG' AND CODE='NOV_BOP'
AND (ZONENO='00000' OR ZONENO=M.ZONENO)
AND INFORMATION='1'
AND DATE_SUB(STR_TO_DATE(M.SENDDATE, '%Y-%m-%D'),INTERVAL 1 DAY) BETWEEN ACTDATE AND DEADLINE);
调整前:
PRIMARY KEY (`ISN`,`ZONENO`),
UNIQUE KEY `IDX_T_DTET_BOPMAIN_RPTNO` (`SUBTYPE`,`RPTNO`,`ZONENO`),
KEY `IDX_T_DTET_BOPMAIN_REFNO` (`ZONENO`,`SUBTYPE`,`REFNO`),
KEY `IDX_T_DTET_BOPMAIN_INKFLAG` (`INKFLAG`,`BOPCODE`) USING BTREE,
KEY `IDX_T_DTET_BOPMAIN_ZONENO` (`ZONENO`,`SUBTYPE`,`SETDATE`,`BRNO`,`SDSTATUS`,`PINBRNO`,`DPBRNO`,`UPDUSERID`),
KEY `IDX_T_DTET_BOPMAIN_SDSTATUS` (`SDSTATUS`,`SENDDATE`,`BOPCODE`) USING BTREE,
KEY `IDX_T_DTET_BOPMAIN_SETDATE` (`SETDATE`,`SUBTYPE`)
调整后:
PRIMARY KEY (`ISN`,`ZONENO`),
UNIQUE KEY `IDX_T_DTET_BOPMAIN_RPTNO` (`SUBTYPE`,`RPTNO`,`ZONENO`),
KEY `IDX_T_DTET_BOPMAIN_REFNO` (`ZONENO`,`SUBTYPE`,`REFNO`),
KEY `IDX_T_DTET_BOPMAIN_INKFLAG` (`INKFLAG`,`BOPCODE`) USING BTREE,
KEY `IDX_T_DTET_BOPMAIN_SETDATE` (`SETDATE`,`SUBTYPE`,`ZONENO`,`BRNO`,`SDSTATUS`,`PINBRNO`,`DPBRNO`,`UPDUSERID`),
KEY `IDX_T_DTET_BOPMAIN_UPDDATE` (`UPDDATE`,`SUBTYPE`,`ZONENO`),
KEY `IDX_T_DTET_BOPMAIN_SDSTATUS` (`BOPCODE`,`SDSTATUS`,`SENDDATE`,`BATCHNO`)
执行计划:
+----+--------------------+-----------------+-------------------------------------------------------------------------------------------------------------------+------+-------------------------------------------------------------+-----------------------------+---------+-------------------+----------+----------+----------------------------------------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+--------------------+-----------------+-------------------------------------------------------------------------------------------------------------------+------+-------------------------------------------------------------+-----------------------------+---------+-------------------+----------+----------+----------------------------------------------------+
| 1 | PRIMARY | <subquery3> | NULL | ALL | NULL | NULL | NULL | NULL | NULL | 100.00 | NULL |
| 1 | PRIMARY | M | PT099,PT002,PT004,PT006,PT008,PT010,PT012,PT014,PT016,PT018,PT020,PT022,PT024,PT026,PT028,PT030,PT032,PT098,PT040 | ALL | PRIMARY,IDX_T_DTET_BOPMAIN_RPTNO,IDX_T_DTET_BOPMAIN_INKFLAG | NULL | NULL | NULL | 50908218 | 0.50 | Using where; Using join buffer (Block Nested Loop) |
| 1 | PRIMARY | T | PT099,PT002,PT004,PT006,PT008,PT010,PT012,PT014,PT016,PT018,PT020,PT022,PT024,PT026,PT028,PT030,PT032,PT098,PT040 | ref | IDX_T_DTET_BOP01_DTL_RELISN | IDX_T_DTET_BOP01_DTL_RELISN | 9 | ioma.M.ISN | 1 | 100.00 | Using index condition |
| 3 | MATERIALIZED | CTL | NULL | ALL | WFFDBAK | NULL | NULL | NULL | 15 | 40.00 | Using where |
| 4 | DEPENDENT SUBQUERY | T_CPRI_NTHPADIC | NULL | ref | IDX_T_CPRI_NTHPADIC_ZONENO,IDX_T_CPRI_NTHPADIC_NAME | IDX_T_CPRI_NTHPADIC_NAME | 529 | const,const,const | 356 | 4.44 | Using where; Using index |
| 2 | DEPENDENT SUBQUERY | D | NULL | ref | PRIMARY | PRIMARY | 92 | const | 3 | 100.00 | Using where; Using index |
+----+--------------------+-----------------+-------------------------------------------------------------------------------------------------------------------+------+-------------------------------------------------------------+-----------------------------+---------+-------------------+----------+----------+----------------------------------------------------+
以上就是客户提供的全部信息,“什么?你还想问调整前的执行计划?”,“抱歉,没有了”。
分析过程
首先别看这个 SQL 它又长又宽,实际上通过执行计划,我们只需要关注 M 表,只有它扫描行数超多,所以我们只需要分析 where 条件中与 M 表相关的条件即可,子查询可以忽略(因为这些子查询与 M 表无关):
AND M.SUBTYPE = '1'
AND M.ZONENO NOT IN (...)
AND M.SDSTATUS IN ('007','010')
AND M.INKFLAG = '1'
AND M.SENDDATE IN (...)
还好我们知道背景是修改了索引导致了慢,所以可以判断修改前应该可以走这个索引:
KEY `IDX_T_DTET_BOPMAIN_SDSTATUS` (`SDSTATUS`,`SENDDATE`,`BOPCODE`) USING BTREE
修改索引后,这个索引变成:
KEY `IDX_T_DTET_BOPMAIN_SDSTATUS` (`BOPCODE`,`SDSTATUS`,`SENDDATE`,`BATCHNO`)
根据最左前缀原则,where 条件中并没有 BOPCODE 字段,所以无法使用这个索引。而执行计划中优化器可能选择的索引:
PRIMARY,IDX_T_DTET_BOPMAIN_RPTNO,IDX_T_DTET_BOPMAIN_INKFLAG
从字段命名来看,选择度都非常低,优化器最终是不会选择这些索引。
总结
最左前缀原则,如果 where 条件中的字段不在组合索引的第一位也就是最左边,那么是无法使用这个索引的;
字段的选择度或者说基数,太小的话建索引也是没用的。
当然这些知识我估计作为DBA都清楚,但是灵活运用到实际的优化分析中可能就难了,注意过滤无效的信息,保持清晰的思路!