什么是事务?
事务就是一组原子性的 SQL 语句,如果该组中的 SQL 有一条执行失败,那么所有的语句都不会执行。事务内的语句要么全部执行成功,要么全部执行失败。
以经典的银行转账为例:
假设银行的数据库有两张表:支票(checking)表和存储(savings)表,现在要从用户 A 的支票账户转移 200 元至她的存储账户,那么需要进行以下步骤:
检查支票账户的余额高于 200从支票账户余额中减去 200在存储账户余额中增加 200
假如执行完步骤 2,服务器崩溃导致步骤 3 没有执行,用户将会损失 200。
假如执行完步骤 2,另外一个进程要清除支票账户余额,银行将会白送 200。
所以,以上三个步骤的操作必须打包在一个事务中,不然就可能造成数据不一致的情况。
ACID特性
事务具有四个特性:原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),简称为事务的ACID特性。
原子性
一个事务是一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。
一致性
数据库总是从一个一致性状态转换到另一个一致性状态。事务的执行不能破坏数据库数据的完整性和一致性。
隔离性
通常来说,一个事务所做的修改在最终提交以前,对其它事务是不可见的。但是出于对性能的考虑,实际上数据库并不会完全隔离,而是通过选择不同的隔离级别在可见性与性能之间进行取舍。
持久性
一旦事务提交,则其所做的修改会永久地保存到数据库中。此时,即使系统崩溃,修改的数据也不会丢失。
事务隔离级别
SQL 标准定义了四种隔离级别,每一种隔离级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。
读未提交(Read Uncommitted)
隔离级别最低,存在脏读,脏读会导致很多问题,实际应用中很少使用。
脏读(Dirty Read):一个事务中做了修改操作,但事务尚未提交,其它事务可以查询到修改后的数据
例:
账户A,资金余额100,事务1执行转账,从账户B转账100至账户A,事务2使用账户A执行购买150元的货物
事务1:执行转账操作,账户A资金+100,执行成功,账户余额200,事务尚未提交。
事务2:执行购买操作,检查账户资金,账户余额为200,判断可以购买,执行扣款-150,账户余额50,提交事务
事务1:执行转账操作,账户B资金-100,提交事务。
脏读发生在步骤二,事务2读取数据时,可以读取事务1尚未提交的数据变更。如果所有步骤都执行成功,最终数据还是可以保持一致性的。但是,如果步骤三执行失败,则步骤一会回滚,这样账户A在余额不够的情况下也购买成功。
读已提交(Read Committed)
解决了脏读问题,是大多数关系型数据库的默认事务隔离级别,存在不可重复读问题。
不可重复读(Nonrepeatable Read):在同一个事务中,前后执行两次同样的查询,可能会得到不一样的结果
例:
账户A,资金余额100,事务1执行资金检查,事务2使用账户A执行购买50元的货物
事务1:执行读取操作,账户A资金100,事务尚未提交。
事务2:执行购买操作,账户A资金-50,操作成功,账户A资金余额被修改为50。
事务1:执行读取操作,账户A资金50。
在事务1中,两次查询同一个数据,可能会得到不一致的结果。
可重复读(Repeatable Read)
解决了脏读及不可重复读的问题,MySQL的默认事务隔离级别,理论上存在幻读的问题。
幻读(Phantom Read):某个事务读取某个范围内的记录时,另外一个事务在该范围内插入了新的记录,之前的事务再次读取该范围的记录时,会产生幻行。
InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC,Multi Version Concurrency Control)解决了幻读问题。
例:
执行用户注册操作,用户名为唯一键
事务1:执行读取操作,检查库中是否有用户名为‘aaa’的用户,结果显示没有,事务尚未提交。
事务2:执行插入操作,插入用户名为‘aaa’的用户数据。
事务1:执行插入操作,插入用户名为‘aaa’的用户数据,提示唯一键错误。
对于事务1而言,校验通过的情况下,执行插入却提示唯一键错误,如坠梦幻之中。
串行化(Serializable)
最高的事务隔离级别,在读取的每一行数据上都加锁,可能导致大量的超时和锁竞争问题,实际应用中很少使用。除非在必须确保数据一致性,并且可以接受没有并发的情况下,才考虑使用。
结束语
只有串行化隔离级别才能完全保障事务ACID特性,之所以出现隔离级别都是基于性能考虑,因串行化的并发处理能力不能满足实际生产需求。
领取专属 10元无门槛券
私享最新 技术干货