为什么 MySQL 写入磁盘的数据比提交到数据库的数据多 5 倍?
Why is MySQL writing 5 times more data to disk, than is committed to the database?
我已经在 Ubuntu 20.04 之上安装了 MySQL 8.0.25,在 C5.2xlarge 实例上安装了 运行。
然后我 运行 一个用数据填充 10 个表的脚本。测试刚好用了 2 个小时,期间创建了 123146.5MB 的数据:
这意味着平均有 17.1MB/s 写入数据库。
但是,atop 报告了一些奇怪的事情:虽然它显示磁盘 activity 大约为 18-19MB/s,但它还显示进程 mysqld 在 10 秒示例中写入了 864MB - t运行 显示为 86.4MB /s,大约是实际提交到数据库的数据量的 5 倍:
为什么会有这样的差异?
iotop 通常还显示 MySQL 正在写入 5 倍:
pidstat 也一样:
我还尝试使用 Percona 工具包中的 pt-diskstats,但它没有显示任何内容...
我也在RDS上复现了这个问题。在这两种情况下(EC2 和 RDS),Cloudwatch 统计数据还显示 5 倍的写入...
数据库有 10 个表已填充。
其中 5 个定义如下:
CREATE TABLE `shark` (
`height` int DEFAULT NULL,
`weight` int DEFAULT NULL,
`name` mediumtext,
`shark_id` bigint NOT NULL,
`owner_id` bigint DEFAULT NULL,
PRIMARY KEY (`shark_id`),
KEY `owner_id` (`owner_id`),
CONSTRAINT `shark_ibfk_1` FOREIGN KEY (`owner_id`) REFERENCES `shark_owners` (`owner_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
另外5个表有这样的定义:
CREATE TABLE `shark_owners` (
`name` mediumtext,
`owner_id` bigint NOT NULL,
PRIMARY KEY (`owner_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
如果差异大约是 2 倍,我可以理解 - 数据首先写入 t运行saction 日志,然后提交到数据库,但是 5 倍?
这是 MySQL 的正常行为,还是我的表中的某些内容触发了此行为?
为什么有这么多“取消的写入”——大约 12%?
LOAD DATA
运行速度非常快,最小 I/O
- 每个查询至少 100 行的批量
INSERT
运行速度是单行插入的 10 倍。
autocommit
在每个 SQL 之后导致至少一个额外的 I/O(为了事务完整性)。
- 50 1 行插入,然后
COMMIT
是一种妥协。
FOREIGN KEY
需要检查另一个 table.
- 如果
innodb_buffer_pool_size
太小,会出现磁盘抖动
owner_id
是一个“二级索引”。它以半优化的方式完成,但可能涉及读取和写入,具体取决于各种因素。
- 如果可以使用较小的数据类型,tables 会更小。 (例如,
BIGINT
占用 8 个字节,通常是矫枉过正。)更小会导致更少 I/O。
name
有多大? ROW_FORMAT
用的是什么?他们密谋导致或多或少的“非记录”存储,因此磁盘 I/O.
- 您在执行插入时是否使用了多个线程?
换句话说,需要更多细节才能分析您的问题。
MySQL 使用 InnoDB 表时多次写入数据。大多数情况下,为了防止数据丢失或损坏,这是值得的,但如果您需要更大的吞吐量,则可能需要降低耐用性。
如果您根本不需要耐用性,另一种解决方案是使用 MEMORY storage engine。这将消除除二进制日志和查询日志之外的所有写入。
您已经提到了 InnoDB 重做日志(又名事务日志)。这无法禁用,但您可以减少文件同步操作的数量。阅读 https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit 了解详情。
innodb_flush_log_at_trx_commit = 0
您可以通过增加对 InnoDB 缓冲池的 RAM 分配来减少页面刷新次数,或帮助 MySQL 整合页面刷新。不要过度分配它,因为其他进程也需要 RAM。
innodb_buffer_pool_size = XXX
二进制日志是所有已提交更改的记录。您可以减少文件同步的次数。有关这如何影响性能的说明,请参阅 https://www.percona.com/blog/2018/05/04/how-binary-logs-and-filesystems-affect-mysql-performance/。
sync_binlog = 0
如果您不关心复制或时间点恢复,您也可以完全禁用二进制日志。通过注释指令关闭二进制日志:
# log_bin
或者在 MySQL 8.0 中,他们终于有了明确禁用它的指令:
skip_log_bin
或
disable_log_bin
详情见https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#option_mysqld_log-bin。
如果您的 MySQL 服务器在页面写入期间崩溃,则双写缓冲区用于防止数据库损坏。在禁用此功能之前请三思,但如果禁用它,它可以为您带来一些性能提升。
请参阅 https://www.percona.com/blog/2006/08/04/innodb-double-write/ 进行讨论。
innodb_doublewrite = 0
MySQL也有两种查询日志:普通查询日志和慢查询日志。这些都会导致一些开销,因此如果您需要最佳性能,请禁用查询日志。 https://www.percona.com/blog/2009/02/10/impact-of-logging-on-mysql’s-performance/
有一些方法可以使慢速查询日志保持启用状态,但前提是查询时间超过 N 秒。这减少了开销,但仍然允许您保留您可能想知道的最慢查询的日志。
long_query_time = 10
另一种策略是忘记优化写入次数,让它们发生。但是使用更快的存储。在 AWS 环境中,这意味着使用实例存储而不是 EBS 存储。如果实例终止,这会带来整个数据库可能丢失的风险,因此您应该保持良好的备份或副本。
我已经在 Ubuntu 20.04 之上安装了 MySQL 8.0.25,在 C5.2xlarge 实例上安装了 运行。
然后我 运行 一个用数据填充 10 个表的脚本。测试刚好用了 2 个小时,期间创建了 123146.5MB 的数据:
这意味着平均有 17.1MB/s 写入数据库。
但是,atop 报告了一些奇怪的事情:虽然它显示磁盘 activity 大约为 18-19MB/s,但它还显示进程 mysqld 在 10 秒示例中写入了 864MB - t运行 显示为 86.4MB /s,大约是实际提交到数据库的数据量的 5 倍:
为什么会有这样的差异?
iotop 通常还显示 MySQL 正在写入 5 倍:
pidstat 也一样:
我还尝试使用 Percona 工具包中的 pt-diskstats,但它没有显示任何内容...
我也在RDS上复现了这个问题。在这两种情况下(EC2 和 RDS),Cloudwatch 统计数据还显示 5 倍的写入...
数据库有 10 个表已填充。 其中 5 个定义如下:
CREATE TABLE `shark` (
`height` int DEFAULT NULL,
`weight` int DEFAULT NULL,
`name` mediumtext,
`shark_id` bigint NOT NULL,
`owner_id` bigint DEFAULT NULL,
PRIMARY KEY (`shark_id`),
KEY `owner_id` (`owner_id`),
CONSTRAINT `shark_ibfk_1` FOREIGN KEY (`owner_id`) REFERENCES `shark_owners` (`owner_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
另外5个表有这样的定义:
CREATE TABLE `shark_owners` (
`name` mediumtext,
`owner_id` bigint NOT NULL,
PRIMARY KEY (`owner_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
如果差异大约是 2 倍,我可以理解 - 数据首先写入 t运行saction 日志,然后提交到数据库,但是 5 倍? 这是 MySQL 的正常行为,还是我的表中的某些内容触发了此行为? 为什么有这么多“取消的写入”——大约 12%?
LOAD DATA
运行速度非常快,最小 I/O- 每个查询至少 100 行的批量
INSERT
运行速度是单行插入的 10 倍。 autocommit
在每个 SQL 之后导致至少一个额外的 I/O(为了事务完整性)。- 50 1 行插入,然后
COMMIT
是一种妥协。 FOREIGN KEY
需要检查另一个 table.- 如果
innodb_buffer_pool_size
太小,会出现磁盘抖动 owner_id
是一个“二级索引”。它以半优化的方式完成,但可能涉及读取和写入,具体取决于各种因素。- 如果可以使用较小的数据类型,tables 会更小。 (例如,
BIGINT
占用 8 个字节,通常是矫枉过正。)更小会导致更少 I/O。 name
有多大?ROW_FORMAT
用的是什么?他们密谋导致或多或少的“非记录”存储,因此磁盘 I/O.- 您在执行插入时是否使用了多个线程?
换句话说,需要更多细节才能分析您的问题。
MySQL 使用 InnoDB 表时多次写入数据。大多数情况下,为了防止数据丢失或损坏,这是值得的,但如果您需要更大的吞吐量,则可能需要降低耐用性。
如果您根本不需要耐用性,另一种解决方案是使用 MEMORY storage engine。这将消除除二进制日志和查询日志之外的所有写入。
您已经提到了 InnoDB 重做日志(又名事务日志)。这无法禁用,但您可以减少文件同步操作的数量。阅读 https://dev.mysql.com/doc/refman/8.0/en/innodb-parameters.html#sysvar_innodb_flush_log_at_trx_commit 了解详情。
innodb_flush_log_at_trx_commit = 0
您可以通过增加对 InnoDB 缓冲池的 RAM 分配来减少页面刷新次数,或帮助 MySQL 整合页面刷新。不要过度分配它,因为其他进程也需要 RAM。
innodb_buffer_pool_size = XXX
二进制日志是所有已提交更改的记录。您可以减少文件同步的次数。有关这如何影响性能的说明,请参阅 https://www.percona.com/blog/2018/05/04/how-binary-logs-and-filesystems-affect-mysql-performance/。
sync_binlog = 0
如果您不关心复制或时间点恢复,您也可以完全禁用二进制日志。通过注释指令关闭二进制日志:
# log_bin
或者在 MySQL 8.0 中,他们终于有了明确禁用它的指令:
skip_log_bin
或
disable_log_bin
详情见https://dev.mysql.com/doc/refman/8.0/en/replication-options-binary-log.html#option_mysqld_log-bin。
如果您的 MySQL 服务器在页面写入期间崩溃,则双写缓冲区用于防止数据库损坏。在禁用此功能之前请三思,但如果禁用它,它可以为您带来一些性能提升。 请参阅 https://www.percona.com/blog/2006/08/04/innodb-double-write/ 进行讨论。
innodb_doublewrite = 0
MySQL也有两种查询日志:普通查询日志和慢查询日志。这些都会导致一些开销,因此如果您需要最佳性能,请禁用查询日志。 https://www.percona.com/blog/2009/02/10/impact-of-logging-on-mysql’s-performance/
有一些方法可以使慢速查询日志保持启用状态,但前提是查询时间超过 N 秒。这减少了开销,但仍然允许您保留您可能想知道的最慢查询的日志。
long_query_time = 10
另一种策略是忘记优化写入次数,让它们发生。但是使用更快的存储。在 AWS 环境中,这意味着使用实例存储而不是 EBS 存储。如果实例终止,这会带来整个数据库可能丢失的风险,因此您应该保持良好的备份或副本。