事务提交前的数据写入redo log buffer和Buffer Pool,标记为脏页;未提交修改对其他事务不可见靠undo log+read view实现MVCC,而非未写磁盘。
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,你显式执行 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 部分确认当前活跃事务数,避免误判MySQL 主从复制依赖 binlog,而崩溃恢复依赖 redo log。两者格式不同、写入时机不同、存储位置不同,但必须保持逻辑一致,否则可能导致主从数据不一致或恢复后 binlog 缺失。
InnoDB 使用“两阶段提交(2PC)”协调二者:
redo log 写入磁盘并标记为 PREPARE 状态binlog 到磁盘后,再将 redo log 标记为 COMMIT
这个机制要求 sync_binlog=1 和 innodb_flush_log_at_trx_commit=1 同时启用,否则可能破坏 2PC 的原子性保障。
长事务本身不直接写更多日志,但它会阻碍 purge thread 清理 undo log。只要有一个活跃事务的 read view 还需要访问某条旧版本记录,对应的 undo log 就不能被回收。
后果是:
ibdata1 或 undo tablespace 持续增长,甚至占满磁盘binlog 无法被 purged(expire_logs_days 不生效),因为从库可能还在拉取旧事务的事件排查方式:
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。