第十章:第13节 MySQL进阶篇——事务的隔离性

更新于:2022-08-18 10:14:16

MySQL的索引我们就探讨到此,对初学MySQL的朋友希望有所帮助。


接下来我们开始探讨MySQL另一重要话题——事务。


索引的目的是帮助查询提高效率的,事务的目的则是确保数据安全的。在上一章,我们对事务简单了解过,InnoDB支持事务,MyISAM不支持事务。没有开启事务前,一条SQL语句就是一个事务。开启事务后,可以把多条SQL语句当成一个事务,要么全部执行,要么全部不执行。


回顾事务的四大特性:


原子性:一个事务中的所有语句,应该做到:要么全做,要么一个都不做。


一致性:让数据保持逻辑上的“合理性”,比如:一个商品出库时,既要让商品库中的该商品数量减1,又要让对应用户的购物车中的该商品加1。


隔离性:如果多个事务同时并发执行,但每个事务就像各自独立执行一样。


持久性:一个事务执行成功,则对数据来说应该是一个明确的硬盘数据更改(而不仅仅是内存中的变化)。


原子性、一致性、隔离性楠神不详细说了,也都好理解,怎么使用事务上节也讲过这里也不说了,从这节开始我们要重点去研究下MySQL的隔离特性。


比方一个网站流量很高,那就免不了很多人都在操作数据库,同一字段上的数据可能多个人(理解为多个事务)同时去修改,假如说MySQL没有对多个事务做隔离(或者说约束吧),必然会造成数据出错。就像楠神前面讲队列时举得“抢购”的例子,无约束就会出现“超卖”的现象。


首先我们需要理解下5种在事务并发下会导致的出错问题:


1)第一类丢失更新:在没有事务隔离的情况下,两个事务都同时更新一行数据,但是第二个事务却中途失败退出, 导致对数据的两个修改都失效了。


例如:

  张三的工资为5000,事务A中获取工资为5000,事务B获取工资为5000,汇入100,并提交数据库,工资变为5100,

  随后

  事务A发生异常,回滚了,恢复张三的工资为5000,这样就导致事务B的更新丢失了。


2)脏读:当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。


例如:
  张三的工资为5000,事务A中把他的工资改为8000,但事务A尚未提交。
  与此同时,
  事务B正在读取张三的工资,读取到张三的工资为8000。
  随后,
  事务A发生异常,而回滚了事务。张三的工资又回滚为5000。
  最后,
  事务B读取到的张三工资为8000的数据即为脏数据,事务B做了一次脏读。



3)不可重复读:在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新了原有的数据。


例如:
  在事务A中,读取到张三的工资为5000,操作没有完成,事务还没提交。
  与此同时,
  事务B把张三的工资改为8000,并提交了事务。
  随后,
  在事务A中,再次读取张三的工资,此时工资变为8000。在一个事务中前后两次读取的结果并不致,导致了不可重复读。

4)第二类丢失更新:不可重复读的特例。有两个并发事务同时读取同一行数据,然后其中一个对它进行修改提交,而另一个也进行了修改提交。这就会造成第一次写操作失效。


例如:

  在事务A中,读取到张三的存款为5000,操作没有完成,事务还没提交。
  与此同时,
  事务B,存储1000,把张三的存款改为6000,并提交了事务。
  随后,
  在事务A中,存储500,把张三的存款改为5500,并提交了事务,这样事务A的更新覆盖了事务B的更新。


5)幻读:是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象发生了幻觉一样。


例如:
  目前工资为5000的员工有10人,事务A读取所有工资为5000的人数为10人。
  此时,
  事务B插入一条工资也为5000的记录。
  这是,事务A再次读取工资为5000的员工,记录为11人。此时产生了幻读。

提醒:


不可重复读的重点是修改,同样的条件,你读取过的数据,再次读取出来发现值不一样
幻读的重点在于新增或者删除,同样的条件,第 1 次和第 2 次读出来的记录数不一样


其次看下MySQL/InnoDB定义的4种隔离级别:


1)Read Uncommitted (读取未提交内容)

这是事务最低的隔离级别,它充许另外一个事务可以看到这个事务未提交的数据。


例如:在事务A中更新了一记录 update t set data = 2 where id = 1;,还没有执行commit,该记录被事务B读走了select data from t where id = 1;,然后事务A rollback,修改的记录作废,那事务B获取的数据就是不正确的了。


此隔离级别只解决第一类丢失更新的问题,但是会出现脏读、不可重复读、第二类丢失更新的问题,幻读 。

本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。


2)Read Committed (RC读取提交内容)

RC是大多数数据库系统的默认隔离级别(但不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。


例如:在事务A中更新了一记录 update t set data = 2 where id = 1;,还没有执行commit,事务B select data from t where id = 1;读走的数据暂不是事务A修改的数据(还是原来的数据)。然后事务A commit,此时事务B获取的数据才是事务A修改的数据。


此隔离级别解决了第一类丢失更新和脏读的问题,但会出现不可重复读、第二类丢失更新的问题,幻读问题。


3)Repeatable Read (RR可重读)

RR是MySQL的InnoDB默认的隔离级别。保证一个事务相同条件下前后两次获取的数据是一致的。


例如:在事务A中更新了一记录 update t set data = 2 where id = 1;,还没有执行commit,事务B select data from t where id = 1;读走的数据暂不是事务A修改的数据(还是原来的数据)。然后事务A commit,此时事务B获取的数据还不是事务A修改的数据。只有事务B结束以后(可以是commit或者rollback),此时执行事务B的session再次获取的数据才是事务A修改的数据。


session指的“会话”的意思,一个客户端与MySQL建立连接等于建了一个会话,在这个会话中可以反反复复开启事务。


可重读以上问题都可以解决的。


4)Serializable (可串行化)

这是最高的隔离级别,最安全并发性也是最差的一种隔离。讲到锁的时候再详细介绍,后面会做演示每种隔离级别的效果。


说明:数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上 “串行化”进行,这显然与“并发”是矛盾的。同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用对“不可重复读”和“幻读”并不敏感,可能更关心数据并发访问的能力。


使用SQL命令:show variables like 'tx_isolation';


1.png


可以查看MySQL正在使用的哪种隔离级别。


和慢查询一样,给MySQL系统变量“tx_isolation”赋值就可以更改隔离级别。