1:事务概述
MySQL 是一个服务器/客户端架构的软件,对于同一个服务器来说,可以有若干个客户端与之连接,每个客户端与服务器连接上之后,就可以称之为一个会话( Session )。我们可以同时在不同的会话里输入各种语句,这些语句可以作为事务的一部分进行处理。不同的会话可以同时发送请求,也就是说服务器可能同时在处理多个事务,这样子就会导致不同的事务可能同时访问到相同的记录。我们前边说过事务有一个特性称之为隔离性,理论上在某个事务对某个数据进行访问时,其他事务应该进行排队,当该事务提交之后,其他事务才可以继续访问这个数据。但是这样子的话对性能影响太大,所以设计数据库的大叔提出了各种隔离级别 ,来最大限度的提升系统并发处理事务的能力,但是这也是以牺牲一定的隔离性来达到的。
事务是数据库最为重要的机制之一,凡是使用过数据库的人,都了解数据库的事务机制,也对ACID四个基本特性如数家珍。但是聊起事务或者ACID的底层实现原理,往往言之不详,不明所以。在MySQL中的事务是由存储引擎实现的,而且支持事务的存储引擎不多,我们主要讲解InnoDB存储引擎中的事务。所以,今天我们就一起来分析和探讨InnoDB的事务机制,希望能建立起对事务底层实现原理的具体了解。
2:ACID四大特性
数据库事务具有ACID四大特性。ACID是以下4个词的缩写:
原子性(atomicity) :事务最小工作单元,要么全成功,要么全失败 。
一致性(consistency): 事务开始和结束后,数据库的完整性不会被破坏 。修改数据之后,关联的数据必须对应的修改。不能出现修改一部分数据的情况
隔离性(isolation) :不同事务之间互不影响,四种隔离级别为RU(读未提交)、RC(读已提交)、RR(可重复读)、SERIALIZABLE (串行化)。 多个事务之间应该是相互隔离互不干扰。干扰程度取决于事务的隔离级别
持久性(durability) :事务提交后,对数据的修改是永久性的,即使系统故障也不会丢失。redo log 的作用。
3:事务的隔离级别
1:未提交读(READ UNCOMMITTED/RU)
当前事务可以读取到其他事务未提交的数据。很少使用。 会出现脏读的问题
脏读:一个事务读取到另一个事务未提交的数据。
如果一个事务读到了另一个未提交事务修改过的数据,那么这种 隔离级别 就称之为 未提交读 (英文名:READ UNCOMMITTED ),示意图如下:
发生时间编号 | Session A | Session B |
① | BEGIN; | |
② | BEGIN; | |
③ | update t set c='关羽' where id =1; | |
④ | select * from t where id=1; (此时读到的列c的值为'关羽') |
Session A 和 Session B 各开启了一个事务, Session B 中的事务先将 id 为 1 的记录的列 c更新为 '关羽' ,然后 Session A 中的事务再去查询这条 id 为 1 的记录,那么在未提交读的隔离级别下,查询结果就是 '关羽' ,也就是说某个事务读到了另一个未提交事务修改过的记录。但是如果Session B 中的事务稍后进行了回滚,那么 Session A 中的事务相当于读到了一个不存在的数据,这种现象就称之为脏读。
发生时间编号 | Session A | Session B |
① | BEGIN; | |
② | BEGIN; | |
③ | update t set c='关羽' where id =1; | |
④ | select * from t where id=1; (此时读到的列c的值为'关羽') | |
⑤ | rollback; |
脏读违背了现实世界的业务含义,所以这种 READ UNCOMMITTED 算是十分不安全的一种 隔离级别 。
2:已提交读(READ COMMITTED/RC)
不可重复读:一个事务因读取到另一个事务 已提交的数据 。导致相同的查询两次以上的查询结果不同。
如果一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值,那么这种 隔离级别 就称之为已提交读( READ COMMITTED ),如图所示:
发生时间编号 | Session A | Session B |
① | BEGIN; | |
② | BEGIN; | |
③ | update t set c='关羽' where id =1; | |
④ | select * from t where id=1; (此时读到的列c的值为'刘备') | |
⑤ | commit; | |
⑥ | select * from t where id=1; (此时读到的列c的值为'关羽') |
从图中可以看到,第4步时,由于 Session B 中的事务尚未提交,所以 Session A 中的事务查询得到的结果只是 '刘备' ,而第6步时,由于 Session B 中的事务已经提交,所以 Session B 中的事务查询得到的结果就是 '关羽' 了。
对于某个处在在 已提交读 隔离级别下的事务来说,只要其他事务修改了某个数据的值,并且之后提交了,那么该事务就会读到该数据的最新值,比方说:
发生时间编号 | Session A | Session B |
① | BEGIN; | |
② | select * from t where id=1; (此时读到的列c的值为'刘备') | |
③ | update t set c='关羽' where id =1; (隐式提交) | |
④ | select * from t where id=1; (此时读到的列c的值为'关羽') | |
⑤ | update t set c='张飞' where id =1; (隐式提交) | |
⑥ | select * from t where id=1; (此时读到的列c的值为'张飞') |
我们在 Session B 中提交了几个隐式事务,这些事务都修改了 id 为 1 的记录的列c的值,每次事务提交之后, Session A 中的事务都可以查看到最新的值。这种现象也被称之为不可重复读。
3:可重复读(REPEATABLE READ/RR)
幻读:一个事务因读取到另一个事务 已提交的insert数据 。导致对同一张表读取两次以上的结果不一致。
在一些业务场景中,一个事务只能读到另一个已经提交的事务修改过的数据,但是第一次读过某条记录后,即使其他事务修改了该记录的值并且提交,该事务之后再读该条记录时,读到的仍是第一次读到的值,而不是每次都读到不同的数据。那么这种 隔离级别 就称之为 可重复读 (英文名: REPEATABLE READ ),如图所示:
发生时间编号 | Session A | Session B |
① | BEGIN; | |
② | select * from t where id=1; (此时读到的列c的值为'刘备') | |
③ | update t set c='关羽' where id =1; (隐式提交) | |
④ | select * from t where id=1; (此时读到的列c的值为'刘备') | |
⑤ | update t set c='张飞' where id =1; (隐式提交) | |
⑥ | select * from t where id=1; (此时读到的列c的值为'刘备') |
从图中可以看出来, Session A 中的事务在第一次读取 id 为 1 的记录时,列 c 的值为 '刘备' ,之后虽然 Session B 中隐式提交了多个事务,每个事务都修改了这条记录,但是 Session A 中的事务读到的列 c 的值仍为 '刘备' ,与第一次读取的值是相同的。
4:串行化(SERIALIZABLE)
以上3种隔离级别都允许对同一条记录进行 读-读 、 读-写 、 写-读 的并发操作,如果我们不允许 读-写 、 写-读 的并发操作,可以使用 SERIALIZABLE 隔离级别,示意图如下:
发生时间编号 | Session A | Session B |
① | BEGIN; | |
② | BEGIN; | |
③ | update t set c='关羽' where id =1; | |
④ | select * from t where id=1; (等待中...) | |
⑤ | commit | |
⑥ | select * from t where id=1; (此时读到的列c的值为'关羽') |
如图所示,当 Session B 中的事务更新了 id 为 1 的记录后,之后 Session A 中的事务再去访问这条记录时就被卡住了,直到 Session B 中的事务提交之后, Session A 中的事务才可以获取到查询结果。
4:设置当前会话的事务隔离级别
//查看当前事务级别:
SELECT @@tx_isolation;
//设置read uncommitted级别:
set session transaction isolation level read uncommitted;
//设置read committed级别:
set session transaction isolation level read committed;
//设置repeatable read级别:
set session transaction isolation level repeatable read;
//设置serializable级别:
set session transaction isolation level serializable;