事务的问题,是经常的问题。
基本概念:
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言(如SQL,C++或Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
定义数据库事务的逻辑:数据库事务是构成单一逻辑工作单元的操作集合
BEGIN TRANSACTION //事务开始
SQL1
SQL2
COMMIT/ROLLBACK //事务提交或回滚
关于事务的定义进一步解释如下:
-
1.数据库事务可以包含一个或多个数据库操作,但这些操作构成一个逻辑上的整体。
-
2.构成逻辑整体的这些数据库操作,要么全部执行成功,要么全部不执行。
-
3.构成事务的所有操作,要么全都对数据库产生影响,要么全都不产生影响,即不管事务是否执行成功,数据库总能保持一致性状态。
-
4.以上即使在数据库出现故障以及并发事务存在的情况下依然成立。
事务的ACID特性
A atomicity
事务中的所有操作作为一个整体像原子一样不可分割,要么全部成功,要么全部失败。
C consistency
事务的执行结果必须使数据库从一个一致性状态到另一个一致性状态。一致性状态是指:
1.系统的状态满足数据的完整性约束(主码,参照完整性,check约束等)
2.系统的状态反应数据库本应描述的现实世界的真实状态,比如转账前后两个账户的金额总和应该保持不变。
I Isolation
并发执行的事务不会相互影响,其对数据库的影响和它们串行执行时一样。
比如多个用户同时往一个账户转账,最后账户的结果应该和他们按先后次序转账的结果一样。
D Durability
事务一旦提交,其对数据库的更新就是持久的。任何食物或者系统故障都不会导致数据的丢失。
在事物的ACID四大特性中,最核心、最根本的特性就是Consistency. 而对数据的一致性的破坏主要来自于两个方面:
1. 事物的并发执行 2. 事务故障或者系统故障
数据库系统是根据并发控制技术和日志恢复技术来避免这种情况发生的。
**并发控制技术 **保证了事务的隔离性,使数据库的一致性状态不会因为并发执行的操作被破坏。
日志恢复技术 保证了事务的原子性,使一致性状态不会因事务或系统故障被破坏。同时使已提交的对数据库的修改不会因系统崩溃而丢失,保证了事务的持久性。
事务的隔离级别:
事务的隔离级别:
-
读未提交 read uncommitted
如果一个事务已经开始写数据,则另外一个事务不允许同时进行写操作,但允许其他事务读此行数据,该隔离级别可以通过“排他写锁”,但是不排斥读线程实现。
这样就避免了更新丢失,却可能出现脏读,也就是说事务B读取到了事务A未提交的数据。
例1: 老板要给程序员发工资,程序员的工资是3.6万/月。但是发工资时老板不小心按错了数字,按成3.9万/月,该钱已经打到程序员的户口,但是事务还没有提交,就在这时,程序员去查看自己这个月的工资,发现比往常多了3千元,以为涨工资了非常高兴。但是老板及时发现了不对,马上回滚差点就提交了的事务,将数字改成3.6万再提交。 分析:实际程序员这个月的工资还是3.6万,但是程序员看到的是3.9万。他看到的是老板还没提交事务时的数据。这就是脏读。 -
读已提交 read committed(解决了)
一个事务要等另一个事务提交后才能读取数据。
如果是一个读事务(线程),则允许其他事务读写,如果是写事务将会禁止其他事务访问该行数据,该隔离级别避免了脏读,但是可能出现不可重复读。事务A事先读取了数据,事务B紧接着更新了数据,并提交了事务,而事务A再次读取该数据时,数据已经发生了改变。
解决了更新丢失和脏读问题 。(这里的读已提交就是 在进行一个读事务时,出现了其他的写事务对所读内容的修改,导致出现前后读数据不一致的情况)
例2: **事例:**程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(程序员事务开启),收费系统事先检测到他的卡里有3.6万,就在这个时候!!程序员的妻子要把钱全部转出充当家用,并提交。当收费系统准备扣款时,再检测卡里的金额,发现已经没钱了(第二次检测金额当然要等待妻子转出金额事务提交完)。程序员就会很郁闷,明明卡里是有钱的… **分析:**这就是读提交,若有事务对数据进行更新(UPDATE)操作时,读操作事务要等待这个更新操作事务提交后才能读取数据,可以解决脏读问题。但在这个事例中,出现了一个事务范围内两个相同的查询却返回了不同数据,这就是不可重复读。 -
可重复读 repeatable read(解决了重复读)
重复读,就是在开始读取数据(事务开启)时,不再允许修改操作 。
可重复读取是指在一个事务内,多次读同一个数据,在这个事务还没结束时,其他事务不能访问该数据(包括了读写),这样就可以在同一个事务内两次读到的数据是一样的,因此称为是可重复读隔离级别,读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务(包括了读写),这样避免了不可重复读和脏读,但是有时可能会出现幻读。(读取数据的事务)可以通过“共享读镜”和“排他写锁”实现。
例3: **事例:**程序员拿着信用卡去享受生活(卡里当然是只有3.6万),当他埋单时(事务开启,不允许其他事务的UPDATE修改操作),收费系统事先检测到他的卡里有3.6万。这个时候他的妻子不能转出金额了。接下来收费系统就可以扣款了。 **分析:**重复读可以解决不可重复读问题。写到这里,应该明白的一点就是,不可重复读对应的是修改,即UPDATE操作。但是可能还会有幻读问题。因为幻读问题对应的是插入INSERT操作,而不是UPDATE操作。 -
串行化 serializable 解决幻读(所有的问题)
提供严格的事务隔离,它要求事务序列化执行,事务只能一个接着一个地执行,但不能并发执行,如果仅仅通过“行级锁”是无法实现序列化的,必须通过其他机制保证新插入的数据不会被执行查询操作的事务访问到。序列化是最高的事务隔离级别,同时代价也是最高的,性能很低,一般很少使用,在该级别下,事务顺序执行,不仅可以避免脏读、不可重复读,还避免了幻读.
例4: **事例:**程序员某一天去消费,花了2千元,然后他的妻子去查看他今天的消费记录(全表扫描FTS,妻子事务开启),看到确实是花了2千元,就在这个时候,程序员花了1万买了一部电脑,即新增INSERT了一条消费记录,并提交。当妻子打印程序员的消费记录清单时(妻子事务提交),发现花了1.2万元,似乎出现了幻觉,这就是幻读。 分析: 在读写线程中,出现“新增INSERT”之类的新的,不同的线程。
可能导致的并发异常:
- 脏写
- 脏读
- 不可重复读
- 幻读
- 丢失更新
具体对应的表格:
@Transactional 注解的属性信息 的细节:
- 数据库的事物的注解属性信息
属性 | 描述 |
---|---|
name | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器 |
propagation | 事务的传播行为,默认值为 REQUIRED |
isolation | 事务的隔离度,默认值采用 DEFAULT |
timeout | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务 |
read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true |
rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔 |
no-rollback- for | 抛出 no-rollback-for 指定的异常类型,不回滚事务 |
- propagation 传播行为
- REQUIRED:如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
- NOT_SUPPORTED:容器不为这个方法开启事务
- REQUIRES_NEW:不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
- MANDATORY:必须在一个已有的事务中执行,否则抛出异常
- NEVER:必须在一个没有的事务中执行,否则抛出异常(与MANDATORY相反)
- SUPPORTS:如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.
- NESTED: 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
-
事物超时设置
@Transactional(timeout=30) //默认是30秒 -
事务隔离级别 isolation
- READ_UNCOMMITTED:读取未提交数据(会出现脏读, 不可重复读) 基本不使用
- READ_COMMITTED:读取已提交数据(会出现不可重复读和幻读)
- REPEATABLE_READ:可重复读(会出现幻读)
- SERIALIZABLE:串行化
注意:
- @Transactional 只能被应用到public方法上;仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。
- REQUIRED:如果存在一个事务,则支持当前事务。如果没有事务则开启一个新的事务。
- REPEATABLE_READ:这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。
- readOnly:不允许只读 rollbackFor:回滚策略为Exception出现异常之后。
- TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 函数内捕获异常时需要来设置事务回滚状态。
Spring Transactional一直是RD的事务神器,但是如果用不好,反会伤了自己。 @Transactional经常遇到的几个场景:
- @Transactional 加于private方法, 无效;
- @Transactional 加于未加入接口的public方法, 再通过普通接口方法调用, 无效;
- @Transactional 加于接口方法, 无论下面调用的是private或public方法, 都有效;
- @Transactional 加于接口方法后, 被本类普通接口方法直接调用, 无效;
- @Transactional 加于接口方法后, 被本类普通接口方法通过接口调用, 有效;
- @Transactional 加于接口方法后, 被它类的接口方法调用, 有效;
- @Transactional 加于接口方法后, 被它类的私有方法调用后, 有效。
对于事务隔离性的实现:
https://www.cnblogs.com/takumicx/p/9998844.html
对于事务注解的原理:
https://blog.csdn.net/baidu_39322753/article/details/100073169;
- @Transactional 加于接口方法后, 被它类的私有方法调用后, 有效。
对于事务隔离性的实现:
https://www.cnblogs.com/takumicx/p/9998844.html
对于事务注解的原理:
https://blog.csdn.net/baidu_39322753/article/details/100073169