事务隔离级别实现原理

ANSI/ISO SQL定义的标准隔离级别有四种,从高到底依次为:可序列化(Serializable)、可重复读(Repeatable reads)、提交读(Read committed)、未提交读(Read uncommitted)。

下面将依次介绍这四种事务隔离级别的概念、用法以及解决了哪些问题(读现象)

未提交读(Read uncommitted)

未提交读(READ UNCOMMITTED)是最低的隔离级别。通过名字我们就可以知道,在这种事务隔离级别下,一个事务可以读到另外一个事务未提交的数据。

未提交读的数据库锁情况(实现原理)

事务在读数据的时候并未对数据加锁。

事务在修改数据的时候只对数据增加行级共享锁。

现象

事务1读取某行记录时,事务2也能对这行记录进行读取、更新(因为事务一并未对数据增加任何锁)

当事务2对该记录进行更新时,事务1再次读取该记录,能读到事务2对该记录的修改版本(因为事务二只增加了共享读锁,事务一可以再增加共享读锁读取数据),即使该修改尚未被提交。

事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(因为事务一对数据增加了共享读锁,事务二不能增加排他写锁进行数据的修改)

举例
/* 事务一 */

SELECT age FROM users WHERE id = 1;

/* will read 20 */     
     
/* 事务二 */ 
UPDATE users SET age = 21 WHERE id = 1;

/* No commit here */

/* 事务一 */
SELECT age FROM users WHERE id = 1;
/* will read 21 */     
     
ROLLBACK;
/* lock-based DIRTY READ */

事务一共查询了两次,在两次查询的过程中,事务二对数据进行了修改,并未提交(commit)。但是事务一的第二次查询查到了事务二的修改结果。这种现象我们称之为脏读。

所以,未提交读会导致脏读

提交读(Read committed)

提交读(READ COMMITTED)也可以翻译成读已提交,通过名字也可以分析出,在一个事务修改数据过程中,如果事务还没提交,其他事务不能读该数据。

提交读的数据库锁情况

事务对当前被读取的数据加 行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁;

事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。

现象

事务1在读取某行记录的整个过程中,事务2都可以对该行记录进行读取(因为事务一对该行记录增加行级共享锁的情况下,事务二同样可以对该数据增加共享锁来读数据。)。

事务1读取某行的一瞬间,事务2不能修改该行数据,但是,只要事务1读取完改行数据,事务2就可以对该行数据进行修改。(事务一在读取的一瞬间会对数据增加共享锁,任何其他事务都不能对该行数据增加排他锁。但是事务一只要读完该行数据,就会释放行级共享锁,一旦锁释放,事务二就可以对数据增加排他锁并修改数据)

事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(事务一在更新数据的时候,会对该行数据增加排他锁,知道事务结束才会释放锁,所以,在事务二没有提交之前,事务一都能不对数据增加共享锁进行数据的读取。所以,提交读可以解决脏读的现象)

举例
/* 事务一 */

SELECT * FROM users WHERE id = 1;
 
     
/* 事务二 */ 
UPDATE users SET age = 21 WHERE id = 1;

COMMIT;

/* in multiversion concurrency
control, or lock-based READ COMMITTED */

/* 事务一 */
SELECT * FROM users WHERE id = 1;

COMMIT; 
/*lock-based REPEATABLE READ */

在提交读隔离级别中,在事务二提交之前,事务一不能读取数据。只有在事务二提交之后,事务一才能读数据。

但是从上面的例子中我们也看到,事务一两次读取的结果并不一致,所以提交读不能解决不可重复读的读现象。

简而言之,提交读这种隔离级别保证了读到的任何数据都是提交的数据,避免了脏读(dirty reads)。但是不保证事务重新读的时候能读到相同的数据,因为在每次数据读完之后其他事务可以修改刚才读到的数据。

可重复读(Repeatable reads)

可重复读(REPEATABLE READS),由于提交读隔离级别会产生不可重复读的读现象。所以,比提交读更高一个级别的隔离级别就可以解决不可重复读的问题。这种隔离级别就叫可重复读(这名字起的是不是很任性!!)

可重复读的数据库锁情况

事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加行级共享锁,直到事务结束才释放;

事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加行级排他锁,直到事务结束才释放。

现象

事务1在读取某行记录的整个过程中,事务2都可以对该行记录进行读取(因为事务一对该行记录增加行级共享锁的情况下,事务二同样可以对该数据增加共享锁来读数据。)。

事务1在读取某行记录的整个过程中,事务2都不能修改该行数据(事务一在读取的整个过程会对数据增加共享锁,直到事务提交才会释放锁,所以整个过程中,任何其他事务都不能对该行数据增加排他锁。所以,可重复读能够解决不可重复读的读现象)

事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。(事务一在更新数据的时候,会对该行数据增加排他锁,知道事务结束才会释放锁,所以,在事务二没有提交之前,事务一都能不对数据增加共享锁进行数据的读取。所以,提交读可以解决脏读的现象)

举例
/* 事务一 */
SELECT * FROM users WHERE id = 1;
COMMIT;

/* 事务二 */ 
UPDATE users SET age = 21 WHERE id = 1;
COMMIT;

/* in multiversion concurrency
control, or lock-based READ COMMITTED */

在上面的例子中,只有在事务一提交之后,事务二才能更改该行数据。所以,只要在事务一从开始到结束的这段时间内,无论他读取该行数据多少次,结果都是一样的。

从上面的例子中我们可以得到结论:可重复读隔离级别可以解决不可重复读的读现象。但是可重复读这种隔离级别中,还有另外一种读现象他解决不了,那就是幻读。看下面的例子:

/* 事务一 */
SELECT * FROM users WHERE age BETWEEN 10 AND 30;
 
/* 事务二 */ 
INSERT INTO users VALUES ( 3, 'Bob', 27 );
COMMIT;

/* 事务一 */
SELECT * FROM users WHERE age BETWEEN 10 AND 30;

上面的两个事务执行情况及现象如下:

  1. 事务一的第一次查询条件是age BETWEEN 10 AND 30;如果这是有十条记录符合条件。这时,他会给符合条件的这十条记录增加行级共享锁。任何其他事务无法更改这十条记录。
  2. 事务二执行一条sql语句,语句的内容是向表中插入一条数据。因为此时没有任何事务对表增加表级锁,所以,该操作可以顺利执行。
  3. 事务一再次执行SELECT * FROM users WHERE age BETWEEN 10 AND 30;时,结果返回的记录变成了十一条,比刚刚增加了一条,增加的这条正是事务二刚刚插入的那条。

所以,事务一的两次范围查询结果并不相同。这也就是我们提到的幻读。

注意!!!!!

虽然事务一在整个事务中对某些行都加了共享锁,但是!他对事务二新增的记录并没有能力加锁!!无法对未来加锁,所以导致幻读!

可序列化(Serializable)

可序列化(Serializable)是最高的隔离级别,前面提到的所有的隔离级别都无法解决的幻读,在可序列化的隔离级别中可以解决。

我们说过,产生幻读的原因是事务一在进行范围查询的时候没有增加范围锁(range-locks:给SELECT 的查询中使用一个“WHERE”子句描述范围加锁),所以导致幻读。

可序列化的数据库锁情况

事务在读取数据时,必须先对其加 表级共享锁 ,直到事务结束才释放;

事务在更新数据时,必须先对其加 表级排他锁 ,直到事务结束才释放。

现象

事务1正在读取A表中的记录时,则事务2也能读取A表,但不能对A表做更新、新增、删除,直到事务1结束。(因为事务一对表增加了表级共享锁,其他事务只能增加共享锁读取数据,不能进行其他任何操作)

事务1正在更新A表中的记录时,则事务2不能读取A表的任意记录,更不可能对A表做更新、新增、删除,直到事务1结束。(事务一对表增加了表级排他锁,其他事务不能对表增加共享锁或排他锁,也就无法进行任何操作)

虽然可序列化解决了脏读、不可重复读、幻读等读现象。但是序列化事务会产生以下效果:

  1. 无法读取其它事务已修改但未提交的记录。
  2. 在当前事务完成之前,其它事务不能修改目前事务已读取的记录。
  3. 在当前事务完成之前,其它事务所插入的新记录,其索引键值不能在当前事务的任何语句所读取的索引键范围中。

四种事务隔离级别从隔离程度上越来越高,但同时在并发性上也就越来越低。之所以有这么几种隔离级别,就是为了方便开发人员在开发过程中根据业务需要选择最合适的隔离级别。

转载于:https://www.cnblogs.com/amunote/p/10355763.html

### 数据库事务隔离级别及其底层实现机制 #### 3.1 事务的 ACID 特性与隔离级别的定义 数据库事务具有四个核心特性,即 **ACID**:原子性、一致性、隔离性和持久性。其中,隔离性决定了事务并发执行时对其他事务操作的可见性程度。根据隔离级别的不同,数据库可能面临以下几种并发问题: - **脏读(Dirty Read)**:一个事务读取了另一个未提交事务的数据。 - **不可重复读(Non-Repeatable Read)**:在同一事务中多次读取同一数据,结果不一致,因为其他事务对该数据进行了更新或删除。 - **幻读(Phantom Read)**:在同一事务中两次范围查询,结果集数量变化,因为其他事务插入或删除了符合条件的新记录[^4]。 MySQL 提供四种标准隔离级别来控制这些并发行为: | 隔离级别 | 脏读 | 不可重复读 | 幻读 | |----------|------|--------------|-------| | 读未提交(READ UNCOMMITTED) | 允许 | 允许 | 允许 | | 读已提交(READ COMMITTED) | 禁止 | 允许 | 允许 | | 可重复读(REPEATABLE READ) | 禁止 | 禁止 | 禁止(通过 Next-Key Locking) | | 串行化(SERIALIZABLE) | 禁止 | 禁止 | 禁止 | #### 3.2 MVCC 多版本并发控制的实现原理 MVCC(Multi-Version Concurrency Control)是一种用于提高并发性能的技术,主要应用于“快照读”场景。在 MySQL 的 InnoDB 存储引擎中,每一行数据都包含两个隐藏字段:`DB_TRX_ID` 和 `DB_ROLL_PTR`,分别记录最后一次修改该行的事务 ID 和指向 Undo Log 的指针[^5]。 当事务进行快照读时,InnoDB 会根据当前活跃事务列表判断哪些数据版本是可见的,并沿着 `DB_ROLL_PTR` 指针回溯到合适的版本。这种方式避免了锁竞争,提升了读写并发性能。 #### 3.3 当前读与幻读问题的解决机制 对于“当前读”(如 `SELECT ... FOR UPDATE`, `SELECT ... LOCK IN SHARE MODE`),MVCC 无法单独防止幻读。此时需要借助 **Next-Key Locking** 来解决这一问题。Next-Key Lock 是 Record Lock(记录锁)和 Gap Lock(间隙锁)的结合体,它不仅锁定实际存在的记录,还锁定记录之间的空隙,防止其他事务插入新数据,从而避免幻读的发生[^1]。 例如,在可重复读隔离级别下,如果事务 A 执行了范围查询并加锁,事务 B 尝试插入一条符合该范围的新记录,将被阻塞直到事务 A 提交或回滚。这种机制确保了事务 A 在后续相同查询中看到的结果保持一致[^3]。 #### 3.4 各隔离级别下的并发控制策略 - **READ UNCOMMITTED**:不做任何并发控制,所有变更都可以被读取,存在脏读风险。 - **READ COMMITTED**:使用 MVCC 实现每次查询生成新的快照,避免脏读,但可能出现不可重复读。 - **REPEATABLE READ**:默认隔离级别,MVCC 保证事务内多次快照读结果一致;Next-Key Locking 防止幻读。 - **SERIALIZABLE**:所有读操作自动加上共享锁,写操作加上排他锁,完全串行化执行,杜绝所有并发问题,但性能代价最高[^4]。 --- ```sql -- 设置事务隔离级别示例 SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ; ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值