MySQL的四种事务隔离级别

转载地址

本文实验的测试环境:Windows 10+cmd+MySQL5.6.36+InnoDB

一、事务的基本要素(ACID)

  1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。

   2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。

   3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

   4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

 

二、事务的并发问题

  1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

  2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

  3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

  小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

 

三、MySQL事务隔离级别

事务隔离级别脏读不可重复读幻读
读未提交(read-uncommitted)
读已提交(read-committed)
可重复读(repeatable-read)
串行化(serializable)

 

 

 

 

 

mysql默认的事务隔离级别为repeatable-read

 

四、用例子说明各个隔离级别的情况

  1、读未提交:

    (1)打开一个客户端A,并设置当前事务模式为read uncommitted(未提交读),查询表account的初始值:

 

    (2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:

 

 

    (3)这时,虽然客户端B的事务还没提交,但是客户端A就可以查询到B已经更新的数据:

 

    (4)一旦客户端B的事务因为某种原因回滚,所有的操作都将会被撤销,那客户端A查询到的数据其实就是脏数据:

 

     (5)在客户端A执行更新语句update account set balance = balance - 50 where id =1,lilei的balance没有变成350,居然是400,是不是很奇怪,数据不一致啊,如果你这么想就太天真 了,在应用程序中,我们会用400-50=350,并不知道其他会话回滚了,要想解决这个问题可以采用读已提交的隔离级别

 

  2、读已提交

    (1)打开一个客户端A,并设置当前事务模式为read committed(读已提交),查询表account的初始值:

 

    (2)在客户端A的事务提交之前,打开另一个客户端B,更新表account:

 

    (3)这时,客户端B的事务还没提交,客户端A不能查询到B已经更新的数据,解决了脏读问题:

 

    (4)客户端B的事务提交

    (5)客户端A执行与上一步相同的查询,结果 与上一步不一致,即产生了不可重复读的问题

 

   3、可重复读

     (1)打开一个客户端A,并设置当前事务模式为repeatable read,开启事务并查询表account

    (2)在客户端A的事务提交之前,打开另一个客户端B,更新表account并提交

    (3)在客户端A执行步骤(1)的查询,注意,此时事务A还未提交但B已提交:

    (4)执行步骤(1),此时客户端A事务还未提交,客户端B修改操作已提交,客户端A读到的还是未被客户端B(已提交的事务)修改后的数据。lilei的balance仍然是400与步骤(1)查询结果一致,没有出现不可重复读的 问题;

     接着客户端A(事务依旧未提交)执行update balance = balance - 50 where id = 1,balance没有变成400-50=350,lilei的balance值用的是步骤(2)(即客户端B提交的修改金额为300事务)中的350来算的注意,此时客户端A查询操作并未提交,很奇怪为什么update读到的是最新的值而不是快照中的值

            猜测:是不是当使用修改等duz独占锁操作时会强制读最新数据:

          a、在可重复读事务下,客户端B已提交更新事务,客户端A加排他锁读数据发现能读到最新的客户端B提交的数据:

         客户端A可重复读加排他锁

           b、测试,加共享锁也能强制客户端A在可重复读隔离级别下读最新数据而不是快照版本。

 

       可重复读隔离级别下加共享锁

 

 

可重复读的隔离级别下使用了MVCC机制,A事务中读取的是记录的快照版本,而非最新版本,B事务的更新是创建了一个新版本来更新,不同事务的读和写是分离的

复制代码

mysql> select * from account;
+------+--------+---------+
| id   | name   | balance |
+------+--------+---------+
|    1 | lilei  |     400 |
|    2 | hanmei |   16000 |
|    3 | lucy   |    2400 |
+------+--------+---------+
3 rows in set (0.00 sec)

mysql> update account set balance = balance - 50 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

mysql> select * from account;
+------+--------+---------+
| id   | name   | balance |
+------+--------+---------+
|    1 | lilei  |     300 |
|    2 | hanmei |   16000 |
|    3 | lucy   |    2400 |
+------+--------+---------+
3 rows in set (0.00 sec)

复制代码

    (5) 在客户端A提交事务,查询表account的初始值

复制代码

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from account;
+------+--------+---------+
| id | name | balance |
+------+--------+---------+
| 1 | lilei | 300 |
| 2 | hanmei | 16000 |
| 3 | lucy | 2400 |
+------+--------+---------+
3 rows in set (0.00 sec)

复制代码

5、可重复读客隔离级别下幻读问题:    

(6)在客户端B开启事务,新增一条数据,其中balance字段值为600,并提交

复制代码

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into account values(4,'lily',600);
Query OK, 1 row affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.01 sec)

复制代码

    (7) 可重复读客隔离级别下幻读问题:A计算balance之和,值为300+16000+2400=18700,没有把客户端B的值算进去,客户端A提交后再计算balance之和,居然变成了19300,这是因为把客户端B的600算进去了

,站在客户的角度,客户是看不到客户端B的,它会觉得是天下掉馅饼了,多了600块,这就是幻读,站在开发者的角度,数据的 一致性并没有破坏。但是在应用程序中,我们得代码可能会把18700提交给用户了,如果你一定要避免这情况小概率状况的发生,那么就要采取下面要介绍的事务隔离级别“串行化”

复制代码

mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 18700 |
+--------------+
1 row in set (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select sum(balance) from account;
+--------------+
| sum(balance) |
+--------------+
| 19300 |
+--------------+
1 row in set (0.00 sec)

复制代码

  幻读问题:

          a、在 REPEATABLE-READ可重复读情况下,mysql还是会发生幻读;

          b、发生幻读原因是事物A查询sum(balance)期间并未对整个表上锁,当事务B插入一条数据时,事务A是看不到的,当事务B插入数据提交后,事务A读到的还是旧的数据。然后A再提交事务,此时能够读到事务B提交的插入数据了,此时再查询sum(balance)会发现数据变了,这就是幻读的情境。

          c、实际操作如下:

         事务A:

事务A

         事务B:

事务B

      以上事务A与事务B操作顺序如下:

   先启动事务A,然后select sum(balance) from account;此时值为1976.00000000,然后启动事务B,插入一条数据insert into account (name,balance) values ('zengxiebo',100);然后提交事务B(注意:此时事务A未关闭),然后再在事务A中查询select sum(balance) from account;此时值依旧为1976.00000000。最后提交事务A,然后再再事务A所在的会话中查询select sum(balance) from account;此时值为2076.00000000。这样幻读情况就发生了。由此可见mysql在repeatable read隔离级别下select语句并未锁表或者使用间隙锁,当然,如果是update或者delete就会使用间隙锁了。

 

 

 

4.串行化

    (1)打开一个客户端A,并设置当前事务模式为serializable,查询表account的初始值:

复制代码

mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from account;
+------+--------+---------+
| id   | name   | balance |
+------+--------+---------+
|    1 | lilei  |   10000 |
|    2 | hanmei |   10000 |
|    3 | lucy   |   10000 |
|    4 | lily   |   10000 |
+------+--------+---------+
4 rows in set (0.00 sec)

复制代码

    (2)打开一个客户端B,并设置当前事务模式为serializable,插入一条记录报错,表被锁了插入失败,mysql中事务隔离级别为serializable时会锁表,因此不会出现幻读的情况,这种隔离级别并发性极低,开发中很少会用到。

复制代码

mysql> set session transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> insert into account values(5,'tom',0);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

复制代码

 

注意:

在mysql的可重复读中事物中,查询语句是没有上共享锁的,只有手工添加才有,当事物A手工加上共享锁时,另一个事物B对相同范围数据修改操作时会被阻塞的,即共享锁与排它锁是互斥的,如下:另一个事物B执行update语句要获取排它锁,但是A事物持有同一行的共享锁,事物B必须等待事物A中这一行共享锁释放才能执行,否则直到超时:

## 事物A
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from account;
+----+--------+------------------------+
| id | name   | balance                |
+----+--------+------------------------+
|  1 | lilei  | 0000000000450.00000000 |
|  2 | lilei  | 0000000000350.00000000 |
|  3 | hanmei | 0000000000500.00000000 |
|  4 | lucy   | 0000000000088.00000000 |
|  5 | hanmei | 0000000000500.00000000 |
|  6 | lucy   | 0000000000088.00000000 |
+----+--------+------------------------+
6 rows in set (0.00 sec)

mysql> select * from account where id=1 lock in share mode;
+----+-------+------------------------+
| id | name  | balance                |
+----+-------+------------------------+
|  1 | lilei | 0000000000450.00000000 |
+----+-------+------------------------+
1 row in set (0.00 sec)

mysql>

## 事务B:
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)

mysql> select * from account;
+----+--------+------------------------+
| id | name   | balance                |
+----+--------+------------------------+
|  1 | lilei  | 0000000000450.00000000 |
|  2 | lilei  | 0000000000350.00000000 |
|  3 | hanmei | 0000000000500.00000000 |
|  4 | lucy   | 0000000000088.00000000 |
|  5 | hanmei | 0000000000500.00000000 |
|  6 | lucy   | 0000000000088.00000000 |
+----+--------+------------------------+
6 rows in set (0.00 sec)

mysql> update account set balance=balance+50 where id=1;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql>

  补充:

  1、SQL规范所规定的标准,不同的数据库具体的实现可能会有些差异

  2、mysql中默认事务隔离级别是可重复读时读数据并不会锁住读取到的行

  3、事务隔离级别为读提交时,写数据只会锁住相应的行

  4、事务隔离级别为可重复读时,如果有索引(包括主键索引)的时候,以索引列为条件更新数据,会存在间隙锁间隙锁、行锁、下一键锁的问题,从而锁住一些行;如果没有索引,更新数据时会锁住整张表。

  5、事务隔离级别为串行化时,读写数据都会锁住整张表

   6、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大,鱼和熊掌不可兼得啊。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed,它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值