MySQL 事务与稳定性

本文最后更新于 2025年9月5日 凌晨

事务

事务可以理解为对一系列读写操作的抽象。在单体架构中,数据库通常作为系统的一个统一组件,直接为业务服务。在这种场景下,一次数据的读写请求(例如 SQL 查询)通常对应一次数据库调用:

1
2
Server --read/write--> MySQL
MySQL --ack--> Server

这是一个同步过程,应用会等待数据库返回结果,然后继续执行后续逻辑。

然而在分布式或微服务架构中,数据往往分散存储在不同的服务节点上。当一个业务操作涉及多个服务的数据修改时,就需要确保这些操作要么全部成功,要么全部失败,从而保证跨服务的数据一致性。这就是分布式事务要解决的问题。

分布式事务的实现方式可以是同步的,也可以是异步的,但其核心目标始终是 在分布式环境下维护数据的一致性和原子性

原子性 :

MySQL中的数据稳定性保证

事务的支持

在Mysql中 通过BEGIN或START TRANSACTION 开启一个事务,在默认情况下AUTOCOMMIT 设置为1 ,意味着每一个Mysql语句本身就是一次事务处理。

开启事务

1
2
3
4
5
6
BEGIN
-- 启动事务
ROLLBACK
-- 回滚事务
COMMIT
-- 提交事务

1
2
3
4
5
6
7
8
START TRANSACTION 
-- (READ ONLY) 只能对表进行只读操作
-- (READ WRITE) 可以对表进行读写操作
-- 启动事务
ROLLBACK
-- 回滚
COMMIT
-- 提交事务

当 开启事务后,当前终端的autocommit 被设置为false。

而当使用ddl 语句后,会将之前的语句自动提交

事务的稳定保证

原子性

即 事务的过程是一个原子过程,要么全部成功,要么全部失败。(undo log)

持久性

一旦事务提交,则其所做的修改会永久保存到数据库中,此时即使系统崩溃,修改的数据页不会丢失(redo log)

一致性

数据库总是从一个一致性的状态转换到另一个一致性的状态

隔离性

即 在事务中执行的修改操作不影响其他事务的操作。 (锁+mvcc)

稳定性场景

对于数据库服务来说,影响服务稳定性的有以下几种场景

  1. 处理sql请求回退,撤销事务中的配置修改
  2. 服务的异常崩溃,由于各种原因导致服务的异常退出/重启
  3. 数据的迁移,恢复
  4. 集群数据的同步

MySQL中的日志

日志

在应用程序中,日志提供的主要是程序执行过程的状态打印。但是在中间件中日志可以被理解为“请求”的快照,以Mysql为例,Mysql对于 数据请求需要经过 连接器-分析器-优化器-执行器,最终由执行器调用存储引擎,完成SQL命令的执行。那么通过日志将请求和变更记录下来,就可以达到使用日志进行数据恢复的目的。

undo log

在InnoDB中,使用undo log 实现事务请求的撤销。

每对一条记录进行一次改动,就会对应着一条undo log

undo log 用于事务执行时的回滚操作,保证事务的原子性。

undo log 会在命令执行时创建一条对应的回退指定存储到undo页中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 针对插入操作,生成一条插入的undo log记录 类型 undo序号 表号 主键
[TRX_UNDO_INSERT_REC][0][1134][1]
undo log类型 undo no 表id 主键
-- 针对删除操作,生成一条删除的undo log记录 包括 类型 undo序号 表号 主键
[TRX_UNDO_DEL_MARK_REC][2][1138][1]
undo log 类型 undo no 表id 主键
-- 针对修改操作,生成一条修改的undo log记录 包括 类型 undo序号 表号 主键
[TRX_UNDO_UPD_EXIST_REC][3][1138][2][修改字段]
undo log 类型 undo no 表id 主键
-- 修改主键操作
[TRX_UNDO_DEL_MARK_REC][4][1138][2]
-- 删除记录
[TRX_UNDO_INSERT_REC][5][1138][1000]
-- 插入记录

redo log

在InnoDB中,使用redo log 完成数据的恢复过程。

1
2
[Type][Table Space Id][Page Number][Offset][Length][Data]
类型 表空间id 页号 偏移位置 数据长度 数据

在一次事务过程中,产生的redo log必须是一个完整的组,只有读取到最终的结束标志,才会使用对应的redo log进行恢复。

redo log写满后,MySQL不再执行新的更新操作,MySQL 进行阻塞,此时会停下来将buffer pool中的脏页刷新到磁盘中,然后标记redo log 哪些记录可以擦除,接着对旧的redo log记录进行擦除,等擦除完旧记录,MySQL恢复正常运行(checkpoint)通过一个线程进行 脏页存盘工作,另一个线程检查脏页处理情况,并标记对应的redo log可以被覆盖

落盘时机 :

  1. 事务提交时
  2. InnoDB后台线程
  3. 写入redo log量已经占满总容量的50%
  4. checkpoint 落盘
  5. 服务关闭时

binlog

binlog 由执行器产生,它用于恢复数据库数据。

binlog可以以以下三种形式进行存储

1
2
3
4
5
6
statement
-- 只存储 SQL语句,优点: 空间占用小,缺点: 只记录SQL命令,可能导致在重放时生成的数据不一致
raw
-- 存储具体改变的字段和值信息,优点:一致性强,缺点:空间占用大
mix
-- 由InnoDB判断,哪些语句会变化,如果会变化使用raw进行存储,如果不会有变化使用statement存储

binlog文件创建时机:

  1. 系统重启
  2. 文件大小超过1G
  3. 执行flush logs

数据恢复

1
2
3
4
5
mysql -u root -p  > data.sql ;
mysqlbinlog -vv /var/lib/mysql/binlog.000001 --start-position=157 --stop-position=460
-- 使用 mysqlbinlog 工具 查询数据
mysqlbinlog --stop-position=460 /var/lib/mysql/binlog.000001 | mysql -u root -p
-- 使用 mysqlbinlog 工具 从binlog中恢复数据

备份

1
2
3
4
5
mysql -u root -p trx > trx.sql;
-- 将trx数据库配置导入trx.sql
-- 通过SOURCE 将trx.sql 的结构恢复到数据库中
mysqlbinlog --stop-position=909 /var/lib/mysql/binlog.000001 | mysql -u root -p
-- 从某个位置开始恢复数据 (重放)

数据复制

主从配置

1
2
3
mysqldump -u root -p > /root/trx2.sql
scp /root/trx2.sql root@192.168.126.131:~

集群配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 主
server-id = 1 #服务器id,随意,但唯一
binlog-do-db = trx #待同步的数据库日志
binlog-ignore-db = mysql #不同步的数据库日志

mysql> CREATE USER 'copyuser'@'192.168.126.131' IDENTIFIED WITH mysql_native_password BY '123456'
# 创建从数据库连接使用的用户
mysql> GRANT replication slave ON *.* to 'copyuser'@'192.168.126.131';
# 设置用户的拷贝权限

# 从
server-id = 2 # 服务器id
replicate-do-db = trx # 待同步的数据库
replicate-ignore-db = mysql,information_schema,performance_schema #不同步的数据库

mysql > stop slave;
mysql > change master to master_host='192.168.126.130',master_user='copyuser',master_password='123456'
,master_log_file='binlog.000023',master_log_pos=696;
# 设置同步,指定主库ip,用户,密码,同步的binlog文件,以及对应的同步位置。

binlog写入过程 → binlog cache →pagecache

设置sync_binlog

sync_binlog = 0 每次提交事务都write,不fsync,由操作系统决定何时fsync

sync_binlog = 1 每次提交事务都write,然后马上执行fsync

sync_binlog = N 每次提交事务都write,但积累N个事务才fsync

两阶段提交

redolog两阶段提交

1. 事务开始阶段

1
2
3
BEGIN TRANSACTION
├── 分配事务ID
└── 创建undo log space

2. DML操作执行阶段

对于每个DML操作(INSERT/UPDATE/DELETE):

1
执行顺序:undo log → 内存修改 → redo log

具体流程:

  1. 写undo log :首先记录原始数据,用于回滚
  2. 修改Buffer Pool :在内存中修改数据页
  3. 写redo log buffer :记录修改操作,用于崩溃恢复

3. 查询执行

1
2
3
4
SELECT操作与DML并发执行
├── 通过MVCC读取一致性视图
├── 利用undo log构建历史版本
└── 不写入任何日志

4. 事务提交阶段(关键)

这是最复杂的阶段,涉及 两阶段提交

Prepare阶段

1
2
3
4
1. redo log写入磁盘(flush)
2. redo log标记为prepare状态
3. 写入binlog到binlog cache
4. binlog刷盘(flush)

Commit阶段

1
2
3
1. redo log标记为commit状态
2. 事务提交完成
3. undo log可以被清理(延迟清理)

MySQL 事务与稳定性
http://gadoid.io/2025/09/05/MySQL-事务与稳定性/
作者
Codfish
发布于
2025年9月5日
许可协议