Mysql 运行原理 (4) - InnoDB 事务原理

什么是事务

事务,就是把多个数据库操作打包成一个不可分割的整体进行执行。同时到这些操作执行后,需要保证数据的一致且可靠。

在 mysql 中可以通过下面的 SQL 开启一个事务:

-- 开启事务
START TRANSACTION;
-- 关闭默认提交,默认开启提交:0
SET autocommit = 1;

-- 业务 SQL
UPDATE users SET name = '张三-upd' WHERE name = '张三';

-- 提交事务
COMMIT;

InnoDB 解决事务的一致且可靠问题的完整方案

InnoDB 存储引擎是基于 ACID 模型解决的事务一致且可靠问题。

  • Atomicity:原子性
  • Consistency:一致性
  • Isolation:隔离性
  • Durability:持久性

一致性是最终的目标。原子、隔离、持久性都是服务、实现一致性。

一致性

原子性

指事务中的所有操作都需要像原子一样不可分割,要么事务中的多个全部成功,要么全部失败。

隔离性

指不同事务在执行期间互相隔离、不能互相干扰、不能查看彼此为提交的数据。

在没有实现隔离性前,多个事务同时执行时,会有三大明显问题:

脏读、写

一个事务读取或更新了另一个事务并没有进行事务提交的数据,当另一个事务回滚时而产生的读写问题。

例:假设有两个事务:A、B

  1. 脏写:A将一行数据的某个字段值写为C,B写为D,由于A进行了事务回滚,导致B的写入失效。(并发 写-写)
  2. 脏读:A将一行数据的某个字段值写为C,B读取C,由于A进行了事务回滚,导致B读取了脏数据C。(并发 写-读)
不可重复读

在事务执行过程中,对于同一条数据的多次读取,每次读取的数据都是不同的。对比脏读,多出了一种情况:读取的数据是其他事务修改后且提交数据。

例:假设有两个事务:A、B

  1. A将一行数据的某个字段值写为C,B读取C,由于A进行了事务回滚,导致B读取了脏数据C。
  2. B先读取C,A将其改为D并提交事务,导致B再次读取了D。
幻读

相比脏读、不可重复读,侧重点为读取到了在之前的查询时未曾被查询到的>=1条数据。

例:假设有两个事务:A、B。A查询用户表年龄大于20的人员数据有10条。B插入并提交了5条年龄大于20的人员数据。A再次查询时发现年龄大于20的数据变为了15条。

隔离性小结
  1. 本质是并发读写、读取了错误的可读版本引发的问题
  2. 脏读:读取了未提交版本的数据
  3. 幻读、不可重复读:读取了未提交、已提交版本的数据

正确的隔性实现,应当满足每次读取的数据,都是开启当前事务时的数据版本。不能读取到未提交的版本,或超前的版本。

SQL标准中的四种隔离级别

为了解决以上的问题,Mysql 实现了 SQL 标准中的四种隔离级别来实现隔离性。开发者可以根据需求进行选择:

  1. read uncommitted(读未提交:可以读取其他事务未提交的数据,但不能写即修改这些数据。并发性最高,但存有脏读的问题、不可重复读、幻读问题)
  2. read committed(读已提交:可以读取其他事务已提交的数据,避免了脏读、写。但存有不可重复读、幻读的问题)
  3. repeatable read(可重复读:避免了脏读脏写、不可重复读的问题。但存有幻读的问题)
  4. serializable(串行化:多个事务操作同份数据时,串行化执行。避免了所有的并发读写引发的隔离性问题,但并发性最低)
Level脏读不可重复读幻读
Read uncommitted
Read committed
Repeatable read
Serializable

MySQL默认的事务隔离级别为RR,但MySQL实现的RR级别与SQL标准的RR级别不同:MySQL实现的RR级别可以避免幻读发生,即可以避免脏读脏写、不可重复读、幻读的问题。

可见四种级别的特点都是不同的,需要在隔离力度与并发性之间做出选择。

Mysql 中可以通过下面的 SQL 查看并设置隔离级别:

-- 查看隔离级别
SHOW VARIABLES LIKE 'transaction_isolation';
-- 设置隔离级别
SET transaction_isolation = '';

持久性

指事务一旦提交,那事务中所有的修改就不会因为电源故障、系统崩溃等意外条件而丢失。

一致性

当保证了原子、隔离、持久性后,最核心的一致性也就达成了。即无论有多少事务在执行,无论事务中有什么样的操作,执行之前、执行过程中、执行以后的任意时刻,数据库的状态都是一致且稳定的。都可以精准的判断数据的变化,而不是数据随机混合的。

InnoDB 实现 ACID 模型的具体机制

  • 原子性:undo log,当对数据进行增删修时,都需要先将对应的 uodo log 记录下来。
    一般来说每个操作都会对应一个 undo log,有些操作可能会对应多条。根据 undo log,当事务中的操作发生异常时,反向执行每次记录的 undo log 就可以进行数据的回滚,恢复到操作之前。

  • 隔离性:锁机制 + MVCC 机制

  • 持久性:redo log + double write

    1. 内存缓冲区 BufferPool 中的数据在写入磁盘前,系统发生了崩溃,导致内存中的刷盘数据丢失:InnoDB 通过双写 redo log buffer & redo log 的机制来保证发生故障后可以从磁盘中的 redo log 文件重新获取还没有刷到 ibd 文件中的数据
    2. 将内存缓冲区 BufferPool 中的数据页刷盘到磁盘 ibd 文件过程中,若发生了系统崩溃,导致数据丢失:InnoDB 通过 double write buffer 的 双写机制来保证整个数据页的完整传输。

原子性原理

InnoDB 存储引擎中,是通过 Undo log 机制实现的事务中的原子性,回滚操作的一种机制。

每个事务在进行数据操作的时候,都会在磁盘中创建与之对应的 undo log。当事务异常时,会根据 undo log 逐一对操作的数据进行回滚,保证事务的原子性。

那么是在什么时候写入 undo log 的呢?在事务中,每个影响数据的 SQL 通过执行器执行后,首先:会在回滚段 undo log segment 中申请一个 undo log 页。然后根据 SQL 信息构造 undo log 内容,然后将其写入 undo log buffer,在刷盘到磁盘中的 undo log 文件,以保证每次操作真正数据之前,undo log 都是完整记录的。这样即使发生了异常也可以保证完整的撤销。

undo log 的页结构

那么申请的 undo log 页具体的格式是什么样的呢?

基础格式:
undo log 的页结构

undo log 页和其他的页一样,都会包含页头与页尾行。除此之外,还包含以下部分:

  1. undo page header:记录了 undo 页类型、log 偏移位置、下一页链表引用等信息的 undo page header
  2. undo log segment header:记录了回滚段信息
  3. undo log header:记录了事务相关信息、重用 undo 页信息
  4. undo log:剩余的所有空间,都会存储 undo log 日志本身了

undo log 页中 undo log 的结构

大体可以分为两部分:基本信息、根据不同操作而不同的操作信息

undo log 页中 undo log 的结构
根据 undo log 的 type 类型,最常见的可分为以下几种页:

  1. 增:TRX_UNDO_INSERT_REC
    新增 undolog
    新增时的 undo log 操作信息相对简单,只记录了主键信息:主键值、主键长度

  2. 删:TRX_UNDO_DEL_MARK_REC
    删除 undolog
    删除的 undo log 除了记录主键信息外,还记录了旧的事物 id,旧的回滚指针,以便可以找到前继版本,用来构建有序的 undo log 版本链。除此之外,还有索引字段信息。

  3. 改:TRX_UNDO_UPD_EXIST_REC

    修改分为两种情况:

    • 如果不更新主键:在删除页的基础之上加上被更新的信息
      修改 undolog-1
    • 如果更新了主键:则会记录两条 undo log,一条删除的,一条新增的。
      修改 undolog-2

若某个事务中的内容很大,一个 undo log 页没有办法完整记录,就需要申请新的 undo log 页。然后通过 undo page header 中的下一页链表引用进行关联。并且,后面的 undo log 页就不需要记录其他的行信息了:

undo log链表
以上由 undo log 页构成的链表就叫做 undo 链。
特别说明一点,由于新增产生的 undo log 在事务提交之后,就可以直接删除了。而删除和更新的 undo log 还需要服务我们之后提到的 MVCC,并不能直接删除。
为了最大程度节省空间,提升效率,对 undo log 页分为两大类:

  1. insert undo 链表:只记录新增操作,事务提交后可以直接清除
  2. update undo 链表:记录删除、更新操作

很多时候,在一个事务中不仅有对于普通表的新增、修改操作,还可能生成临时表。所以,又需要对于临时表,在创建两个 undo log 链,如下图(undo log 的完整存储格式):

undo log 完整存储格式

从数据角度出发查看 undo log

undo log 是如何体现在数据页中的 Dynamic row 行记录上的呢?

新增数据的 undo log 很简单,Insert undo 链表中每个节点都会对应一条数据行。在数据行中的 roll_ptr 字段记录 undo log 结点的指针即可,回滚时就可以通过它找到需要回滚的 undo log 了。

新增数据的 undolog
对于修改数据,Update undo log 版本链表中的每个节点都会对应一条数据行的历史版本。
每次更新,都会先将修改前的数据作为一个 update undo log 追加到数据行的版本链中,将在修改后把数据同步到数据行。并通过数据行中的 DB_RPOLL_PTR 关联刚刚新增到版本链中的 undo log 节点。回滚时找到数据行关联的 undo log 的最新节点,就可以找到整个历史修改记录了。

undo log 版本链在隔离性的实现机制 MVCC 中起到了至关重要的作用。

修改数据的 undolog

隔离性原理

指不同事务在执行期间互相隔离、不能互相干扰、不能查看彼此为提交的数据。分为:读写隔离、写写隔离。在 InnoDB 的 Repeatable Read 隔离级别中,通过锁机制 + MVCC 实现了完整的事务隔离性。

实现原理一:锁机制

锁的作用是使得多个事务之间的操作相互不影响。在事务操作一些数据之前,就会先对数据进行加锁,防止其他事务进行操作。在操作完之后,就会对锁进行释放,此时其他事务就可以上锁进行操作。

锁结构

回忆一下前面的 blog :Mysql 运行原理 (2) - SQL执行原理与InnoDB读写原理 中 InnoDB 读写原理图,在 BufferPool 中专门的一块区域,记录了锁的信息。下面我们将锁的具体信息单独拿出来进行分析。

锁结构

  1. 上锁事务的信息:记录了所属事务的 id
  2. 被锁的索引信息:锁加到了哪个索引上。聚簇索引,非聚簇索引(即二级索引)都可以被加锁
  3. 锁的类型和模式:使用一个 32 位的 BIT 数来表示。分为:

锁的类型和模式

共享锁、独占锁

共享锁与独占所就是读写锁,遵循读写锁的特性:

  1. 读读不互斥
  2. 读写互斥
  3. 写写互斥

因为读取数据不涉及修改数据,不应该是互斥的操作,所以并发读是不需要进行并发控制的。
反之,只要涉及写操作,无论其他线程是读还是写,都需要互斥。互斥读可以防止读取到未更新的历史数据。互斥写可以防止写数据丢失。

这样,根据读写锁,就可以防止数据被其他事务修改了。

可以根据下面的 SQL 开启读锁与写锁:

-- 加读锁
SELECT u.* FROM users u WHERE u.id = 1 LOCK IN SHARE MODE;

-- 加写锁
SELECT u.* FROM users u WHERE u.id = 1 FOR UPDATE;

下面我们进行互斥测试(如果通过终端工具进行测试,需要开启两个执行窗口分别执行):

-- 开启事务 1
START TRANSACTION;

-- 加读锁
SELECT u.* FROM users u WHERE u.id = 1 LOCK IN SHARE MODE;

-- 暂不提交事务 1
-- COMMIT;
-- 开启事务 2
START TRANSACTION;

-- 加读锁
SELECT u.* FROM users u WHERE u.id = 1 LOCK IN SHARE MODE;
-- 加写锁
-- UPDATE users SET `name` = "张三-upd" WHERE id = 1;
SELECT u.* FROM users u WHERE u.id = 1 FOR UPDATE;

-- 提交事务 2
COMMIT;

先执行第一个 SQL 片段,在执行第二个 SQL 片段:

读写锁测试
可以发现,事务 2 阻塞在了加写锁语句,终端上一直处于 watting 状态。当在窗口 1 执行事务 1 的 commit 操作后,窗口 2 的 SQL 就可以全部执行完毕了。可以发现一个问题:默认的,事务提交后就会释放数据上的锁资源。

合理的使用读、写锁可以解决事务间因写入而导致的脏读、不可重复读问题
因为两种锁是互斥的,无论是先读取还是先写入,都必须等到先执行者的事务提交后释放了锁资源才能继续操作。
如果事务 A 先读取,则事务 B 就不能写入,事务 A 就不会读取到事务 B 已提交的数据,或读取到事务 B 修改但又回滚的数据。
如果事务 B 先写入,则事务 A 就必须等到事务 B 提交后才能正常读取数据。
总结下来就是读写锁会使得事务串行执行,但可以控制锁的粒度减少等待成本。这是访问冲突数据的情况,对于没有冲突访问数据情况,不同事务间不存在串行化。

但是幻读的情况还是存在的,因为新增且提交的数据,是没有进行加锁控制的,还是可以查询出来。

共享意向锁、独占意向锁

意向锁的功能简单许多,在上锁时会在表上做一个标记,表示这个表已经被加锁了。这样做的好处是,不需要遍历每一条数据是否有锁,提升了锁判断的效率。

自增锁

自增锁是一个表锁,为配置了 AUTO_INCREMENT 的自增列服务。在插入数据时,会增加自增锁然后生成自增值,并阻塞其他的插入操作,以保证自增值的唯一。
如果自增值已经生成,但是事务已经回滚了,自增值也不会回退,意味着自增值可能不连续。

自增锁的机制可以通过下面的 SQL 进行查看:

-- 查看自增锁机制
SHOW VARIABLES LIKE 'innodb_autoinc_lock_mode';

有三种取值(0/1/2):

  • simple insert(可以预先知道插入数据的数量): insert into … value(…) / insert into … values(…),(…)

  • bulk insert(无法知道插入数据的数量): insert into … select …

  • 0:自增场景都添加自增锁到 SQL 执行完成,无论是 simple insert 还是 bulk insert,可以保证自增值的连续性。

  • 1:对 0 的优化,simple insert 只会使用一个轻量的 mutex 锁获取一段连续的自增值,而不是增加自增表锁,无需等待 SQL 完全执行完毕才释放自增锁。
    对于 bulk insert 依旧是使用自增表锁来保证主键的连续性,即必须等到 SQL 完全执行完后才能释放自增锁。

  • 2:完全无锁,并发度最高,但是主键可能是不连续的。

表锁、行锁
  • 表锁:在释放锁之前,拒绝其他事务操作这张表的任何数据,在特有信息中会记录被锁表的信息
  • 行锁:对表内某些行进行加锁。在特有信息中会记录被锁表的信息、所属表空间、页号,以及最重要的一个 bits 数组,记录了哪些行被锁了。
    行锁都是添加到数据行或索引行上,也就是聚簇索引树的行、非聚簇索引树的行上(非聚簇索引也叫做二级索引)。
行锁类型

行锁类型
根据不同的条件会选择不同功能的行锁。

  1. lock_rec_not_gap:精准行锁,锁住精准行。

  2. lock_gap:间隙锁,锁住行与行之间的间隙,防止在行间隙中插入数据。

  3. lock_ordinary:精准行锁与间隙锁的结合。

    总结:这三个行锁的范围根据不同的 where 范围条件进行选择。
    间隙锁锁的是个范围(x, y),搭配上行锁就会变成(x, y],即 next key lock。

  4. 插入意向锁:一个特殊的间隙锁,也是共享锁。如果一个行间隙添加了间隙锁,这时如果其他事务要在这个行间隙插入数据,就会加插入意向锁。由于是共享的,所以多个事务都可以加这个锁。当间隙锁释放时,允许多个阻塞在插入意向锁的事务同时进行,不需要串行执行提高效率。

实现原理二:MVCC

multi-version concurrency control 多版本并发控制机制。该机制不仅可以更高效的解决读写锁机制解决的脏读、不可重复读问题,还可以解决幻读。

MVCC 机制在某些场景下可以替代效率更低的锁机制,在保证隔离性的基础上,提升了读取的效率和并发性。

那么 MVCC 机制是如何实现的呢?MVCC 是基于前面讲到的 Undo log 版本链和 Read View 读视图来达成的。

下面我们有一条数据行的 undo log 版本链,那么当我们想要读取数据时,该选择哪一个版本的数据呢?

mvcc-undolog

这里我们就需要使用 Read View 来实现了。Read View 是一个视图的内存结构,在事务中通过 select 查询数据时,就会构造一个 Read View。里面记录了该数据版本链的一些统计值。这样在后续查询处理时,就不需要在遍历所有版本链了。这些统计值具体包括:

mvcc-readview

MVCC 解决脏读问题

构建好 Read View 后,需要根据特定的查询规则,才能找到唯一的可用版本,解决脏读问题

以下面的 id 为 1 的数据行的 undo log 版本链为例(注意 undo log 版本链中是包含了数据行的),我们在最新事务 id 为 120 的事务中执行查询该数据的 select 语句并构建 Read View(m_ids 不包含 80 是因为该事务已提交,不是一个活跃事务):

mvcc 脏读
从链表头节点开始遍历所有版本,根据下面的三步查找规则,判断每一个版本:

  1. 当前事务的版本:该版本的 trx_id = creator_trx_id,满足则意味着读取自己修改的数据,可以直接访问。
  2. 超前版本:该版本的 trx_id > max_trx_id,意味着该版本在当前事务生成 Read View 之后才生成,不可访问。
  3. 不超前的已提交版本:该版本的 trx_id < min_trx_id & not in m_ids,满足则意味着该版本不是超前版本,且事务不是一个未提交的活跃事务,可以直接访问。比如 undo log 1。

这样从链头遍历到链尾,找到首个符合要求的版本即可。就可以查询到其他事务已经提交的数据,或在当前事务中修改后的数据。最终查询到字段1为寒冰的版本。

MVCC 解决不可重复读问题

其实已经通过上面的 Read View 解决了。因为即使数据行的 undo log 版本链新增了节点,即其他事务再次进行了修改。但 Read View 是不变的,里面的各种统计信息不会发生变化,依旧为当前事务执行 select 语句时构建的那个 Read View,后续查询依旧使用这个 Read View。这样,根据 Read View 的 max_trx_id 就可以过滤掉新增的 undo log 版本节点,所以每次都查询结果都是一样的。Repeatable Read 隔离级别就是使用的这种方式

相反,如何事务中的每次查询都创建一个 Read View,就会出现不可重复读问题。Read Committed 隔离级别就是使用的这种方式。

快照读与当前读

根据前面 MVCC 的应用我们可以发现:MVCC 之所以解决脏读、不可重复读,重点都是在于事务中 select 执行时构建的 Read View。如:select … / select … where …

Read View 就如一个快照一样,记录了 select 时数据行的各版本的事务信息,间接的达到了记录数据行的历史版本的快照。所以,Read View 也称为 快照,通过 Read View 搜索唯一可用的版本也称为快照读

当前读,可以通过下面的语句触发:

  1. insert、update、delete
  2. select … for update
  3. select … lock in share mode

二者的区别是什么呢?

  1. 当前读:读取的是记录的最新已提交版本,并且当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。即不会搜索历史版本。
  2. 快照读:根据 Read View,可以读取数据行的历史版本,且是无锁操作。

可能会发生这情况,一个事务中,同时出现快照读与当前读,导致对于某一行数据的查询结果是不同的,如下例:

当前读与快照读

-- 事务 1
START TRANSACTION;
-- 快照读
SELECT * FROM users u WHERE u.id = 1;
-- 当前读
SELECT * FROM users u WHERE u.id = 1 FOR UPDATE;
-- 快照读
SELECT * FROM users u WHERE u.id = 1;
COMMIT;

-- 事务 2
START TRANSACTION;
UPDATE users SET NAME = '张三-upd' WHERE id = 1;
COMMIT;

执行顺序:

  1. 事务1 -> 快照读 (张三)
  2. 事务2 -> 修改并提交
  3. 事务1 -> 当前读 (张三-upd)
  4. 事务1 -> 快照读 (张三)

当前读与快照读分别读取不同的数据。并且, 当前读不会修改 Read View 中的事务数据,使得后继的快照读能读取到数据行最新的已提交 undo log 版本。假设当前读会修改 Read View,那么就会产生不可重复读的问题。

MVCC + next key 锁 解决快照读、当前读查询的幻读问题

1. MVCC 机制解决快照读下的幻读问题:如果仅是快照读,根据 undo log 版本链 + Read View 就可以解决幻读的问题。

  1. 其他事务对数据的修改会产生新版本的 undolog,那么事务id必定大于当前事务的 ReadView 中的 max_trx_id,所以不会查询到最新修改的数据。
  2. 其他事务新增的数据行上的事务id字段必定大于当前为提交的事务,所以事务id也必定大于当前事务的 ReadView 中的 max_trx_id,所以不会查询到最新新增的数据。

2. MVCC 机制解决当前读下的部分场景的幻读问题

防止幻读的例子:

-- 事务 1
START TRANSACTION;
-- 当前读,添加 next key 锁,锁住:[2, +∞]
SELECT * FROM users u WHERE u.id >= 2 FOR UPDATE;
COMMIT;

-- 事务 2
START TRANSACTION;
INSERT INTO `users`(`name`, `email`, `password`) VALUES ('孙七-2', 'qianqi@example.com', 'password123')
COMMIT;

执行循序:

  1. 事务 1 执行当前读,加 next key 锁但不提交
  2. 事务 2 执行新增,被阻塞

也就是说,通过 next key 锁的互斥性,阻塞其他事务新增数据。使得事务 1 中的所有查询都不会出现幻读的情况,但缺点显而易见,降低了并发度。

防止幻读失败的例子:

-- 事务 1
START TRANSACTION;
-- 快照读
SELECT * FROM users u WHERE u.id >= 2;
-- 当前读,添加 next key 锁,锁住:[2, +∞]
SELECT * FROM users u WHERE u.id >= 2 FOR UPDATE;
-- 快照读
SELECT * FROM users u WHERE u.id >= 2;
COMMIT;

-- 事务 2
START TRANSACTION;
INSERT INTO `users`(`name`, `email`, `password`) VALUES ('孙七-2', 'qianqi@example.com', 'password123')
COMMIT;

执行循序:

  1. 事务 1 执行快照读

    防止幻读失败的例子-1

  2. 事务 2 执行新增并提交

  3. 事务 1 执行当前读

    防止幻读失败的例子-2

  4. 事务 1 执行快照读

    防止幻读失败的例子-3

其实思考一下,问题的根源就是一个事务中同时出现了快照读与当前读,并且快照读中是没有幻读的问题。将二者查询的结果进行比较,则出现了幻读。

总结一下预防幻读的方法

  1. 一个事务中最好全部都是快照读
  2. 一个事务中若同时存在快照读与当前读,则必须保证当前读最先执行,锁住 select where 条件匹配的数据行空间,来防止其他事务新增数据导致的幻读。当锁住后,快照读也是一定不会出现问题的,因为不会有新的 undo log 追加到数据行的版本链中。

InnoDB 实现四种隔离级别原理


Level脏读不可重复读幻读
Read uncommitted
Read committed
Repeatable read
Serializable

回顾一下 SQl 标准中的四种隔离级别,以及每种级别可以解决的问题。

对于脏读、不可重复读、幻读这些隔离性问题,解决方式不仅一种。
最简单直接的,使用锁机制实现:

  1. 读、写锁:解决脏读、不可重复度
  2. next key 锁、排它锁:解决幻读

不过使用锁的缺点很明显:频繁的加锁与解锁会严重影响性能。
InnoDB 采用了锁 + MVCC相接合的机制,通过一种更高效的方式解决了以上的问题。

  • Read uncommitted
    • 读:不加锁,直接读取数据版本链中最新的版本,即当前读。会出现脏读、不可重复读、幻读问题。
    • 写:增加共享行锁,事务结束时释放。使得事务提交前,其他事务不能修改数据,但可以被读取。
  • Read committed
    • 读:不加锁,使用快照读,根据 MVCC 机制读取可读的数据版本。但每次查询都会构建一个 Read View,所以可以避免脏读问题,会出现不可重复读、幻读问题。
    • 写:增加独占行锁,事务结束时释放。使得事务提交前,其他事务不能修改数据,也不能被当前读查询。
  • Repeatable read
    • 读:不加锁,使用快照读,根据 MVCC 机制读取可读的数据版本。但无论少次查询,都会只构建一个 Read View,所以可以避免脏读、不可重复读问题。配合 next key 锁,可以解决当前读下的部分场景下的幻读问题,具体可以查看。
    • 写:增加 next key 范围行锁,事务结束时释放。使得事务提交前,其他事务不能修改、插入、删除该区间内的数据,同时也不能被当前读查询。
  • Serializable:
    • 读:直接添加共享表锁,同时均读取版本链中最新的版本。因为读写互斥,所以可以解决所有的隔离性问题,但并发度最低。
    • 写:增加独占表锁,事务结束时释放,读写操作完全串行执行。但读读操作可以同时执行。

持久性原理

redo log 与 double write 的详细说明可以查看 blog: Mysql 运行原理 (2) - SQL执行原理与InnoDB读写原理 中的名词解释。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值