数据库引入
事务隔离(Transaction Isolation)的核心目的是为了解决:多事务并发执行时可能引发的数据不一致性问题
介绍
事务保证一组原子性的操作,要么全部成功,要么全部失败。四种常见的隔离级别:未提交读(Read UnCommitted)事务中的修改,即使没提交对其他事务也是可见的- 事务可能读取未提交的数据,造成脏读。
提交读(Read Committed)一个事务开始时,只能看见已提交的事务所做的修改- 事务未提交之前,所做的修改对其他事务是不可见的
- 也叫不可重复读,同一个事务多次读取同样记录可能不同
可重复读(RepeatTable Read)同一个事务中多次读取同样的记录结果时结果相同可串行化(Serializable)最高隔离级别,强制事务串行执行
- SDK 实现
- golang database/sql 的实现
事务隔离要解决的三大核心问题
如果事务之间完全不受隔离,多个事务同时对同一数据执行读写操作,可能导致脏读、不可重复读、幻读等问题,破坏数据的完整性和可靠性。
脏读(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 |
✅ | ✅ | ✅ | 低 |
事务隔离的实现机制
数据库通过以下技术实现事务隔离:
-
锁机制(Locking): 如行锁、表锁、共享锁(S Lock)、排他锁(X Lock)悲观锁:默认认为冲突会发生,提前加锁(如SELECT ... FOR UPDATE)乐观锁:通过版本号(Version)或时间戳(Timestamp)检测冲突
-
多版本并发控制(MVCC): 为数据维护多个版本,事务读取特定时间点的快照(Snapshot),而非实时数据实现方式:PostgreSQL 和 MySQL 的 InnoDB 引擎均使用 MVCC优点:读操作不会阻塞写操作,提高并发性能
为什么数据库不默认使用最高隔离级别?
最高隔离级别(如 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;MySQL
- 支持
READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ(默认)、SERIALIZABLE。 - 使用
SET TRANSACTION ISOLATION LEVEL level>设置。
PostgreSQL
- 默认隔离级别为
READ COMMITTED,支持REPEATABLE READ和SERIALIZABLE。 - PostgreSQL 的
REPEATABLE READ实际已解决幻读。
SQLite
- 仅支持
SERIALIZABLE,不支持手动设置。
F&Q
MySQL 同一个事物中,先插入后查询可以获取到最新的数据么
这依赖于 MySQL 的事务隔离级别设置。默认的事务隔离级别是可重复读(REPEATABLE READ)。在这个隔离级别下,在同一个事务中的查询可以看到事务中先前插入的数据。即使使用读提交(READ COMMITTED)隔离级别,该行为依然成立。
以下是一个简单的例子:
START TRANSACTION;
INSERT INTO 表名 (列1, 列2) VALUES (值1, 值2);
SELECT * FROM 表名 WHERE 某条件; -- 这将返回你刚插入的数据(如果符合条件)
COMMIT;确保事务中的所有操作都最终执行COMMIT来提交事务,以保证数据被永久保存到数据库中。否则,事务会在回滚(ROLLBACK)时撤销所有未提交的更改。
总结
事务隔离的本质是在并发性能和数据一致性之间寻找平衡。数据库通过隔离级别和并发控制机制(锁、MVCC),既允许高效并发操作,又避免了数据混乱。理解隔离级别及其解决的问题,是设计高可靠分布式系统的关键基础。