MySQL的事务学习

MySQL的事务

本文为《MySQL技术内幕: InnoDB存储引擎》的阅读笔记。

前言

事务就是一组原子性的SQL查询,或者说一个独立的工作单元。
事务内的语句,要么全部执行成功,要么全部执行失败。

    一个运行良好的事务处理系统,具备以下特征。

  • 原子性 (atomicity)
  • 一致性 (consistency)
  • 隔离性 (isolation)
  • 持久性 (durability)

    其中,ISOANIS SQL 标准制定了四种事务隔离级别的标准,但是很少有数据库厂商遵循这些标准。比如 Oracle 数据库就不支持 READ_UNCOMMITTEDREPEATABLE_READ

    事务的实现

在这里插入图片描述
    redoundo 的作用都可以视为一种恢复操作,redo 恢复提交事务修改的操作,而 undo 回滚行记录到某个特定的版本。redo 通常是物理日志,记录的是页的物理修改操作,undo 是逻辑日志,根据每行记录进行记录。

redo

    重做日志有两部分组成:一是内存中的重做日志缓冲 (redo log buffer),其是易失的;二是重做日志文件 (redo log file),其是持久的。

    InnoDB 是事务的存储引擎,当事务提交时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的提交操作完成才算完成。在 InnoDB 中,redo log 用来保证事务的持久性。

undo

    undo log 用来帮助事务回滚以及 MVCC 的功能。redo 存放在重做日志文件中,与 redo 不同,undo 存放在数据库内部的一个特殊段(segment)中,这个段称为 undo 段。undo 段位于共享表空间内。

    undo 是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。当 InnoDB 存储引擎回滚时,它实际做的是与先前相反的工作。对于每个 INSERTInnoDB 存储引擎会完成一个 DELETE;对于每个 DELETEInnoDB 存储引擎会执行一个 INSERT

    在SQL标准中定义了四种隔离级别。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。

   undo 的另一个作用是 MVCC,即在 InnoDB 存储引擎中的 MVCC 的实现是通过 undo 来完成。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过 undo 读取之前的版本信息,以此实现非锁定读取。

   undo log 会产生 redo log

    四种隔离级别分别是:

  1. 未提交读 (READ_UNCOMMITTED)
  2. 提交读 (READ_COMMITTED)
  3. 可重复读 (REPEATABLE_READ)
  4. 可串行化 (SERIALIZABLE)
    在这里插入图片描述

未提交读

    READ_UNCOMMITTED 级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,也称为脏读。从性能上来说,READ_UNCOMMITTED 不会比其他级别好太多,但却缺乏其他级别的很多好处,一般很少使用。

脏读

    将两个会话的隔离级别都修改为未提交读。(我试了将一个会话设置为未提交读,另一个隔离级别默认,但是没有效果

## 初始数据
mysql> SELECT * FROM t_bank;
+----+-------+---------+
| id | name  | balance |
+----+-------+---------+
|  1 | Tom   | 1600.00 |
|  2 | Cindy | 100.00  |
|  3 | Jerry | 100.00  |
+----+-------+---------+
3 rows in set (0.02 sec)

    会话执行实例:下表第5行,两个事务中,会话B可以读取到会话A的修改结果。

Time会话A会话B
1SELECT @@tx_isolation;SELECT @@tx_isolation;
2SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
3START TRANSACTION;START TRANSACTION;
4UPDATE t_bank SET balance = balance - 100 WHERE id = 1;
5SELECT * FROM t_bank;
6ROLLBACK;ROLLBACK;

提交读

    大多数数据库系统默认隔离级别都是 READ_COMMITTED (但 MySQL 不是)。一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读, 因为两次执行同样的查询,可能会得到不一样的结果。

复现未提交读

    会话执行实例:下表第7行,两个事务中,会话A读取不到会话B的修改结果。可是一旦会话B提交了事务,会话A就可以读取到会话B修改的结果了。

Time会话A会话B
1SELECT @@tx_isolation;SELECT @@tx_isolation;
2SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
3START TRANSACTION;START TRANSACTION;
4SELECT * FROM t_bank;
5UPDATE t_bank SET balance = balance - 100 WHERE id = 1;
6SELECT * FROM t_bank; // 1700
7SELECT * FROM t_bank; // 1800
8COMMIT;
9SELECT * FROM t_bank; // 1700
8ROLLBACK;ROLLBACK;

可重复读

    REPEATABLE_READ 解决了脏读的问题。该级别保证了在同一事务中多次读取同样记录的结果是一致的。但是没有解决幻读问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,会产生幻行InnoDBXtraDB 存储引擎通过多版本并发控制解决了幻读问题。

    可重复读MySQL默认事务隔离级别

验证 REPEATABLE_READ 解决了可重复读

    会话执行实例:下表第6行,说明解决了脏读。下表第8行,说明解决了不可重复读。注意第9行,是在会话B提交的结果上修改的。

Time会话A会话B
1SELECT @@tx_isolation;SELECT @@tx_isolation;
2SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
3START TRANSACTION;START TRANSACTION;
4SELECT * FROM t_bank;SELECT * FROM t_bank;
5UPDATE t_bank SET balance = balance + 100 WHERE id = 1;
6SELECT * FROM t_bank; // 1600SELECT * FROM t_bank; // 1700
7COMMIT;
8SELECT * FROM t_bank; // 1600SELECT * FROM t_bank; // 1700
9UPDATE t_bank SET balance = balance + 100 WHERE id = 1;
10SELECT * FROM t_bank; // 1800SELECT * FROM t_bank; // 1700
11COMMIT;
12SELECT * FROM t_bank; // 1800

    关于幻读的操作,在一次事务里面,多次查询之后,结果集的个数不一致的情况叫做幻读,而多出来或者少出来的那一行叫做幻行。

    在快照读的情况下,MySQL 通过 MVCC 来避免幻读。

    在当前读的情况下,MySQL 通过 next-key 来避免幻读。

mysql> select * from t_bank;
+----+-------+---------+
| id | name  | balance |
+----+-------+---------+
|  1 | Tom   | 2100.00 |
|  2 | Cindy | 100.00  |
|  3 | Jerry | 100.00  |
+----+-------+---------+
3 rows in set (0.03 sec)
Time会话A会话B
1SELECT @@tx_isolation;SELECT @@tx_isolation;
2SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
3START TRANSACTION;
4# 使用当前读(加共享锁)
SELECT * FROM t_bank LOCK IN SHARE MODE;
5START TRANSACTION;
6INSERT INTO t_bank VALUES(4, “new_man”, 500); // 执行此条命令阻塞
7ROLLBACK;强行停止

在这里插入图片描述

InnoDB可重复读隔离级别下如何避免幻读

    与标准 SQL 不同,InnoDB 存储引擎在 REPEATABLE_READ 事务隔离级别下,使用Next-Key Lock锁的算法,因此避免幻读的产生。

RR级别下

  • 表象:快照读(非阻塞读) – 伪MVCC机制
  • 内在:next-key锁 (行锁+gap锁)

什么是当前读
加了锁(共享锁和排他锁)的增删改查语句 (读取的是最新版本)

  • 当前读:select …… lock in share modeselect……for update
  • 当前读:updatedeleteinsert

什么是快照读

  • 快照读:不加锁的非阻塞读,select (非 串行化隔离级别下 )

RCRR 级别下的 InnoDB 的快照读 (非阻塞读) 如何实现

    数据行里的DB_TRX_IDDB_ROLL_PTRDB_ROW_ID字段。

  • DB_TRX_ID : 用来标识对本行作修改的事务id
  • DB_ROLL_PTR : undo日志使用
  • DB_ROW_ID : 隐藏主键字段

在这里插入图片描述
    read view决定可以看到哪个版本的事务。有一个可见性算法。

    在RC级别下,当前的和快照读读取到的版本的一样的。

next-key锁 (行锁+gap锁)

RR 级别下如何避免幻读?
因为有next-key锁。

    Gap 锁在 RC 或者更低隔离级别下是没有的。

什么情况下使用Gap锁
  • 如果 where 条件全部命中,则不会使用 Gap 锁,只会加记录锁。
    • 如果走的是唯一索引并且命中记录,则不会加 Gap 锁。
  • 如果 where 条件部分命中或者全不命中,则会加 Gap 锁。
  • Gap 锁会用在非唯一索引或者不走索引的当前读中
    • 不走索引:锁表的效果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值