MySQL (事务篇)

一、事务的概念

事务(transaction)是一个操作序列,这些操作要么全部执行,要么全部不执行。

事务通常以BEGIN TRANSACTION 开始,以 COMMIT 或 ROLLBACK 操作结束。

二、事务的特性

1、原子性(atomicity)

指事务在逻辑上是不可分割的操作单元,其所有语句要么都执行,要么都撤销执行。

举例:

假设有两个账户,A账户和B账户。A账户转给B账户100元,这里有两个动作在里面:A账户减去100元,B账户增加100元,这两个动作不可分割,即原子性。

2、一致性(consistency)

事务是一种逻辑上的工作单元。一个事务就是一系列在逻辑上相关的操作指令的集合,用于完成一项任务,其本质是将数据库中的数据从一种一致性状态转换到另一种一致性状态。

举例:

假设A账户和B账户两者的钱加一起一共是5000元,那么不管A账户和B账户之间如何转账,转几次账,事务结束后,两个用户的钱加起来应该还得是5000元,这就是事务的一致性。

3、隔离性(isolation)

隔离是针对并发事务而言的,所谓并发是指数据库服务器同时处理多个事务,如果不采取专门的控制机制,那么并发事务之间可能会相互干扰,进而导致数据出现不一致或错误的状态。

隔离性就是隔离并发运行的多个事务间的相互影响。关于事务的隔离性,数据库提供了多种隔离级别,各有优缺点,下面第三节会说到。

隔离性就是要达到这么一种效果:对于任意两个并发的事务T1、T2,在事务T1看来,T2要么在T1开始之前就结束,要么在T1结束后才开始。这样每个事务都感觉不到有其他事务在并发的执行。

4、持久性

指一旦事务提交成功,其对数据的修改就是持久性的。

数据更新的结果已经从内存转到外部存储器上,此后即使发生了系统故障,已提交的事务所做的数据更新也不会丢失。

三、如果不考虑事务的隔离,可能会发生什么?

1、脏读(dirty read)

一个事务读取了已被另一个事务修改、但尚未提交的数据。

当一个事务正在多次修改某个数据,而这个事务中这多次的修改都还未提交,这时,另一个并发的事务来访问该数据时,就会造成两个事务得到的数据不一致

举例:

用户A向用户B转账1000元,对应SQL命令:

update account set money = money + 1000 where name = ‘B’;

update account set money = money - 1000 where name = ‘A’;

当只执行成功了第一条SQL时,整个事务还未都执行成功,也没有提交事务,这时另一个并发事务来查询B账户的钱,得到的结果就是B账户增加了1000元之后的结果。

这个时候就形成了脏读,无论第二条SQL是否执行,只要该事务不提交,那么所有操作都将回滚,这时也就意味着之前另一个并发事务查询的结果是不正确的。

2、不可重复读(nonrepeatable read)

在同一事务中,同一个查询在TIme1时刻读取某一行,在Time2时刻重新读取这一行的数据时,发现这个行的数据已经发生修改,可能被更新了(UPDATE),也可能被删除了(DELETE)。

举例:

事务T1在读取某一数据,而事务T2立即修改了这个数据,并提交事务给数据库,事务T1再次读取该数据就得到了不同的结果,发生了不可重复读。

3、幻读(phantom read)

在同一事务中,当同一查询多次执行的时候,由于其他插入(insert)操作的事务提交,会导致每次返回不同的结果集。

举例:

事务T1对一个表中的所有行的某个字段执行了从1修改到2的操作,这时事务T2又在这个表中插入了一行数据,而这行数据的那个字段值还是1,并且提交给数据库。

这时,操作事务T1的用户如果在查看刚刚修改的数据,就会发现还有一条没有修改,就好像产生幻觉一样,这就是发生了幻读(其实这行数据是另一个事务T2新添加的)

四、脏读与不可重复读的区别、幻读与不可重复读的区别

1、脏读和不可重复读的区别

脏读是某一事务读取了另一个事务未提交的脏数据;

不可重复读是在同一个事务内,多次查询同一条数据,得到了不同的结果,原因是该条数据在查询间隔期间被另一个事务修改并提交了

2、幻读和不可重复读的区别

幻读和不可重复读都是读取了其他事务提交的数据,不同之处在于,不可重复读查询的是一条数据,而幻读针对的是一个数据整体。

五、事务的四种隔离级别

1、未提交读(read uncommitted)

在该隔离级别,所有事务都可以看到其他未提交事务的处理结果,即在未提交读级别,事物中的修改,即使没有提交,对其他事务也是可见的。

该隔离级别很少用于实际应用,隔离级别最低,并发性最高,但会出现脏读、不可重复读、幻读三种问题。

2、提交读(read committed)

它满足了隔离的基本定义:一个事务只能看见已经提交事务所做的改变。

换句话说,一个事务从开始到提交之前,所做的任何修改对其他事务都是不可见的。

它是Oracle、SQL Server数据库系统的默认隔离级别。不会出现脏读问题,但会出现不可重复读和幻读问题

3、可重复读(repeatable read)

可重复读可以确保同一个事务,在多次读取同样的数据时,得到同样的结果。

可重复读是MySQL数据库系统的默认隔离级别。它不会出现脏读和不可重复读问题,但仍会出现幻读问题。

MySQL数据库中的InnoDB和Falcon存储引擎通过MVCC(multi-version concurrent control 多版本并发控制)解决了不可重复读问题,而 MVCC 加上 间隙锁又解决了幻读问题。

4、可串行化、序列化(serializable)

这是最高的隔离级别,它通过强制事务排序,强制事务串行执行。使之不可能相互冲突。

它不会出现脏读、不可重复读和幻读问题。但是可能会出现大量的超时现象和锁竞争。

实际应用中很少用到这个级别。只有在非常需要数据的一致性,且接受无并发的情况下,才考虑用该级别。

六、关于MySQL事务的几个问题

1、事务内,存在两条sql语句,第一条执行成功了,第二条执行失败了,commit之后,什么结果?

结果:第一条执行成功,第二条执行失败,不进行回滚

注意:MySQL 中的回滚,必须人为去做,在PHP 中,我们常常使用 try catch 来进行事务的执行和回滚

考察点:对 MySQL 事务应用的熟练程度

2、在事务执行过程中,如果服务器突然挂掉,MySQL 处理过程及结果?

正常执行事务时,过程是

try {
    开启事务
    执行sql
    提交事务
} catch (\Exception $e) {
    回滚
}

当开始事务后,每执行一条sql语句,都会对应一条相反的sql语句(insert 对应 delete,update 对应相反 update)写入 undo.log(回滚日志中)。

当事务执行完毕后,会写入一个checkpoint(检查点)到 undo.log 日志中,MySQL 在下次执行事务或者重启MySQL,只会检查最近的checkpoint后面的,checkpoint 之前的说明已经执行成功不需要回滚了。

如果执行过程中,服务器突然挂掉,也就是说,执行了部分sql,不确定提没提交,那么这个时候,重启MySQL 时,MySQL 会检查 undo.log,如果最近的检查点后面有内容,则说明有部分sql执行了,但是没有提交,这时,MySQL 会对数据进行回滚。

并执行redo.log内的sql,进行重做

考察点:MySQL 事务的执行过程,内部是如何实现的

3、MySQL 事务执行流程图