简单的更新查询需要很长时间才能在 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 之前,不存在索引不正确的风险。这是因为“更改缓冲区”。该构造保留挂起的索引更新,以便以后持久保存到磁盘。
我有一个 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 之前,不存在索引不正确的风险。这是因为“更改缓冲区”。该构造保留挂起的索引更新,以便以后持久保存到磁盘。