簡體   English   中英

“使用索引”與復合索引:A=, B=, C<=

[英]"Using index" with composite index: A=, B=, C<=

盡管查詢很簡單,但下面的執行計划似乎令人失望且次優。

我正在使用 MySQL 5.7。 這是小提琴(雖然它只提供 5.6)。

CREATE TABLE `event` (
  `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(63) CHARSET ASCII COLLATE ASCII_BIN NOT NULL,
  `is_sequenced` TINYINT(3) UNSIGNED NOT NULL,
  `sequence_number` BIGINT(20) UNSIGNED DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `Name-SequenceNumber` (`name`,`sequence_number`),
  KEY `Name-IsSequenced` (`name`,`is_sequenced`,`id`)
) ENGINE=INNODB
;

INSERT INTO `event`
(id, `name`, is_sequenced, sequence_number)
VALUES
(NULL, 'OrderCreated', 0, NULL),
(NULL, 'OrderCreated', 0, NULL),
(NULL, 'OrderCreated', 0, NULL),
(NULL, 'OrderCreated', 0, NULL),
(NULL, 'OrderCreated', 0, NULL),
(NULL, 'OrderCreated', 0, NULL),
(NULL, 'OrderCreated', 0, NULL),
(NULL, 'OrderCreated', 0, NULL),
(NULL, 'OrderCreated', 0, NULL)
;

我們將使用Name-IsSequenced二級索引。 讓我們試試下面的EXPLAIN (查詢在 Fiddle 中。打開“查看執行計划”以查看其EXPLAIN結果。)

EXPLAIN
SELECT * -- This part needs the PK
FROM `event` e
WHERE e.name = 'OrderCreated'
AND e.is_sequenced = 0
AND e.id <= 3
;

到現在為止還挺好。 Using index condition是有意義的:可以在預期的索引Name-IsSequenced上解析整個條件,然后需要 PK 來獲取SELECT *的剩余數據。

如果我們只選擇屬於二級索引的內容,我們應該能夠將其改進為Using index索引,對嗎? (請注意,PK 始終是任何二級索引的一部分,但我們甚至可以通過在二級索引的末尾包含id來確保這一點。結果應該是一樣的。)

EXPLAIN
SELECT id
FROM `event` e
WHERE e.name = 'OrderCreated'
AND e.is_sequenced = 0
AND e.id <= 3
;

現在,結果是Using where; Using index Using where; Using index 等等,那是……更糟?! 我們減少了它的工作量,計划表明它正在更加努力地工作。

Using index應該是可以實現的。 找到name=OrderCreated的范圍,然后在其中找到is_sequenced=0的子范圍,然后在其中找到id<=3的子范圍。

奇怪的是,我還有其他實驗(有更多數據),我可以通過將id<=3更改為id=3 (與FORCE INDEX結合以防止它偏向於 PK)來獲取Using index 我看不出有什么不同。 (如果我們用 Fiddle 試試這個,它會保持不變——也許是因為數據集很小。)

誰能解釋為什么執行計划沒有表明二級索引的預期有效使用? 有沒有辦法把它弄直?

WHERE e.name = 'OrderCreated'
  AND e.is_sequenced = 0
  AND e.id <= 3

規則很簡單:首先以任何順序執行=列。 然后你會在一個“范圍”上出現一個裂縫。

INDEX(name, is_sequenced, -- in either order
      id)                 -- last

不要聽信關於根據基數排序的老生常談。

使用SELECT id ,該索引包含所需的所有列,因此它是“覆蓋”的,如EXPLAIN's “使用索引”所示。

使用SELECT *索引缺少sequence_number 所以,它有兩種執行方式:

方案A:使用索引; 對於索引的 BTree 中每一行相關的行,進入數據的 BTree(通過id )以查找丟失的列。

方案 B:避開索引並簡單掃描數據,按PRIMARY KEY(id)排序。 但是你瞧, id < 3實際上是對 PK 的一個很好的使用。 EXPLAIN可能會說PRIMARYRange

優化器將在計划之間做出半智能選擇,通常會選擇更好的一個。

計划 C:計划 A 可以改進。 添加sequence_number (在末尾)以制作INDEX(name, is_sequenced, id, sequence_number) 現在你得到了“覆蓋”(“使用索引”)和最快的索引。

更多討論: http : //mysql.rjweb.org/doc.php/index_cookbook_mysql

大約 5.6 / 5.7 / 8.0,優化器做了很多改進。 它轉向了“基於成本的模型”,它使用索引統計等來計算每個可能的執行計划的成本估計。 該更改大張旗鼓地推出,但對查詢計划的凈影響很小。 如果WHERE子句在JOIN兩個不同表上具有范圍條件,則沒有模型做得很好的一個領域。 ORDER BY和/或LIMIT將額外的猴子扳手投入戰斗。

ANALYZE TABLE曾經對於“修復”統計數據很重要; 5.6 在這方面做出了根本性的改進。 盡管如此,“統計數據”並不完美。

id=3 -- 好吧,你是要求所有的列,而使用 PK 有所有的列,所以為什么還要考慮一些二級索引。 (PK 與數據“聚集在一起”。)即使存在同樣好的索引,數據也更有可能緩存在 RAM 中。 (成本模型 [尚未] 考慮緩存或 SSD 與 HDD。)

根據經驗法則(根據經驗確定),如果需要超過 20% 的二級指數,則將避免使用二級指數。 假設在輔助 BTree 和 Data BTree 之間來回跳動比簡單地掃描數據成本更高。 在您的表中,需要 30% 的索引。 QED。 (實際上,這是優化器有時會“出錯”的另一個灰色區域。)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM