Mysql
錯誤的索引被偷看
我有一個索引可以過濾 99% 的表,即
ix_magic_composite
(對於那個查詢參數)。當我添加另一個or
過濾器時,它選擇了錯誤的索引,即fTS
即使我創建了一個以該欄位開頭的索引,它仍然選擇了錯誤的索引。執行時間是 20 秒對 3 秒到更好的索引。ix_magic_composite
index 為這兩個 SQL 返回(初始過濾器)大約 10 行中的數百萬行,同時fTS
返回數百萬行。有點不知所措。在我看來,統計數據並沒有為引擎提供所有這些列組合的正確圖片。
我簡化了表格,它有更多的列和索引。
帶有良好計劃的 SQL:
select * from tblExample where 1=1 and status = 'okay' and textCol > '' and insrBLN = 1 and (magic is NULL or magic = '') and (itemId is NULL or itemId = '') and fTS > '2020-01-01' and fTS > '2020-01-01' order by fTS limit 50 +----+-------------+------------+------------+-------------+--------------------------------------------------+---------------------+---------+-------+---------+----------+----------------------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+------------+------------+-------------+--------------------------------------------------+---------------------+---------+-------+---------+----------+----------------------------------------------------+ | 1 | SIMPLE | tblExample | NULL | ref_or_null | textCol,status,textCol_4,ix_magic_composite,fTS | ix_magic_composite | 53 | const | 5892974 | 0.24 | Using index condition; Using where; Using filesort | +----+-------------+------------+------------+-------------+--------------------------------------------------+---------------------+---------+-------+---------+----------+----------------------------------------------------+
帶有錯誤計劃的 SQL:
select * from tblExample where 1=1 and status = 'okay' and textCol > '' and insrBLN = 1 and (magic is NULL or magic = '' or magic = 'retry') and (itemId is NULL or itemId = '' or itemId = 'retry') and fTS > '2020-01-01' and fTS > '2020-01-01' order by fTS limit 50 +----+-------------+------------+------------+-------+-------------------------------------------------+---------+---------+------+---------+----------+------------------------------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+------------+------------+-------+-------------------------------------------------+---------+---------+------+---------+----------+------------------------------------+ | 1 | SIMPLE | tblExample | NULL | range | textCol,status,textCol_4,ix_magic_composite,fTS | fTS | 5 | NULL | 6271587 | 0.18 | Using index condition; Using where | +----+-------------+------------+------------+-------+----------------------------------------- ----+---------+---------+------+---------+----------+------------------------------------+
桌子:
CREATE TABLE `tblExample` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `fTS` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `status` varchar(50) NOT NULL DEFAULT 'new', `textCol` varchar(50) DEFAULT NULL, `insrBLN` tinyint(4) NOT NULL DEFAULT '0', `itemId` varchar(50) DEFAULT NULL , `magic` varchar(50) DEFAULT NULL , PRIMARY KEY (`id`), KEY `ix_magic_composite` (`itemId`,`magic`,`fTS`,`insrBLN`), KEY `fTS` (`fTS`) ) ENGINE=InnoDB AUTO_INCREMENT=14391289 DEFAULT CHARSET=latin1
編輯
我們重構了程式碼,所以查詢看起來像:
select * from tblExample where 1=1 and status = 'okay' and textCol > '' and insrBLN = 1 and (retry = '' or (retry='retry' and retryDT < now() - interval 1 day)) and fTS > '2020-01-01' order by fTS limit 50
該問題未排序(還嘗試了索引中的不同列順序)。看起來只有當我刪除訂單時它才會選擇正確的索引。
重構不起作用。我決定移到which 給出與or
UNION ALL
相同的結果。我選擇這種方法是因為如果索引被刪除或重命名,它不需要任何程式碼更改。use index``force index
添加 OR 子句使估計索引的過濾效果變得更加困難。一種解決方案是添加一個生成的 always 列,該列計算是否滿足 magic 和 itemId 的謂詞,並索引:
CREATE TABLE tblExample ( id int(11) unsigned NOT NULL AUTO_INCREMENT, fTS timestamp NULL DEFAULT CURRENT_TIMESTAMP, status varchar(50) NOT NULL DEFAULT 'new', textCol varchar(50) DEFAULT NULL, insrBLN tinyint(4) NOT NULL DEFAULT '0', itemId varchar(50) DEFAULT NULL , magic varchar(50) DEFAULT NULL , retry tinyint GENERATED ALWAYS AS ( case when (magic is NULL or magic = '' or magic = 'retry') AND (itemId is NULL or itemId = '' or itemId = 'retry') then 1 else 0 end ) STORED, PRIMARY KEY (`id`), KEY `ix_magic_composite` (retry,`fTS`,`insrBLN`), KEY `fTS` (`fTS`) ) ENGINE=InnoDB AUTO_INCREMENT=14391289 DEFAULT CHARSET=latin1
然後可以將查詢更改為:
SELECT t.* FROM tblExample t WHERE status = 'okay' and textCol > '' and insrBLN = 1 and retry and fTS > '2020-01-01' and fTS > '2020-01-01' -- can be removed I assume order by fTS limit 50;
正確的解決方案可能是修復數據模型,但這可能是不可能的。