事务提交前的数据到底写到哪了
MySQL 执行
INSERT、
UPDATE、
DELETE时,即使还没
COMMIT,数据也并非“只在内存里晃荡”。InnoDB 会立即将变更写入
redo log buffer(内存中的重做日志缓冲区),同时更新 Buffer Pool 中对应页的副本。这些页此时被标记为“脏页”,但尚未刷盘。
关键点在于:未提交事务的修改对其他事务不可见,靠的是
undo log+
read view实现 MVCC,而不是靠“没写磁盘”来保证隔离性。
redo log保证崩溃可恢复(持久性),它记录的是“物理逻辑日志”,不是 SQL 语句
undo log存在
ibdata1或独立表空间中,用于回滚和构造一致性读视图 如果事务中途崩溃,
redo log中未刷盘的部分丢失,但已刷盘的
redo log会在重启时重放;而未
COMMIT的事务,其
undo log记录会被用于回滚——这正是 ACID 中原子性与持久性的协同机制
autocommit=OFF 时哪些操作会隐式开启新事务
当
autocommit=OFF,你显式执行
BEGIN或
START TRANSACTION后,事务边界看似清晰。但 MySQL 在某些语句执行时会自动提交当前事务并开启新事务,导致你以为的“大事务”其实被悄悄拆开。
典型触发语句包括:
ALTER TABLE、
DROP TABLE、
CREATE INDEX、
RENAME TABLE、
ANALYZE TABLE,以及任何 DDL 操作(除
CREATE TEMPORARY TABLE外)。 执行
ALTER TABLE t1 ADD COLUMN x INT前若有未提交事务,MySQL 会先隐式
COMMIT当前事务,再执行 DDL,DDL 完成后再开启新事务(如果 autocommit 仍为 OFF)
SELECT不会触发隐式提交,但
SELECT ... FOR UPDATE或
SELECT ... LOCK IN SHARE MODE属于事务内加锁操作,必须在事务中执行,否则会自动开启一个只读事务(且立即释放锁) 可通过
SHOW ENGINE INNODB STATUS\G查看
TRANSACTIONS部分确认当前活跃事务数,避免误判
binlog 和 redo log 如何协同保证主从一致性
MySQL 主从复制依赖
binlog,而崩溃恢复依赖
redo log。两者格式不同、写入时机不同、存储位置不同,但必须保持逻辑一致,否则可能导致主从数据不一致或恢复后 binlog 缺失。
InnoDB 使用“两阶段提交(2PC)”协调二者:
第一阶段:事务 prepare 时,将redo log写入磁盘并标记为
PREPARE状态 第二阶段:写入
binlog到磁盘后,再将
redo log标记为
COMMIT若 crash 发生在 prepare 后、binlog 写入前:重启后该事务被回滚(因无对应 binlog,不可复制) 若 crash 发生在 binlog 写入后、redo commit 前:重启后该事务被提交(因有 binlog,必须保证主库和从库行为一致)
这个机制要求
sync_binlog=1和
innodb_flush_log_at_trx_commit=1同时启用,否则可能破坏 2PC 的原子性保障。
长事务为什么会导致 undo log 膨胀和复制延迟
长事务本身不直接写更多日志,但它会阻碍
purge thread清理
undo log。只要有一个活跃事务的
read view还需要访问某条旧版本记录,对应的
undo log就不能被回收。
后果是:
ibdata1或
undo tablespace持续增长,甚至占满磁盘 主库
binlog无法被 purged(
expire_logs_days不生效),因为从库可能还在拉取旧事务的事件 从库应用 relay log 时,若遇到大事务(如全表 UPDATE),会单线程卡住,造成复制延迟飙升
排查方式:
SELECT trx_id, trx_state, trx_started, trx_mysql_thread_id, trx_query FROM information_schema.INNODB_TRX ORDER BY trx_started LIMIT 5;重点关注
trx_state = 'RUNNING'且
trx_started时间过久的记录。
真正危险的不是“事务运行时间长”,而是“事务持有锁 + 持有 read view 时间长”。哪怕只是
SELECT不加锁,只要没提交,也会拖住 purge。
