简单的更新查询需要很长时间才能在 MySQL 中执行(等待 innodb)

simple update query taking very long to execute in MySQL (wait innodb)

我有一个 table 有 54k 行,包含 10G 数据

我运行对此更新查询:

UPDATE my_table SET blog_object_version='19'

需要超过 1 小时 到 运行、
我怎样才能提高性能?

补充信息:

我运行正在关注亚马逊 rds,db.m5.4xlarge
这是我的例子:

这是我在 aws 性能洞察 中看到的:
wait/io/file/innodb/innodb_data_file

我的数据库上没有任何其他查询 运行ning:

mysql> show processlist;
+----+----------+---------------------+----------+---------+------+----------+----------------------------------------------+
| Id | User     | Host                | db       | Command | Time | State    | Info                                         |
+----+----------+---------------------+----------+---------+------+----------+----------------------------------------------+
|  3 | rdsadmin | localhost:65182     | NULL     | Sleep   |    0 |          | NULL                                         |
|  4 | rdsadmin | localhost           | NULL     | Sleep   |    1 |          | NULL                                         |
|  6 | admin    | 123.45.67.890:6170  | my_table | Query   | 3901 | updating | UPDATE my_table SET blog_object_version='19' |
| 12 | admin    | 123.45.67.890:6360  | NULL     | Sleep   | 2981 |          | NULL                                         |
| 18 | admin    | 123.45.67.890:7001  | NULL     | Query   |    0 | starting | show processlist                             |
+----+----------+---------------------+----------+---------+------+----------+----------------------------------------------+

这是我的 table:

mysql> show create table my_table\G;
*************************** 1. row ***************************
       Table: my_table
Create Table: CREATE TABLE `my_table` (
  `index` int(11) NOT NULL AUTO_INCREMENT,
  `id` varchar(100) DEFAULT NULL,
  `user_id` varchar(50) NOT NULL,
  `associate_object_id` varchar(50) NOT NULL,
  `type` varchar(50) DEFAULT NULL,
  `creation_date` datetime DEFAULT NULL,
  `version_id` varchar(50) NOT NULL,
  `blog_object` longtext,
  `blog_object_version` varchar(100) DEFAULT NULL,
  `last_update` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`index`),
  UNIQUE KEY `id_user_id_version_id` (`id`,`user_id`,`version_id`) USING BTREE,
  KEY `user_id_associate_object_id` (`user_id`,`associate_object_id`),
  KEY `user_id_associate_object_id_version_id` (`user_id`,`associate_object_id`,`version_id`)
) ENGINE=InnoDB AUTO_INCREMENT=54563151 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)

这些是我的索引:

mysql> SHOW INDEX FROM my_table;
+----------+------------+----------------------------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table    | Non_unique | Key_name                               | Seq_in_index | Column_name         | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+----------+------------+----------------------------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| my_table |          0 | PRIMARY                                |            1 | index               | A         |       43915 |     NULL | NULL   |      | BTREE      |         |               |
| my_table |          0 | id_user_id_version_id                  |            1 | id                  | A         |        3659 |     NULL | NULL   | YES  | BTREE      |         |               |
| my_table |          0 | id_user_id_version_id                  |            2 | user_id             | A         |        8783 |     NULL | NULL   |      | BTREE      |         |               |
| my_table |          0 | id_user_id_version_id                  |            3 | version_id          | A         |       43915 |     NULL | NULL   |      | BTREE      |         |               |
| my_table |          1 | user_id_associate_object_id            |            1 | user_id             | A         |         378 |     NULL | NULL   |      | BTREE      |         |               |
| my_table |          1 | user_id_associate_object_id            |            2 | associate_object_id | A         |        4391 |     NULL | NULL   |      | BTREE      |         |               |
| my_table |          1 | user_id_associate_object_id_version_id |            1 | user_id             | A         |         385 |     NULL | NULL   |      | BTREE      |         |               |
| my_table |          1 | user_id_associate_object_id_version_id |            2 | associate_object_id | A         |        6273 |     NULL | NULL   |      | BTREE      |         |               |
| my_table |          1 | user_id_associate_object_id_version_id |            3 | version_id          | A         |       43915 |     NULL | NULL   |      | BTREE      |         |               |
+----------+------------+----------------------------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+

用这个语句:

UPDATE my_table SET blog_object_version='19'

需要获取、检查和更新所有记录。因为没有WHERE子句。

如果只需要更新一些记录(因为其他记录已经 blog_object_version='19' 那么您可能会看到(小的)改进:

UPDATE my_table SET blog_object_version='19' WHERE blog_object_version != '19'

因为他的语句只更新需要更改的记录,但仍然需要获取所有记录。

如果不是所有记录都有不等于“19”的 blog_object_version,那么在此字段上添加索引可能会有所改善,因为这样只有那些记录需要使用 blob_object_version 不等于“19”。

如果所有记录都需要更新,那么这不会有任何改善...

非常基本的问题,有一个非常基本的解决方案:

INDEX(blog_object_version)

为什么?如果没有这个索引,UPDATE 必须读取 54K(或 54M?)行中的每个 行以检查 '19'.

有了那个索引,只需要读取相关的行。

提示:

许多 VARCHAR 列听起来应该是 INT(或更小的东西,例如 SMALLINT)? (更改类型不太可能加快查询速度。)

user_id_associate_object_id;索引 user_id_associate_object_id_version_id 处理相同的事情。

更新所有行

最多更新 1K 行是合理的。更新不到 20% 的 table 将 可能 使用索引,如果它是 suitable.

但是...如果您需要更新所有 54K 行,则有几个问题。

这将花费很长时间,而且可能需要很多磁盘空间 space,因为在更新完成之前,新旧副本都会保留。 (这样它就可以原子地提交或回滚整个更新。)

通常,需要更新整个 table 的所有行中的列是“糟糕的设计”。有时,可以将列放在一行中的另一个 table 中。然后是更新blog_object_version的单行查询。但这意味着在 SELECT 中需要时执行 JOIN。 (这可能不是问题。)如果您不更改所有行,那么它会更混乱。

所以,...如果您决定更新“很多”(或全部)大 table,我建议以每块 100-1000 行的形式进行更新。更多详情:http://mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks

更改缓冲区

另一个问题(不太重要)是更新非唯一索引列时,索引需要更新。这需要修改代表 INDEX 的 BTree。对于非唯一索引,这是在后台完成的,主要是在提交查询之后。

在完成更新 BTree 之前,不存在索引不正确的风险。这是因为“更改缓冲区”。该构造保留挂起的索引更新,以便以后持久保存到磁盘。