MySQL数据库事务剖析
事务就是一组原子性的SQL查询,是一个独立的执行单元。事务内的语句,要么全部执行成功,要么全部执行失败。
1、事务的标准特征
一个运行良好的事务处理系统,必须具备原子性、一致性、隔离性、持久性。
原子性:一个事务必须被视为一个不可分割的最小工作单元,这个事务的所有操作要么全部提交成功,要么全部失败回滚。
一致性:数据库总是从一个一致性的状态转换到另外一个一致性状态。比如在一个事务中执行一组sql,其中一个执行失败,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。
隔离性:通常来说,一个事务所做的修改在最终提交之前,对其他事务是不可见的。
持久性:一旦事务提交,则其所做的修改就会永久保存到数据库中。
2、隔离级别
在SQL标准中定义了四种隔离级别,每一种级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。
未提交读:
在此级别中,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读。
提交读:
此级别是大多数数据库系统的默认隔离级别。此级别满足前面提到的隔离性的简单定义:一个事务开始时,只能看见已经提交的事务所做的修改。换言之,一个事务从开始直到提交之前,所做的任何修改对其他事务都是不可见的。这个级别有时候也叫做不可重复读。比如:A事务第一次读取数据,然后B事务更改该数据并提交,A事务再次读取数据,两次读取的数据不一样。
可重复读:
此级别是MySQL的默认级别。基于提升并发性能的考虑,MySQL的InnoDB存储引擎实现的都不是简单的行级锁,而是多版本并发控制(MVCC),MVCC可以被认为是行级锁的变种,但是它却避免了很多情况下的加锁操作,因此开销更低。MVCC 的实现是通过保存数据在某个时l同点的快照来实现的。InnoDB的MVCC是通过在每行记录后面保存两个隐藏 的列来实现的。这两个列,一个保存了行的创建时间 ,一个保存行的过期时间 (或删除时间)。有赖于MVCC,同一个事务中的查询只能返回版本号不高于当前事务版本的数据,即事务只能看到该事务开始前或者被该事物影响的数据。即防止了不可重复读的发生。
依靠上面的机制,已经做到了在事务内数据内容的不变,但是不能保证多次查询得到的数据数量一致。因为在一个事务执行的过程中别的事务完全可以执行数据插入,当插入了刚好符合查询条件的数据时,就会引发数据查询结果集增加,引发幻读。InnoDB提供的间隙锁机制可以在一定程度上防止幻读的发生。
可串行化:
此级别是最高的隔离级别,它通过强制事务串行执行,避免了前面所说的幻读的问题。简单地说,可串行化会在读取地每一行数据上加锁,所以可能导致大量的超时和锁争用的问题。实际应用中很少用到此级别。
3、死锁
死锁是两个或者多个事务在同一个资源上互相占用,并请求锁定对方占用的资源,从而导致恶性循坏的过程。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。如下所示:
事务A:
事务B:
如果两个事务都执行了第一条update语句,更新了一行数据,同时锁定了该行数据,然后这两个事务又尝试去执行第二个update语句,但是发现该行数据已经被对方锁定,然后都等待对方释放锁,同时持有对方需要的锁,则陷入死循环。死锁发生以后只有部分或完全回滚一个事务才能打破死锁。
为解决死锁,数据库往往会实现各种死锁检测和死锁超时机制。
4、事务日志
事务日志可以提高事务的效率。使用事务日志,存储引擎在修改表的数据时,只需要修改其内存拷贝,再把该修改行为持久化到磁盘上的事务日志中,而不需要每次都将数据持久到磁盘。事务日志采用追加的方式,写日志的操作是磁盘上一小块区域内的顺序IO,而不像随机IO需要在磁盘的多个地方移动磁头。事务日志持久化以后,内存中被修改的数据被慢慢刷回磁盘。这种方式称为预写式日志,Innodb通过此方式来保证事务的完整性。
如果数据的修改被持久化到事务日志,而数据本身还没有被写回磁盘,此时系统崩溃,则存储引擎存重启以后会自动恢复这部分被修改的数据。
Innodb事务日志包括:重做日志redo和回滚日志undo。
Redo记录的是已经全部完成的事务,就是执行了commit的事务,记录文件是ib_logfile0 ib_logfile1。Undo记录的是已部分完成并且写入硬盘的未完成的事务,默认情况下回滚日志是记录下表空间中的。
一般情况下,mysql在崩溃之后,重启服务,innodb通过回滚日志undo将所有已完成并写入磁盘的未完成事务进行rollback,然后redo中的事务全部重新执行一遍即可恢复数据。
5、MySQL事务处理的方法
(1)、用begin,rollback,commit来实现
begin 开始一个事务
rollback 事务回滚
commit 事务确认
如下图所示:
(2)、默认采用自动提交模式
如果不是显式的开启一个事务,则每个查询都当作一个事务执行提交操作。在当前连接中可以通过设置variables来启用或者禁用自动提交模式。
查看当前系统的自动提交是否开启: show variables like "autocommit%";
如上图所示:1或者ON表示启用,0或者OFF表示禁用。
我们可以通过
set autocommit=0 禁止自动提交
set autocommit=1 开启自动提交
来实现事务的处理。
如下图所示: