什么是数据库事务
事务是逻辑上的一组数据库操作,要么都执行,要么都不执行。
事务的四大特性(ACID)
- 原子性(A):要么成功,要么失败
- 一致性©:不能破坏数据完整性,数据不会因过程凭空消失
- 隔离性(I):一个事物的执行不能受其它事务干扰
- 持久性(D):事务一旦提交,修改是永久的
事务之间的相互影响
事务之间的相互影响分为几种,分别为:脏读,不可重复读,幻读,丢失更新
脏读
脏读:一个事务读取了另一个事务改写但还未提交的数据
不可重复读
如果事务1在事务2的更新操作之前读取一次数据,在事务2的更新操作之后再读取同一笔数据一次,两次结果是不同的。
不可重复读:同一个事务中,多次读取同一数据返回结果不同
(内容的变化)
==解决方案:==不可重复读出现的原因就是事务并发修改记录,要避免这种情况,最简单的方法就是对要修改的记录加锁,这回导致锁竞争加剧,影响性能。
幻读
事例:程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉。
幻读:一个事务读取了几行记录后,另一个事务插入一些记录,发现有些原来没有的记录
(条数的变化)
==解决方案:==幻读是由于并发事务增加记录导致的,这个不能像不可重复读通过记录加锁解决,因为对于新增的记录根本无法加锁。需要将事务串行化,才能避免幻读。
隔离级别
数据库常见默认隔离级别
- Oracle:读已提交
- Postgresql:读已提交
- SqlServer:读已提交
- Mysql:可重复读
注解
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
读取未提交数据(会出现脏读, 不可重复读) 基本不使用
@Transactional(isolation = Isolation.READ_COMMITTED)
读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ)
可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE)
串行化
锁类型简述
共享锁(S锁):假设事务T1对数据A加上共享锁,那么事务T2可以读数据A,不能修改数据A。
排他锁(X锁):假设事务T1对数据A加上共享锁,那么事务T2不能读数据A,不能修改数据A。
读未提交
Read Uncommitted:最低的隔离级别,什么都不需要做,一个事务可以读到另一个事务未提交的结果。所有的并发事务问题都会发生。
原理:
- 事务对当前读取的数据不加锁;
- 事务对数据更新前添加 行级共享锁,直到事务结束才释放。
可能发生的情况: - 事务1读取某些数据记录时,事务2也能对这些记录进行读取、更新;当事务2对这些记录进行更新时,事务1再次读取记录,能读到事务2对该记录的修改版本,即使更新尚未提交。
- 事务1更新某些数据记录时,事务2不能对这行记录做更新,直到事务1结束。
简单地理解就是: - 允许事务同时读数据
- 允许一个事务读取数据同时另外一个事务修改数据
- 必须等更新数据的事务执行完成后,才能对执行其他的读取或者修改该数据的事务
读已提交
Read Committed:只有在事务提交后,其更新结果才会被其他事务看见。可以解决脏读问题。
原理:
- 事务对当前被读取的数据加 行级共享锁(当读到时才加锁),一旦读完该行,立即释放该行级共享锁;
- 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。
可能发生的情况: - 事务1读取某行记录时,事务2也能对这行记录进行读取、更新;当事务2对该记录进行更新时,事务1再次读取该记录,读到的只能是事务2对其更新前的版本,要不就是事务2提交后的版本。
- 事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。
简单地理解就是: - 允许事务同时读数据
- 必须一个事务读取完数据后,另外一个事务才能修改该数据
- 必须等更新数据的事务执行完成后,才能对执行其他的读取或者修改该数据的事务
可重复读
Repeated Read:在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交。可以解决脏读、不可重复读。
原理:
- 事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加 行级共享锁,直到事务结束才释放;
- 事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。
可能发生的情况:
- 事务1读取某行记录时,事务2也能对这行记录进行读取、更新;当事务2对该记录进行更新时,事务1再次读取该记录,读到的仍然是第一次读取的那个版本。
- 事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。
简单地理解就是:
- 允许事务同时读数据
- 必须等读取数据的事务执行完成后,才能对执行其他的修改该数据的事务
- 必须等更新数据的事务执行完成后,才能对执行其他的读取或者修改该数据的事务
可序列化
Serialization:事务串行化执行,隔离级别最高,牺牲了系统的并发性。可以解决并发事务的所有问题。
事务在读取某数据的瞬间(就是开始读取的瞬间),必须先对其加 行级共享锁,直到事务结束才释放;
事务在更新某数据的瞬间(就是发生更新的瞬间),必须先对其加 行级排他锁,直到事务结束才释放。
将两行记录间的空隙加上锁,阻止新记录的插入;这个锁称为间隙锁。
间隙锁与间隙锁之间没有冲突关系。跟间隙锁存在冲突关系的,是往这个间隙中插入一个记录这个操作。
可能发生的情况:
事务1读取某行记录时,事务2也能对这行记录进行读取、更新;当事务2对该记录进行更新时,事务1再次读取该记录,读到的仍然是第一次读取的那个版本。
事务1更新某行记录时,事务2不能对这行记录做更新,直到事务1结束。
简单地理解就是:
- 所有的事务必须等上一个事务执行完成后才开始执行
解释说明:
一个事务开启到结束可人为控制,比如后端方法加注解,调用多个相同方法以及不同方法查询数据库都数据在本事务内,读已提交当执行一个查询方法时加行级锁,执行为这个查询方法行级锁消失,这时可能有其它事务更新该条数据,当再次执行这个查询方法查询该条数据查询到了其它事务操作后的数据。
而可重复读,是操作查询方法时就加了行级锁直到事务中所有操作数据库的方法结束,才取消行级锁这样其它事务在该过程中只有查询权限没有修改权限。
注意:
- 这里需要注意的是:Mysql 默认采用的 REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别
- 事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。
- 因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是- InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)**并不会有任何性能损失。
- InnoDB 存储引擎在 分布式事务 的情况下一般会用到**SERIALIZABLE(可串行化)**隔离级别。