[英]Why the primary key is not the clustered index if another non clustered index is added in MariaDB
您好,我有一個由以下查詢創建的表 MariaDB 版本 10.5.9
CREATE TABLE `test` (
`id` int unsigned NOT NULL AUTO_INCREMENT,
`status` varchar(60) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `test_status_IDX` (`status`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4
我一直認為主鍵默認是聚簇索引,它也定義了表中行的順序,但這里似乎狀態上的索引被選為聚簇。 為什么會發生這種情況,我該如何改變它?
MariaDB [test]> select * from test;
+----+--------+
| id | status |
+----+--------+
| 2 | cfrc |
| 5 | hjr |
| 1 | or |
| 3 | test |
| 6 | verve |
| 4 | yes |
+----+--------+
6 rows in set (0.001 sec)
假設 SELECT 的結果將按跨 dB 引擎的任何列排序是不安全的。 如果您希望進行排序,則應始終使用 ORDER BY col [ASC|DESC]。 我看到記錄按添加順序顯示,但在刪除/插入等之后可能會發生變化,不應依賴。 有關更多詳細信息,請參見此處。
(我將在我的回答中引用 MySQL 文檔,但在這個問題的上下文中,該信息也適用於 MariaDB。)
首先,讓我們談談索引擴展。 每當您定義二級索引(即任何非聚簇索引的索引)時,InnoDB 引擎都會在后台自動創建一個附加(復合)索引。 這稱為索引擴展。 這個額外的索引包含您在原始二級索引中定義的列(以相同的順序)以及在它們之后添加的主鍵列。 因此,在您的示例中,InnoDB 為test_status_IDX
創建了一個索引擴展(我們稱之為 X),其中包含列(stauts, id)
。
現在讓我們看看select * from test;
. 這里沒有WHERE
子句,所以優化器需要做的就是獲取表中所有行的所有列來滿足這個查詢。 這歸結為獲取status
和id
,因為表中沒有其他列。 這些確切的字段恰好存儲在擴展索引 X 中。這使得索引 X 成為該查詢的覆蓋索引。 覆蓋索引是一種索引,在給定查詢的情況下,它可以完全生成查詢結果而無需讀取任何實際數據行。 因此,優化器從索引 X 讀取並返回查詢結果所需的值,按照它們出現在那里的順序,即按status
,因此是您觀察到的順序。
為了進一步證明和擴展(雙關語意)這一點,讓我們重現示例(使用 MariaDB 10.4 測試):
1 . 首先創建表並添加行
CREATE TABLE foo (
id int(10) unsigned NOT NULL AUTO_INCREMENT,
status varchar(60) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB;
INSERT INTO foo VALUES
(1, 'or'),
(2, 'cfrc'),
(3, 'test'),
(4, 'yes'),
(5, 'hjr'),
(6, 'verve');
SELECT * FROM foo;
+----+--------+
| id | status |
+----+--------+
| 1 | or |
| 2 | cfrc |
| 3 | test |
| 4 | yes |
| 5 | hjr |
| 6 | verve |
+----+--------+`
2 . 現在讓我們添加二級索引並再次檢查順序
CREATE INDEX secondary_idx ON foo (status);
SELECT * FROM foo;
+----+--------+
| id | status |
+----+--------+
| 2 | cfrc |
| 5 | hjr |
| 1 | or |
| 3 | test |
| 6 | verve |
| 4 | yes |
+----+--------+
如上所述,行按照它們在(擴展的) secondary_idx
中出現的順序返回
3 . 現在讓我們刪除索引並使用 2 個字節的前綴長度重新添加它。 這意味着索引不會存儲列的完整值,而只會存儲它的前兩個字節,這意味着擴展索引不再是覆蓋索引,因為它不能完全產生查詢結果。 因此將使用聚簇索引
ALTER TABLE foo DROP INDEX secondary_idx;
CREATE INDEX secondary_idx ON foo (status(2));
SELECT * FROM foo;
+----+--------+
| id | status |
+----+--------+
| 1 | or |
| 2 | cfrc |
| 3 | test |
| 4 | yes |
| 5 | hjr |
| 6 | verve |
+----+--------+
4 . 讓我們以另一種方式展示這種行為。 在這里,我們將保留原始二級索引(沒有前綴長度),但我們將向表中添加第 3 列。 這將再次使二級索引成為非覆蓋索引(因為它不包含第 3 列),因此,聚集索引也將在這里使用。
ALTER TABLE foo DROP INDEX secondary_idx;
CREATE INDEX secondary_idx ON foo (status);
ALTER TABLE foo ADD bar integer NOT NULL;
SELECT * FROM foo;
+----+--------+-----+
| id | status | bar |
+----+--------+-----+
| 1 | or | 0 |
| 2 | cfrc | 0 |
| 3 | test | 0 |
| 4 | yes | 0 |
| 5 | hjr | 0 |
| 6 | verve | 0 |
+----+--------+-----+
將bar
添加到索引(或將其從表中刪除)將再次使查詢使用二級索引。
ALTER TABLE foo DROP INDEX secondary_idx;
CREATE INDEX secondary_idx ON foo (status, bar);
SELECT * FROM foo;
+----+--------+-----+
| id | status | bar |
+----+--------+-----+
| 2 | cfrc | 0 |
| 5 | hjr | 0 |
| 1 | or | 0 |
| 3 | test | 0 |
| 6 | verve | 0 |
| 4 | yes | 0 |
+----+--------+-----+
您還可以對上面的所有SELECT
語句使用EXPLAIN
來查看每個階段使用了哪個索引。
@aprsa 是對的,我錯誤地假設結果將與聚集索引的順序相同,但在這種情況下(使用 INNODB)狀態索引用於查詢的評估,這就是為什么它似乎是按狀態“排序”的. 如果我 select 使用 id,則使用主索引,結果似乎按 id“排序”。 在另一個引擎中,這可能不是真的。
該特定表由 2 個 BTree 組成:
數據,按PRIMARY KEY
排序。 是的,它是聚集的,並按 1,2,3,... 排序
二級索引,按status
排序。 每個二級索引都包含一個PK的副本,以便它可以到達另一個BTree以獲得列的rest(不是還有更多。),即BTree相當於一個2列表PRIMARY KEY(status)
加上一個id
。
請注意 output 的status
順序。 我必須假設它決定按順序讀取二級索引以提供結果。
是的,如果您想要特定的排序,則必須指定ORDER BY
。 你不能假設我剛才討論的細節。 誰知道,明天可能會有其他事情發生,例如內存中的“哈希”,其中信息以其他方式加擾!
(This Answer applies to both MySQL and MariaDB. However, MariaDB is already playing a game with hashing that MySQL has not yet picked up. Be warned! Or simply add an ORDER BY
.)
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.