【问题标题】:When mysql updates a non-indexed column,why can use the primary keymysql更新非索引列时,为什么可以使用主键
【发布时间】:2021-10-10 07:31:59
【问题描述】:

MySQL 版本为 8.0.23

创建表并插入数据

CREATE TABLE `test_update` (
  `id`      int         NOT NULL ,
  `column1` varchar(20) DEFAULT NULL,
  `column2` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB;

insert into test_update values (1, 'a', 'test1');
insert into test_update values (2, 'b', 'test2');
insert into test_update values (3, 'c', 'test3');
insert into test_update values (4, 'd', 'test4');
insert into test_update values (5, 'e', 'test5');
insert into test_update values (6, 'f', 'test6');
insert into test_update values (7, 'g', 'test7');
insert into test_update values (8, 'h', 'test8');
insert into test_update values (9, 'i', 'test9');
insert into test_update values (10,'j', 'test10');

当我通过使用column2选择column1时,说明show mysql使用扫描全表,我认为这是合理的

mysql> explain select column1 from test_update where column2='test8';
+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table       | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | test_update | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   10 |    10.00 | Using where |
+----+-------------+-------------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

但是当我更新column1时,说明show mysql使用主键,它是一个非索引列,这是为什么呢?

mysql> explain update test_update set column1='z' where column2='test8';
+----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table       | partitions | type  | possible_keys | key     | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
|  1 | UPDATE      | test_update | NULL       | index | NULL          | PRIMARY | 4       | NULL |   10 |   100.00 | Using where |
+----+-------------+-------------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)

【问题讨论】:

  • 主索引是聚簇一,扫描它和扫描表是一样的。它用于 UPDATE 是因为服务器必须首先收集要更新的行的 PK 值,然后更新它们。
  • @Hedwig 。 . .行数太少,以至于 MySQL 可能会认为索引对select 没有用处。由于锁定和日志记录,updates 的索引使用不同。您可能想在有数千行而不是 10 行的表上尝试此操作。
  • @GordonLinoff,这不是我阅读问题的方式。两种情况下的 where 子句都不包含带索引的列 - 为什么“column2='test8'”会在主键上使用索引?
  • @Akina 那么,mysql 还是会扫描全表进行更新?
  • @GordonLinoff ,我在表中插入了 40,000 行,解释还是一样

标签: mysql sql indexing sql-execution-plan


【解决方案1】:

Select 的最佳索引是

INDEX(column2,   -- to handle the WHERE test
      column1)   -- for "covering"

这对更新也很有用,尽管出于不同的原因。

有了INDEXselect column1 from t where column2='test8' 将触及索引 BTree 中的 2 行(在您的示例数据中),而数据的 BTree 中没有行

  • column2='test8' 的一行。 (这里可能有零行或多行。)
  • 再多一行,发现没有更多带有column2='test8' 的行。

而且因为SELECT 中唯一的其他列是索引的一部分,所以索引就足够了(“Covering” == “Using index”)。

Update 也是一样,会读取 1+1 行,更新 1 行。

在 Explain 中,有时“Type = INDEX”会产生误导。请注意,“Key”是“PRIMARY”。在这种情况下,它实际上是在进行表扫描。进一步的线索是“Rows”是“10”,这是整个表中行数的近似值。

更多讨论:http://mysql.rjweb.org/doc.php/index_cookbook_mysql

至于使用 PK 更新,我会说:“由于没有有用的索引,更新必须扫描表以查找与 WHERE 匹配的所有行。对于每个这样的行,它将执行更新。

【讨论】:

  • 是不是说mysql还在扫描全表?
  • @Hedwig - 不。我更新了我的答案以详细说明。
  • 谢谢你的回答,我知道覆盖索引是一个更好的选择,我只是好奇为什么在这个例子的更新中使用了主键
  • @Hedwig - 对于UPDATE,“主要”和“索引”具有误导性,但在技术上是“正确的”。更新必须扫描整个表,该表由“PRIMARY”键“索引”。 (我同意这令人困惑。)
猜你喜欢
  • 1970-01-01
  • 2019-02-08
  • 1970-01-01
  • 2013-06-21
  • 2018-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-16
相关资源
最近更新 更多