【事务-隔离级别-底层实现】

事务

事务的概念

一个事务是由一条或者多条对数据库操作的SQL语句所组成的一个不可分割的单元,只有当事务中的所有操作都正常执行完了,整个事务才会被提交给数据库;如果有部分事务处理失败,那么事务就要回退到最初的状态,因此,事务要么全部执行成功,要么全部失败。所以记住事务的几个基本概念,如下:

1. 事务是一组SQL语句的执行,要么全部成功,要么全部失败,不能出现部分成功,部分失败的结果。保证事务执行的原子操作。

2. 事务的所有SQL语句全部执行成功,才能提交(commit)事务,把结果写回磁盘上。

3. 事务执行过程中,有的SQL出现错误,那么事务必须要回滚(rollback)到最初的状态

ACID特性

每一个事务必须满足下面的 4 个特性:
  • 事务的原子性(Atomic): 事务是一个不可分割的整体,事务必须具有原子特性,及当数据修改时,要么全执行,要么全不执行, 即不允许事务部分的完成。
  • 事务的一致性(Consistency): 一个事务执行之前和执行之后,数据库数据必须保持一致性状态。数据库的一致性状态必须由用户来负 责,由并发控制机制实现。就拿网上购物来说,你只有让商品出库,又让商品进入顾客的购物车才能构 成一个完整的事务。
  • 事务的隔离性(Isolation): 当两个或者多个事务并发执行时,为了保证数据的安全性,将一个事物内部的操作与其它事务的操作隔 离起来,不被其它正在执行的事务所看到,使得并发执行的各个事务之间不能互相影响。
  • 事务的持久性(Durability): 事务完成(commit)以后,DBMS保证它对数据库中的数据的修改是永久性的,即使数据库因为故障出 错,也应该能够恢复数据!

事务并发问题

  • 脏读:事务A读取事务B未提交的数据,B回滚后A读到无效数据。
  • 不可重复读:事务A多次读取同一数据,期间事务B修改导致A前后结果不一致。
  • 幻读:事务A查询范围内数据,事务B新增或删除记录导致A结果集变化。

隔离级别及解决方案

隔离级别脏读不可重复读幻读实现方式
READ UNCOMMITTED无锁
READ COMMITTED每次查询生成新快照(MVCC)
REPEATABLE READ(默认)事务首次查询生成快照(MVCC)
SERIALIZABLE锁机制(Next-Key Lock)
-- 设置隔离级别示例
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;

InnoDB默认隔离级别是REPEATABLE READ可重复读

在“可重复读“级别,实际上可以解决部分的幻读问题,但是不能防止update更新产生的虚读问题,要禁止虚读产生,还是需要设置串行化隔离级别。

隔离级别底层实现


已提交读:每次执行语句的时候都重新生成一次快照(Read View),每次`select`查询时。这样就解决了脏读


可重复读:同一个事务开始的时候生成一个当前事务全局性的快照(Read View),第一次`select`查询时。


串行化隔离级别下怎么解决幻读问题:   next-key lock:行锁+间隙锁

锁的粒度差异

表级锁
锁定整张表,适合数据量小或全表操作场景。资源消耗低,但会限制其他事务对表的访问。
行级锁
仅锁定特定行,适合高并发场景。资源消耗较高,需注意死锁问题。

-- 表级锁示例(MyISAM引擎)
LOCK TABLES orders READ;  -- 共享锁
UNLOCK TABLES;

-- 行级锁示例(InnoDB引擎)
SELECT * FROM orders WHERE id = 1 FOR UPDATE;  -- 排它锁

共享锁与排它锁

共享锁(S锁)
允许并发读取,阻止其他事务获取排它锁。适用于查询为主场景。

排它锁(X锁)
独占数据访问权,阻止其他事务获取任何锁。适用于数据修改操作。

-- 显式加锁示例
SELECT * FROM products WHERE stock > 0 LOCK IN SHARE MODE;  -- 共享锁
UPDATE products SET stock = stock -1 WHERE id = 101;        -- 自动加排它锁

锁的兼容性矩阵
当前锁 \ 请求锁S锁X锁
S锁兼容冲突
X锁冲突冲突
-- 查看锁等待情况(MySQL)
SHOW ENGINE INNODB STATUS;

间隙锁

间隙锁(Gap Lock)是InnoDB引擎在特定隔离级别(如串行化)下使用的一种锁机制,主要用于防止幻读现象。以下从多个维度分析其工作原理和应用场景。


间隙锁触发条件

使用范围条件而非相等条件检索数据时,InnoDB会为符合条件的数据记录及不存在的“间隙”加锁。例如:

SELECT * FROM user WHERE userid > 100 FOR UPDATE;

此语句会锁定所有userid > 100的记录,以及userid值大于101的未存在区间(如102, 103等)。


防止幻读的机制

间隙锁的核心目标是满足串行化隔离级别的要求。若不使用间隙锁,其他事务插入符合范围条件的新记录(如userid=102),当前事务再次执行相同查询会读到新插入的行,导致幻读。


辅助索引的间隙锁

当过滤条件为辅助索引时,间隙锁会锁定索引值的间隙范围。例如:

SELECT * FROM user WHERE age > 20 FOR UPDATE;

假设age的索引值为10,13,14,15,20,21,22,则锁定的间隙包括:

  • 15-20
  • 20-21
  • 21-22
  • 22以上 此时插入age=15age=16会被阻塞,但age=14允许插入。

全表扫描与表锁

若查询条件无法有效使用索引(如age > 14但优化器选择全表扫描),InnoDB会退化为表级锁,导致所有插入操作被阻塞。


等值查询与辅助索引

主键等值查询因唯一性无需间隙锁,但辅助索引允许重复,需间隙锁防止幻读。例如:

  • 事务A执行:
    SELECT * FROM user WHERE age = 20 FOR UPDATE;
    

  • 事务B尝试插入:
    INSERT INTO user(name, age, sex) VALUES ('ddd', 18, 'M');
    

    此操作会被阻塞,因间隙锁锁定了age=20附近的区间。

代码示例:间隙锁验证

可通过以下实验验证间隙锁行为:

-- 会话1:开启事务并锁定范围
START TRANSACTION;
SELECT * FROM user WHERE age BETWEEN 15 AND 20 FOR UPDATE;

-- 会话2:尝试插入被阻塞
INSERT INTO user(age) VALUES (16);  -- 阻塞
INSERT INTO user(age) VALUES (14);  -- 成功


关键总结
  • 作用范围:锁定索引记录间的“间隙”,阻止其他事务插入。
  • 隔离级别:主要应用于串行化隔离级别。
  • 索引影响:是否使用间隙锁取决于查询能否有效利用索引。
  • 性能权衡:过度使用间隙锁可能导致并发性能下降。

undo log回滚日志

`undo log`:回滚日志,保存了事务发生之前的数据的一个版本,用于事务执行时的回滚操作,同时也是实现多版本并发控制(MVCC)下读操作的关键技术。
`DB_TRX_ID`:事务ID

`DB_ROLL_PTR`:回滚指针

MVCC

已提交读和可重复读 ---> 底层实现原理MVCC ---> 并发方式:快照读 ---> 底层依赖undo log回滚日志

InnoDB提供了两个读取操作:锁定读(S X)和非锁定读(MVCC的快照读)

MVCC是多版本并发控制(Multi-Version Concurrency Control,简称MVCC),是MySQL中基于乐观锁理论实现隔离级别的方式,用于实现已提交读和可重复读隔离级别的实现,也经常称为多版本数据库。MVCC机制会生成一个数据请求时间点的一致性数据快照 (Snapshot), 并用这个快照来提供一定级别 (语句级或事务级) 的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本(系统版本号和事务版本号)。

MVCC多版本并发控制中,读操作可以分为两类:

1. 快照读(snapshot read):读的是记录的可见版本,不用加锁。如`select`

2. 当前读(current read):读取的是记录的最新版本,并且当前读返回的记录。如`insert,delete,update,select...lock in sharemode/for update`

MVCC:每一行记录实际上有多个版本,每个版本的记录除了数据本身之外,增加了其它字段
`DB_TRX_ID`:记录当前事务ID
`DB_ROLL_PTR`:指向undo log日志上数据的指针

已提交读:每次执行语句的时候都重新生成一次快照(Read View),每次`select`查询时。这样就解决了脏读
可重复读:同一个事务开始的时候生成一个当前事务全局性的快照(Read View),第一次`select`查询时。

快照内容读取原则
1. 版本未提交无法读取生成快照
2. 版本已提交,但是在快照创建后提交的,无法读取
3. 版本已提交,但是在快照创建前提交的,可以读取
4. 当前事务内自己的更新,可以读到

总结

MVCC(多版本并发控制)= 让读写互不干扰的黑科技

核心思想
数据库同时保存同一数据的多个版本(像时光机一样),读操作和写操作可以各干各的,互不等待。

举个栗子

  • 场景:  你在淘宝看商品库存(读操作),同时商家正在修改库存(写操作)。
  • 传统方式:  商家改库存时会上锁,你必须等他改完才能看到库存,体验很差。
  • MVCC 方式:                                                                                                                                  1. 商家修改库存前,数据库自动保存旧库存(如 100 件)到 `undo log`(版本链)。
        2. 商家修改为 99 件(当前版本),但你读的是旧版本 100 件(通过 `Read View` 判断可见性)。
        3. 你和商家的操作互不影响,并发执行!


 关键机制
1. 版本链(undo log)
    - 每次修改数据时,旧值被保存到 `undo log`,形成类似 `100 → 99 → 98` 的链条。
    - 每个版本带一个 **事务 ID**(如 `trx_id=100` 生成版本 100)。
2. 读视图(Read View)
    - 你(事务)查询时,数据库生成一个 “时间窗口”(Read View),只让你看到窗口内已完成的修改。
    - 规则:
        - 若商家事务未提交,你看不到 99 件,只能读旧版本 100 件;
        - 若商家已提交,且你的窗口包含该提交,你才能读到 99 件。
3. 读写不冲突
    - **读操作**:通过 `Read View` 直接从版本链取可见版本,无需锁。
    - **写操作**:直接修改当前版本,加锁防止其他写操作,但不影响读。

 隔离级别差异

- 读已提交(RC):  
    每次读都生成新窗口,能看到最新已提交的修改(如商家提交后,你立即看到 99 件)。
- 可重复读(RR,MySQL 默认):  
    事务内窗口固定,多次读同一数据结果一致(如你始终看到 100 件,直到事务结束)。


MVCC 让数据库像 “平行时空” 一样,写操作创建新版本,读操作按自己的时间线取数据,读写互不干扰,并发性能暴增

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值