相关文章:数据库AICD特性之–原子性 Atomicity、数据库ACID特性之–持久性 Durability、数据库AICD特性之–一致性 Consistency。

数据库AICD特性之–隔离性 Isolation

隔离性 是指数据库的并发事务之间的可访问性问题。

隔离性问题引入

首先我们需要知道隔离性问题是如何被引入的。如果只有单个事务的情况下,那么并不存在隔离性问题,当前事务中读到的数据永远是正确的。

而当多个事务并发时,则可能存在当前事务 T1 开启时,正在查询 A 表某条数据,同时另一个并发的事务 T2 又对 A 表的该数据做了修改,那么此时就会存在一些问题:

  • 如果事务 T1 读取到了事务 T2 的修改,然后基于修改的数据进行后续操作,但是事务 T2 由于发生了错误,导致数据回滚,那么 T1 读取的数据实际就是错误的,这种错误在金融系统中是无法接受的。

这种存在于多个并发事务之间的影响就叫作事务的隔离性问题。

隔离级别

通常数据库针对不同隔离级别的需求,拥有以下四种不同级别的隔离级别:

  • 读未提交(脏读):事务可以读取其它事务未提交的数据。隔离级别最低,性能最高,但是数据一致性最差。
  • 读已提交:事务中只允许读取其它事务已提交的数据。同一事务中多次读取数据结果可能不一致(不可重复读)。PostgreSQL、SQL Server、Oracle 等数据库默认级别。
  • 可重复读:同一事务中多次读取结果一致。MySQL 默认级别。
  • 串行化:强制事务串行化执行,避免了所有并发事务的问题,不过性能最差。

在实际使用中,需要根据不同需要设置隔离级别:

  • 高并发 + 低一致性需求:读未提交或读已提交(如实时统计)。
  • 平衡并发与一致性:可重复读(如大多数业务场景)。
  • 强一致性需求:串行化(如金融交易)。

实现机制

对于 MySQL,是使用 锁 + MVCC 来实现的。

  • 读未提交:允许事务读取其他事务未提交的数据,无需加锁。
  • 读已提交:
    • 读取时使用 MVCC 机制配合 undo log 读取最新版本数据,在每次读取时都会创建一个 read view。
    • 写入数据时则使用行级锁。
  • 可重复读:
    • 读取时使用 MVCC 机制配合 undo log 读取最新版本数据,不过只在每个事务开始时创建一个 read view,以此保证整个事务读的一致。
    • 写入数据时则使用行级锁。
  • 串行化:读取时使用共享锁,所有的读取操作都会加锁,并且禁用 MVCC,写入数据时使用排他锁。

演示

相关设置:基于 MySQL 8.x。

查看事务隔离级别:

SQL
-- 查看全局隔离级别
select @@GLOBAL.transaction_isolation;

-- 查看当前会话隔离级别
select @@SESSION.transaction_isolation;

修改事务隔离级别:REPEATABLE READ、READ COMMITTED、READ UNCOMMITTED、SERIALIZABLE。

SQL
-- 修改当前会话隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL SERIALIZABLE;

准备数据:

SQL
create table t1(c1 int, c2 varchar(50));
insert into t1 values (1, 'aaaa');

读未提交

  1. 先修改当前隔离级别为 READ UNCOMMITTED。
SQL
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
  1. 事务 1 中开启事务,并执行查询。
SQL
START TRANSACTION;
select @@SESSION.transaction_isolation;
select * from t1 where c1 = 1;
  1. 事务 2 开启事务,并修改数据。
SQL
START TRANSACTION;
update t1 set c2 = 'abcd' where c1 = 1;
  1. 事务 1 再次查询数据。
SQL
select * from t1 where c1 = 1;
  1. 事务 2 执行回滚。
SQL
rollback;
select * from t1 where c1 = 1;
  1. 事务 1 再次查询。
SQL
select * from t1 where c1 = 1;
commit;

从结果中可以看到,事务 1 中查询到了事务 2 没有提交时修改的数据。

事务 1 在事务 2 未提交时已经读到了修改后的数据。

读已提交

  1. 先修改事务级别为 READ COMMITTED。
SQL
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
  1. 事务 1 开启事务,进行初始查询。
SQL
START TRANSACTION;
select @@SESSION.transaction_isolation;
select * from t1 where c1 = 1;
  1. 事务 2 开启事务,修改数据。
SQL
START TRANSACTION;
update t1 set c2 = 'abcd' where c1 = 1;
  1. 事务 1 再次查询。
SQL
select * from t1 where c1 = 1;
  1. 事务 2 提交事务。
SQL
COMMIT;
select * from t1 where c1 = 1;
  1. 事务 1 再次查询,并关闭事务。
SQL
select * from t1 where c1 = 1;
COMMIT;

可以看到,事务 1 在事务 2 修改数据后但是没有提交时,没有查询到事务 2 修改的数据,在事务 2 提交后,事务 1 就可以查询到事务 2 修改的数据。

事务 2 提交前后,事务 1 的查询结果发生变化。

可重复读

  1. 先修改事务级别为 REPEATABLE READ。
SQL
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
  1. 使用 t2 表进行验证。
SQL
create table t2 (c1 int, c2 int);
insert into t2 values (1, 10);
insert into t2 values (2, 20);
  1. 事务 1 开启事务,进行初始查询。
SQL
START TRANSACTION;
select @@SESSION.transaction_isolation;
select * from t2 where c1 = 1;
  1. 事务 2 开启事务,修改数据,同时新增一条 c1 = 1 的数据。
SQL
START TRANSACTION;
update t2 set c2 = 15 where c1 = 1;
insert into t2 values (1, 30);
  1. 事务 1 再次查询。
SQL
select * from t2 where c1 = 1;
  1. 事务 2 提交事务。
SQL
COMMIT;
select * from t2 where c1 = 1;
  1. 事务 1 再次查询,并关闭事务。
SQL
select * from t2 where c1 = 1;
COMMIT;

可以看到,事务 1 中的数据在事务 2 修改后查询结果仍然保持一致。

在可重复读级别下,事务 1 的查询结果保持稳定。

感谢读到这里

日志还会继续,欢迎常来。

返回首页