数据库引入 事务隔离(Transaction Isolation)
的核心目的是为了解决:多事务并发执行时可能引发的数据不一致性问题
介绍
事务
保证一组原子性的操作,要么全部成功,要么全部失败。四种常见的隔离级别:
未提交读(Read UnCommitted)
事务中的修改,即使没提交对其他事务也是可见的
提交读(Read Committed)
一个事务开始时,只能看见已提交的事务所做的修改
- 事务未提交之前,所做的修改对其他事务是不可见的
- 也叫不可重复读,同一个事务多次读取同样记录可能不同
可重复读(RepeatTable Read)
同一个事务中多次读取同样的记录结果时结果相同
可串行化(Serializable)
最高隔离级别,强制事务串行执行
- SDK 实现
事务隔离要解决的三大核心问题
如果事务之间完全不受隔离,多个事务同时对同一数据执行读写操作,可能导致脏读
、不可重复读
、幻读
等问题,破坏数据的完整性和可靠性。
脏读(Dirty Read)
场景
:事务 A 修改了某数据但未提交,事务 B 读取到了这个未提交的修改
风险
:如果事务 A 最终回滚,事务 B 读到的数据是无效的(脏数据
)
示例
:
-- 事务 A 修改余额(未提交)
UPDATE accounts SET balance = 500 WHERE id = 1;
-- 事务 B 读取到未提交的余额(500)
SELECT balance FROM accounts WHERE id = 1;
不可重复读(Non-Repeatable Read)
场景
:事务 A 多次读取同一数据,事务 B 在期间修改并提交了该数据,导致事务 A 两次读取结果不一致
风险
:同一事务内无法保证数据的一致性
示例
:
-- 事务 A 第一次读取余额(100)
SELECT balance FROM accounts WHERE id = 1;
-- 事务 B 修改余额并提交
UPDATE accounts SET balance = 200 WHERE id = 1;
-- 事务 A 再次读取余额(200)
SELECT balance FROM accounts WHERE id = 1;
幻读(Phantom Read)
场景
:事务 A 按条件查询一批数据,事务 B 在期间插入或删除符合该条件的记录并提交,导致事务 A 两次查询结果的记录数不一致
风险
:范围查询的结果集不可靠
示例
:
-- 事务 A 查询余额 > 100 的账户(返回 2 条记录)
SELECT * FROM accounts WHERE balance > 100;
-- 事务 B 插入一条余额为 200 的记录并提交
INSERT INTO accounts (id, balance) VALUES (3, 200);
-- 事务 A 再次查询,返回 3 条记录
SELECT * FROM accounts WHERE balance > 100;
为什么需要不同的事务隔离级别?
不同的业务场景对数据一致性和性能的要求不同。数据库通过提供可配置的隔离级别
,允许开发者在以下两方面进行权衡:
数据一致性
:隔离级别越高,数据越一致(如 Serializable
完全避免并发问题)
并发性能
:隔离级别越高,锁的粒度越大,并发吞吐量越低
隔离级别 |
脏读 |
不可重复读 |
幻读 |
性能 |
Read Uncommitted |
❌ |
❌ |
❌ |
高 |
Read Committed |
✅ |
❌ |
❌ |
较高 |
Repeatable Read |
✅ |
✅ |
❌ |
中等 |
Serializable |
✅ |
✅ |
✅ |
低 |
事务隔离的实现机制
数据库通过以下技术实现事务隔离:
为什么数据库不默认使用最高隔离级别?
最高隔离级别(如 Serializable
)虽然能彻底解决并发问题,但会导致:
性能下降
:大量锁竞争,事务串行执行,吞吐量急剧降低
死锁风险增加
:复杂的锁机制更容易引发死锁
适用场景有限
:只有对数据一致性要求极高的场景(如金融交易)才需要
实际开发中的选择建议
-
常规业务场景
:
使用默认隔离级别(如 MySQL 的 Repeatable Read
、PostgreSQL 的 Read Committed
)。
-
高一致性场景
(如支付、库存扣减):
显式指定 Repeatable Read
或 Serializable
,结合悲观锁(SELECT ... FOR UPDATE
)。
-
高并发读场景
:
使用 MVCC 支持的隔离级别(如 Read Committed
)或乐观锁。
-
MySQL 指定事务级别示例
dba> set tx_isolation='read-committed';
dba> BEGIN;
dba> update t1 set b2=4 where b2=2;
dba> commit;
F&Q
MySQL 同一个事物中,先插入后查询可以获取到最新的数据么
这依赖于 MySQL 的事务隔离级别设置。默认的事务隔离级别是可重复读(REPEATABLE READ)
。在这个隔离级别下,在同一个事务中的查询可以看到事务中先前插入的数据。即使使用读提交(READ COMMITTED)
隔离级别,该行为依然成立。
以下是一个简单的例子:
START TRANSACTION;
INSERT INTO 表名 (列1, 列2) VALUES (值1, 值2);
SELECT * FROM 表名 WHERE 某条件; -- 这将返回你刚插入的数据(如果符合条件)
COMMIT;
确保事务中的所有操作都最终执行COMMIT
来提交事务,以保证数据被永久保存到数据库中。否则,事务会在回滚(ROLLBACK
)时撤销所有未提交的更改。
总结
事务隔离的本质是在并发性能和数据一致性之间寻找平衡
。数据库通过隔离级别和并发控制机制(锁、MVCC),既允许高效并发操作,又避免了数据混乱。理解隔离级别及其解决的问题,是设计高可靠分布式系统的关键基础。