什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务最经典也经常被拿出来说例子就是转账了。假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
事物的四大特性
原子性
atomicity
又叫不可分割性,事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用; 整个事务中的所有操作要么全部执行成功,要么全部执行失败后回滚到最初的状态。
一致性
consistency
执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的; 数据库中的数据在事务操作前和事务处理后必须都满足业务规则约束,比如A和B的账户总金额在转账前和转账后必须保持一致;
隔离性
isolation
又叫独立性,并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的; 一个事务在提交之前所做出的操作是否能为其他事务可见。由于不同的场景需求不同,因此针对隔离性来说有不同的隔离机制;
持久性
durability
一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
事务相关语句
BEGIN; # 开启事务
START TRANSACTION; # 开启事务
COMMIT; # 提交事务
ROLLBACK; # 回滚事务
隔离级别
事务的隔离级别都是通过锁机制来实现的,锁在计算机中是协调多个进程或线程来并发访问某一资源的一种机制。
并发事务带来的问题
脏读
Dirty Read
当前事务能够看到别的事务中未提交的数据;
当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。 因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的;
解决办法:如果在第一个事务提交之前,任何其他事务不可读取其他修改过的值,则可避免该问题。
丢失修改
Lost to modify
一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。
不可重复读
Non-repeatable Reads
一个事务对同一行数据重复读取两次,但是却得到了不同的结果;
一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读;
解决办法:如果只有在修改事务完全提交之后才可以读取数据,则可以避免该问题。
幻读
Phantom read
在其中一个事务中读取到了其他事务新增的数据,仿佛出现了幻影现象;
幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读;
解决办法:在操作事务完成数据处理之前任何其他事务都不可以添加新数据,则可避免该问题。
不可重复读与幻读的区别就是,不可重复读的重点是修改,幻读的重点在于新增或删除。
四种隔离级别
为了解决以上并发事务带来的问题,数据库提供了四种隔离级别,由低到高依次是为:
读未提交
read uncommitted
最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
并发性能是最强的,但隔离性和安全性是最差的,生产环境中不使用。
读已提交
read committed
允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
读取数据的事务允许其他事务读,但未提交的写事务禁止其他事务读。
可重复读取
repeatable read
对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以组织脏读和不可重复读,但幻读仍有可能发生。
第一个事物还没有结束,另一事务也访问同一数据,那么第一个事务中的两次读数据之间,即使第二个事务对数据进行修改,第一个事务两次读取到的数据也是一样的,因此称为可重复读。
读取数据的事务禁止写事务(但允许读事务),写事务则禁止任何其他事务。
序列化
serializable
最高的隔离级别,完全服从ACID的隔离级别。
所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
仅仅通过“行级锁”是无法实现事务的序列化的,必须通过其他机制保证新插入的数据不会被刚执行的查询操作的事务访问到。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
Read-uncommitted 读未提交 | ✔️ | ✔️ | ✔️ |
Read-committed 读已提交 | × | ✔️ | ✔️ |
Repeatable-Read 可重复读取 | × | × | ✔️ |
Serializable 序列化 | × | × | × |
- 隔离级别越高,越能保证数据的完整性和一致性,但对并发性能影响也越大。
- MySQL的默认隔离级别是Repeatable-Read“可重复读”
- 对于多数应用程序,可以优先考虑把数据库隔离级别设置为 Read committed“读已提交”,它能够避免脏读,尽管会导致不可重复读、幻读这些并发问题,但是可以在可能出现类似问题的场合采用悲观锁或乐观锁来控制。
- 查询当前设置的隔离级别:
show variables like 'transaction_isolation'
其他
Q:可重复读当中,已提交数据修改的事务1,事务2在事务1修改提交前后读取到的同一条数据是不发生改变的。我们都知道事务隔离性是通过锁来实现的,那么当事务1执行更新语句时,应该对数据增加了排他锁,但是事务2依然可以有读操作?
A:这是因为InnoDB为了提高数据库并发性,采用了“一致性非锁定读”的机制。
一致性非锁定读表示如果当前行被施加了排它锁,那么当需要读取行数据时,则不会等待行上锁的释放,而是会去读一个快照的数据。
之所以称为非锁定读,是因为它不需要等待被访问的行上排它锁的释放。
其实快照版本就是改行对应的之前版本已提交的数据,即历史数据,快照的真实实现是由事务日志所对应的undo段来完成的