mysql - 在 MySQL 中执行简单的更新查询需要很长时间(等待 innodb)
问题描述
我有一个 54k 行的表,包含 10G 的数据
我正在对其运行此更新查询:
UPDATE my_table SET blog_object_version='19'
运行时间超过 1小时,
如何提高性能?
附加信息:
我在 AMAZON rds 上运行,db.m5.4xlarge
这是我的实例:
这是我在aws 性能洞察中看到的:
wait/io/file/innodb/innodb_data_file
我的数据库上没有运行任何其他查询:
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 |
+----+----------+---------------------+----------+---------+------+----------+----------------------------------------------+
这是我的桌子:
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 | | |
+----------+------------+----------------------------------------+--------------+---------------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
解决方案
非常基本的问题,有一个非常基本的解决方案:
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% 的表可能会使用索引。
但是...如果您需要更新所有 54K 行,则存在几个问题。
这将需要很长时间,并且可能需要大量磁盘空间,因为新旧副本都会保留到更新完成。(这样它就可以原子地提交或回滚整个更新。)
通常,需要更新整个表的所有行中的列是“糟糕的设计”。有时,可以将列放在另一个表中的一行中。然后是更新的单行查询blog_object_version
。但这意味着JOIN
当你需要它时在 a 中做 a SELECT
。(这可能不是问题。)如果您没有更改所有行,那么它会更混乱。
所以,...如果您决定更新“很多”(或全部)一个大表,我建议以每行 100-1000 行的块进行更新。更多细节:http: //mysql.rjweb.org/doc.php/deletebig#deleting_in_chunks
更改缓冲区
另一个问题(不太重要)是更新非唯一索引列时,索引需要更新。这需要修改代表INDEX
. 对于非唯一索引,这是在后台完成的,主要是在提交查询之后。
在完成更新 BTree 之前不存在索引不正确的风险。这是因为“更改缓冲区”。该构造保留挂起的索引更新,以便以后持久保存到磁盘。
推荐阅读
- csv - 使用 Neo4j 创建图形数据库 - 航空公司示例
- python-3.x - 无法从 Django 模板中的静态文件夹加载 Vue JS 文件
- angular - 如何检查 observable 是否属于 NEVER 类型?
- apache-nifi - 如何在Nifi(Json)中为数组中的每个元素添加键名
- c# - 如何使用线性判别分析来识别训练文件夹中的图像与测试文件夹中的相似图像?
- python - 在python中将数组转换为列表时如何删除方括号和引号
- python - 使用 BeautifulSoup 抓取 - 无组织列表中的列表
- css - 使用 CSS 隐藏类而不级联
- opengl - 如何在 1D 缓冲区中生成 2D 纹理并将其加载到 OpenGL 中?
- c++ - 我们可以在可变参数模板参数之前有可变参数概念吗?