mysql 如果在MariaDB中添加了另一个非聚集索引,为什么主键不是聚集索引

uxh89sit  于 2022-12-03  发布在  Mysql
关注(0)|答案(4)|浏览(148)

您好,我有一个由以下查询创建的表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)
bq3bfh9z

bq3bfh9z1#

假设SELECT的结果将按跨dB引擎的任何列排序是不安全的。|DESC]如果您希望进行排序。我看到记录是按照添加的顺序显示的,但在删除/插入等操作后可能会发生变化,因此不应依赖。有关详细信息,请参阅here

fdbelqdn

fdbelqdn2#

(我将在回答中引用MySQL文档,但在此问题的上下文中,这些信息也适用于MariaDB。)
第一点,InnoDB引擎会自动创建一个额外的(复合)索引在定义辅助索引时后台执行(即非聚集索引的任何索引)。这称为 * 索引扩展 *。此额外索引包含在原始辅助索引中定义的列(以相同的顺序),主键的列添加在它们之后。因此,在您的示例中,InnoDB为test_status_IDX(我们称之为X)创建了一个索引扩展,列为(stauts, id)
现在让我们看一下查询select * from test;,这里没有WHERE子句,因此优化器需要做的就是获取表中所有行的所有列,这归结为获取statusid,因为表中没有其他列,这些字段恰好存储在扩展索引X中。这会使索引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。让我们以另一种方式演示此行为。这里我们将保留原始的二级索引(没有前缀长度),但将向表中添加第三列。这将再次使二级索引成为非覆盖索引(因为它不包含第三列),因此,这里也将使用聚集索引。

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,以查看在每个阶段使用哪个索引。

6mw9ycah

6mw9ycah3#

@aprsa是正确的我错误地假设结果的顺序与聚集索引的顺序相同,但在本例中(使用INNODB),状态索引用于查询的评估,所以它看起来是按状态“排序”的。如果我选择ID,则使用主索引,结果看起来是按ID“排序”的。在另一个引擎中,这可能不是真的。

neskvpey

neskvpey4#

该特定表由2个B树组成:

  • PRIMARY KEY排序的数据。是的,它是聚类的,按1、2、3......排序。
  • 二级索引,按status排序。每个二级索引都包含一个PK副本,以便它可以访问另一个BTree以获取其余列(不是说还有更多列!)。也就是说,is BTree等效于一个包含PRIMARY KEY(status)id的2列表。

注意输出是如何按照status的顺序进行的,我必须假设它决定简单地读取二级索引 * 按照它的顺序 * 来提供结果。
是的,如果你想要一个特定的顺序,你 * 必须 * 指定一个ORDER BY。你 * 不能 * 假设我刚才讨论的细节。谁知道呢,明天可能会有其他的东西,比如一个内存中的“hash”,它以其他方式对信息进行了编码!
(This答案适用于MySQL和MariaDB。但是,MariaDB已经在玩一个MySQL还没有学会的哈希游戏了。请注意!或者简单地添加一个ORDER BY。)

相关问题