0%

MySQL事务

MySQL 事务详解:ACID 特性、隔离级别与实践

事务是数据库操作的基本单位,确保一组 SQL 语句要么全部成功执行,要么全部失败回滚,是保证数据一致性的核心机制。MySQL 中仅 InnoDB 存储引擎支持事务,本文详细解析事务的特性、隔离级别及实际应用。

事务的基本概念

事务(Transaction):由一个或多个 SQL 语句组成的执行单元,具备 “原子性”—— 要么所有语句执行成功(提交),要么全部失败(回滚),不存在部分执行的情况。

  • 支持情况:MySQL 中只有 InnoDB 引擎支持事务(MyISAM、MEMORY 等引擎不支持)。
  • 典型场景:转账操作(扣减 A 账户金额 + 增加 B 账户金额,两步必须同时成功或失败)。

事务的 ACID 特性

事务的四大核心特性(ACID)是保证数据可靠性的基础:

特性 含义说明 示例
原子性(Atomicity) 事务是不可分割的最小单位,操作要么全执行,要么全不执行。 转账时,若扣减 A 账户后系统崩溃,B 账户不会增加金额(事务回滚,恢复 A 账户)。
一致性(Consistency) 事务执行前后,数据库从一个一致性状态切换到另一个一致性状态(数据规则不变)。 转账前 A+B 总金额为 1000,转账后总金额仍为 1000(不会多增或少减)。
隔离性(Isolation) 多个事务并发执行时,彼此不干扰,每个事务感觉不到其他事务的存在。 事务 1 查询余额时,事务 2 的转账操作不会影响事务 1 的查询结果(隔离级别决定影响程度)。
持久性(Durability) 事务提交后,对数据的修改永久生效,即使系统崩溃也不会丢失。 事务提交后,数据写入磁盘,重启数据库后修改仍存在。

事务的创建与控制

MySQL 默认开启 “自动提交”(autocommit=ON),即每条 SQL 语句自动作为一个事务提交。如需手动控制事务,需先关闭自动提交。

事务控制的核心语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 1. 查看自动提交状态
SHOW VARIABLES LIKE 'autocommit'; -- 默认值为 ON

-- 2. 关闭自动提交(当前会话有效)
SET autocommit = 0; -- 或 OFF

-- 3. 显式开始事务(可选,关闭自动提交后,第一条SQL自动开始事务)
START TRANSACTION; -- 或 BEGIN;

-- 4. 提交事务(使修改永久生效)
COMMIT;

-- 5. 回滚事务(撤销所有未提交的修改)
ROLLBACK;

示例:手动控制事务

1
2
3
4
5
6
7
8
9
10
11
12
-- 关闭自动提交
SET autocommit = 0;

-- 开始事务(隐含)
UPDATE account SET balance = balance - 100 WHERE id = 1; -- A账户扣100
UPDATE account SET balance = balance + 100 WHERE id = 2; -- B账户加100

-- 若两条语句都成功,提交事务
COMMIT; -- 数据永久生效

-- 若第二条语句失败,回滚事务
ROLLBACK; -- 恢复A账户余额,B账户不变

事务的隔离级别

当多个事务并发执行时,若隔离不当,会出现脏读、不可重复读、幻读等问题。MySQL 通过 “隔离级别” 控制事务间的可见性,平衡一致性与并发性能。

并发事务的三大问题

问题 定义 场景示例
脏读 事务 T1 读取了 T2 已修改但未提交的数据,若 T2 回滚,T1 读取的是 “无效数据”。 T2 修改余额为 200(未提交),T1 读取到 200;T2 回滚后,T1 读取的 200 是脏数据。
不可重复读 事务 T1 多次读取同一数据,T2 修改并提交该数据,导致 T1 两次读取结果不一致。 T1 首次读余额为 100,T2 修改为 200 并提交,T1 再次读为 200(前后不一致)。
幻读 事务 T1 读取某范围数据,T2 在该范围插入新数据并提交,T1 再次读取时多了 “幻行”。 T1 查询 id<5 的记录(3 条),T2 插入 id=4 的记录并提交,T1 再次查询变为 4 条。

四种隔离级别及解决的问题

MySQL 定义了四种隔离级别,从低到高安全性递增,并发性能递减:

隔离级别 含义 解决的问题 未解决的问题 MySQL 默认值?
READ UNCOMMITTED(读未提交) 允许读取其他事务未提交的修改。 脏读、不可重复读、幻读
READ COMMITTED(读已提交) 只允许读取其他事务已提交的修改。 脏读 不可重复读、幻读
REPEATABLE READ(可重复读) 事务期间,多次读取同一数据结果一致(禁止其他事务修改)。 脏读、不可重复读 幻读(InnoDB 可缓解)
SERIALIZABLE(串行化) 事务串行执行(加表锁),禁止并发操作。 所有问题 无(但性能极差)

隔离级别的查看与修改

1
2
3
4
5
6
7
8
9
10
11
-- 查看当前会话隔离级别(MySQL 8.0+)
SELECT @@transaction_isolation;

-- 查看全局隔离级别
SELECT @@global.transaction_isolation;

-- 修改会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

-- 修改全局隔离级别(需重启会话生效)
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ;

隔离级别的测试验证

(1)READ UNCOMMITTED(读未提交)
  • 现象:事务 T1 修改数据未提交,T2 可立即读取到修改后的值(脏读)。

  • 结论:几乎不使用,安全性极差。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    --首先设置mysql1和mysql2隔离级别为read uncommited
    set session transaction isolation level read uncommitted;
    --禁用mysql1 mysql2的自动提交
    set autocommit = 0;

    update users set u_name='李四' where id = 3;--① mysql1修改数据但未提交
    select * from users where id =3;--② mysql1 查询数据 为李四
    select * from users where id =3;--③ mysql2 查询数据 也为李四 查询到了mysql1中未提交的数据 出现了脏读

    insert into users (u_name) values ('刘亦菲');--④mysql1 插入数据但未提交
    select * from users where u_name = '刘亦菲';--⑤mysql1 查询到该数据
    select * from users where u_name = '刘亦菲';--⑥mysql2 查询到该数据 出现了幻读

    rollback;--⑦mysql1 回滚之前的操作 此时mysql2在查询发现之前查询到的数据都变了 出现了不可重复读
(2)READ COMMITTED(读已提交)
  • 现象:T1 修改未提交时,T2 看不到;T1 提交后,T2 才能看到(解决脏读),但 T1 多次读取可能不一致(不可重复读)。
  • 适用场景:对一致性要求不高,需高并发(如电商商品浏览)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
--首先设置mysql1和mysql2隔离级别为read commited
set session transaction isolation level read committed;
--禁用mysql1 mysql2的自动提交
set autocommit = 0;

update users set u_name='李四' where id = 3;--① mysql1修改数据但未提交
select * from users where id =3;--② mysql1 查询数据 为李四
select * from users where id =3;--③ mysql2 查询数据 为张三0 没有出现脏读

insert into users (u_name) values ('刘亦菲');--④mysql1 插入数据但未提交
select * from users where u_name = '刘亦菲';--⑤mysql1 查询到该数据
select * from users where u_name = '刘亦菲';--⑥mysql2 没有查询到该数据

commit;--⑦mysql1 提交之前的操作 此时mysql2在查询发现之前查询到的数据都变了 出现了不可重复读
(3)REPEATABLE READ(可重复读,MySQL 默认)
  • 现象:T1 启动后,多次读取同一数据结果不变(即使 T2 修改并提交),解决不可重复读;对 “幻读”,InnoDB 通过间隙锁缓解(阻止其他事务插入当前事务查询的范围数据)。
  • 示例
    T1 执行 SELECT * FROM users WHERE id < 10,InnoDB 会对 id<10 的范围加间隙锁,T2 无法插入 id=5 的新记录(直到 T1 提交),避免幻读。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
--首先设置mysql1和mysql2隔离级别为repeatable read
set session transaction isolation level repeatable read;
--禁用mysql1 mysql2的自动提交
set autocommit = 0;

update users set u_name='李四' where id = 3;--① mysql1修改数据但未提交
select * from users where id =3;--② mysql1 查询数据 为李四
select * from users where id =3;--③ mysql2 查询数据 为张三0 没有出现脏读

insert into users (u_name) values ('刘亦菲');--④mysql1 插入数据但未提交
select * from users where u_name = '刘亦菲';--⑤mysql1 查询到该数据
select * from users where u_name = '刘亦菲';--⑥mysql2 没有查询到该数据

commit;--⑦mysql1 提交之前的操作 此时mysql2还是没有数据
(4)SERIALIZABLE(串行化)
  • 现象:事务排队执行(加表锁),完全禁止并发,不会有任何并发问题,但性能极低。
  • 适用场景:数据一致性要求极高(如金融核心交易),可接受低并发。

幻读的特殊处理(InnoDB 间隙锁)

MySQL 的默认隔离级别(REPEATABLE READ)下,InnoDB 通过间隙锁(Gap Lock) 缓解幻读:

  • 当事务执行范围查询(如 WHERE id < 10)时,InnoDB 会对该范围的 “间隙”(如 id=1~9 之间的空白区域)加锁,阻止其他事务插入该范围的新数据。

  • 示例:

    1
    2
    3
    4
    5
    -- 事务T1执行范围更新,触发间隙锁
    UPDATE users SET age = 20 WHERE id < 10;

    -- 事务T2尝试插入id=5的记录,会被阻塞(直到T1提交或回滚)
    INSERT INTO users (id, name) VALUES (5, 'test'); -- 阻塞

保存点(Savepoint):部分回滚

保存点允许在事务中设置 “中间点”,回滚时可只撤销到该点,而非整个事务:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- 示例:使用保存点
SET autocommit = 0;
START TRANSACTION;

DELETE FROM users WHERE id = 1; -- 操作1
SAVEPOINT sp1; -- 设置保存点sp1

DELETE FROM users WHERE id = 2; -- 操作2

-- 回滚到sp1(仅撤销操作2,保留操作1)
ROLLBACK TO sp1;

-- 提交事务(最终仅删除id=1的记录)
COMMIT;
  • 注意:保存点仅在当前事务内有效,事务提交或回滚后自动失效。

事务的最佳实践

  1. 尽量缩短事务时长:减少锁持有时间,提高并发性能(避免在事务中执行无关操作,如等待用户输入)。
  2. 合理选择隔离级别:非核心业务用 READ COMMITTED 提高并发;核心业务用 REPEATABLE READ 或 SERIALIZABLE 保证一致性。
  3. 避免长事务:长事务可能导致锁积累,引发性能问题(如 InnoDB 的 undo 日志膨胀)。
  4. 显式控制事务:关键操作显式使用 START TRANSACTIONCOMMITROLLBACK,避免依赖自动提交。

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10