返回

文章详情

一个安全的 MySQL 升级却并不那么安全

Hacker News2026年8月29日 19:15

我收到通知,数据库版本已达到生命周期末尾,必须进行升级。同时,AWS 的扩展支持费用也成为尽快升级的良好动力。我有一个绿色副本,于是我升级了它,检查一切是否正常工作,然后切换过来。 快速且简单,对吧?我原以为是这样。然而,并不是一切都正确。一小时后,我收到有关奇怪错误的报告,于是检查数据库,发现一个特定的表(我们称之为表 X)中的 ID 分配顺序不同。之前数据库中 ID 为 1 的行现在的 ID 为 26。这个表在 6 个其他表中也被引用,其中 5 个正确地引用了这些新 ID,但有一个表 somehow 使用了之前数据库中的 ID,而这些 ID 现在指向完全不同的行。这真是令人困惑。事情怎么会搞得如此糟糕?迁移在升级之前的某个时间,运行了迁移以向表 X 添加一个新的自增主键。ALTER TABLE X ADD COLUMN id INT NOT NULL AUTO_INCREMENT PRIMARY KEY;这次迁移还更新了 6 个相关表,以便它们引用新 ID 而不是旧 ID。对于每个表,更新大致如下:UPDATE some_table JOIN x ON x.old_id = some_table.x_old_id SET some_table.x_id = x.id;自增列事实证明,向一个复制表中添加 AUTO_INCREMENT 列可能导致源和副本的行具有不同的 ID。根据 MySQL 的复制和 AUTO_INCREMENT 文档,使用 ALTER TABLE 添加 AUTO_INCREMENT 列可能不会在源和副本上产生相同的行顺序。ID 分配的顺序取决于存储引擎和行处理的顺序。好的,我之前对此并不知情。但为什么在 6 个表中有 5 个能正确引用这个新字段,而在一个表中完全搞砸了呢?二进制日志格式这就变得更加复杂了。MySQL 复制使用“二进制日志”记录源上所做的更改。这些更改随后被发送到副本,副本利用这些更改再现相同的事务。记录的内容以及如何在副本上应用,取决于二进制日志的“格式”。MySQL 支持 3 种“格式”:STATEMENT:SQL 语句本身被写入二进制日志,副本执行该语句。ROW:对单个行所做的更改被写入二进制日志,这些行更改直接应用于副本。MIXED:在混合日志中,默认为语句基础日志,但在某些情况下自动切换到行基础日志。显然,我的源数据库的 binlog_format 配置为 MIXED。因此,5 个表引用正确的新 IDs 的原因是它们的更新语句是通过 STATEMENT 模式复制的。确切的 UPDATE 语句在副本上重新运行。它查找副本的 x.id 版本,并将正确的本地 ID 写入每个相关表。但对于剩下的表,MySQL 决定使用 ROW 模式……在该模式中,副本不执行原始的 UPDATE。它接收来自源的行更改并直接应用。因此,源上生成的 x_id 值被复制到副本。但由于表 X 在副本上有不同的 ID,这些值现在指向完全不同的行。你可能会问,为什么 MySQL 仅为一个表决定使用 ROW 模式?我能找到的关于这个表和其他 5 个表之间的唯一明显区别是,这个表有一个 AUTO_INCREMENT 列。MySQL 确实有一些涉及 AUTO_INCREMENT 的特殊情况,它认为对语句基础复制不安全,因此在混合模式下使用 ROW 记录。这真是讽刺,不是吗?归根结底,这一切似乎都与 AUTO_INCREMENT 功能有关。结论在使用 MySQL 副本时要小心。这类问题令人恐惧的地方在于,它是意外的并且容易被忽视,但在生产中可能迅速变成灾难,而你不禁想知道事情是如何变成这样的。

赞助内容

NordVPN Next-gen Antivirus

本站免费、广告极少。如果觉得有帮助,可以请我们喝杯咖啡 —— 任何金额都对持续运营有实际帮助。

请我喝杯咖啡