1. 事务回顾
1)什么是事务2)事务的特性(ACID)
原子性(Atomicity)一致性(Consistency)隔离性(Isolation)持久性(Durability) 2.事务的隔离级别
1)常见的并发异常
1.丢失修改(脏写)
第一类丢失更新第二类丢失更新 2.脏读( Dirty Read )3.不可重复读( Non-Repeatable Read ) 3.幻读( Phantom ) 四种异常的严重程度对比扩展问题:
脏读和不可重复读的区别:不可重复度和幻读区别: 2)不同的隔离级别
READ UNCOMMITTED :读未提交READ COMMITTED :读已提交REPEATABLE READ :可重复读SERIALIZABLE :可串行化四种隔离级别的选取扩展问题: 3. 实现机制
从对待锁的态度划分:乐观锁、悲观锁
1.悲观锁(Pessimistic Locking
共享锁(S锁)排他锁(X锁) 2.乐观锁(Optimistic Locking)
乐观锁的版本号机制乐观锁的时间戳机制两种锁的适用场景 4. Spring事务管理
声明式事务
实现方式优缺点代码实现示例
==@Transactional(隔离级别,传播机制)==
==REQUIRED====REQUIRES_NEW====NESTED== 编程式事务
实现方式优缺点代码实现示例
==TransactionTemplate==
设置事务隔离级别和传播机制==transactionTemplate.execute==
1. 事务回顾参考牛客网高级项目教程
尚硅谷Mysql教程课件
Mybaits事务管理解析
1)什么是事务
事务是由N步数据库操作序列组成的逻辑执行单元,
一组逻辑操作单元,使数据从一种状态变换到另一种状态。
这系列操作要么全执行,要么全放弃执行在数据库中的表现是:如果一个SQL语句出错,则该批次内的所有SQL都将被取消执行
举例说明事务:
事务最经典也经常被拿出来说例子就是转账了。 假如小明要给小红转账 1000 元,这个转账会涉及到两个关键操作就是: 将小明的余额减少 1000 元,将小红的余额增加 1000 元。 万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。 事务就是保证这两个关键操作要么都成功,要么都要失败。2)事务的特性(ACID) 原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,要么全部提交,要么全部失败回滚。 一致性(Consistency)
一致性是指事务执行前后,数据从一个 ==合法性状态==变换到另外一个 合法性状态 。
满足 预定的约束 的状态就叫做合法的状态满足了约束即满足了一致性
例如:
比如:表中 money 字段,规定为了只能大于等于 0,如果在事务中设置为小于 0 的值, 那么这个事务会执行失败。隔离性(Isolation)
各个事务的执行互不干扰,任何事务的内部操作对其他的事务都是隔离的。
即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
举例:
在B没有写回磁盘前,其他事务同时读B,最终B写回磁盘的是50,而不是100
持久性(Durability)
事务一旦提交,对数据所做的任何改变都要记录到永久存储器中,而不是内存里。
即一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
数据库事务持久性的实现:
持久性是通过 事务日志 来保证的。日志包括了 重做日志 和 回滚日志 。 当我们通过事务对数据进行修改的时候,首先会将数据库的变化信息记录到重做日志中,然后再对数据库中对应的行进行修改。 这样做的好处是,即使数据库系统崩溃,数据库重启后也能找到没有更新到数据库系统中的重做日志,重新执行,从而使事务具有持久性。2.事务的隔离级别 1)常见的并发异常
在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户(线程)对统一数据进行操作)。并发虽然是必须的,但可能会导致以下的问题。 1.丢失修改(脏写)
对于两个事务 Session 1、Session 2,访问同一条数据
一个事务 Session1在提交之前,另一个事务Session2对这条数据写了,就是脏写,对这条数据读了,就是脏读
指在一个事务读取一个数据还未提交时,另外一个事务也访问了该数据,那么在事务2修改了这个数据后,事务1最终也修改了这个数据并提交或回滚。这样事务2修改的数据会丢失,那么事务2写的这个数据是“脏数据”, 依据“脏数据”所做的操作可能是不正确的。 第一类丢失更新
某一个事务的回滚,导致另外一个事务已更新的数据丢失了
例如:由于事务1的回滚,导致事务2的更新无效,丢失了
第二类丢失更新
某一个事务的提交,导致另外一个事务已更新的数据丢失了。
例如:由于事务1的提交,导致事务2的更新无效,丢失了
读的问题
2.脏读( Dirty Read )某一个事务,读取了另外一个事务未提交的数据。
当一个事务Session 1 正在访问数据并且对数据进行了修改**,而这种修改还没有提交到数据库中**,这时另外一个事务Session 2 也访问了这个数据,然后读取了这个没提交的数据。因为这个数据是还没有提交的数据, 那么另外一个事务读到的这个数据是“脏数据”, 依据“脏数据”所做的操作可能是不正确的。 3.不可重复读( Non-Repeatable Read )
某一个事务,对同一个数据前后读取的结果不一致
即事务2读取了数据,事务1将数据更新并提交了,事务2第二次读,数据前后不一致了,就出现问题了这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。 注意:重复只有同一个事务才叫重复,不同事务不叫重复。 3.幻读( Phantom )
某一个事务,对同一个表前后查询到的行数不一致。
与不可重复读类似,只是读到的数据行数前后不一致它发生在一个事务( T2)读取了几行数据,接着另一个并发事务( T1) 插入了一些数据时。在随后的查询中,事务( T2)就会发现多了一些原本不存在的记录, 就好像发生了幻觉一样,所以称为幻读。
四种异常的严重程度对比脏写 > 脏读 > 不可重复读 > 幻读扩展问题: 脏读和不可重复读的区别:
脏读是两个事务读的数据不同,不可重复读是同一事务读取的数据不同。 不可重复度和幻读区别:
不可重复读的重点是修改,幻读的重点在于新增或者删除。 2)不同的隔离级别
READ UNCOMMITTED :读未提交在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。不能避免脏读、不可重复读、幻读。 READ COMMITTED :读已提交
它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。可以避免脏读,但不可重复读、幻读问题仍然存在。 REPEATABLE READ :可重复读
事务A在读到一条数据之后,此时事务B对该数据进行了修改并提交,那么事务A再读该数据,读到的还是原来的内容。可以避免脏读、不可重复读,但幻读问题仍然存在。这是MySQL的默认隔离级别。 SERIALIZABLE :可串行化
对数据是加了锁读的,所有隔离级别最高,但并发性能最低
确保事务可以从一个表中读取相同的行。
在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作。所有的并发问题都可以避免,但性能十分低下。能避免脏读、不可重复读和幻读。
四种隔离级别的选取
为了兼顾性能和隔离效果,一般使用中间的READ COMMITTED :读已提交和REPEATABLE READ :可重复读
对于幻读,
可以采用乐观锁解决,或者是修改业务,
比如调整读取时间为更新比较少的时间段后台统计时,只影响自己,数据有问题,可以再次统计 扩展问题:
与 SQL 标准不同的地方在于 InnoDB 存储引擎在 REPEATABLE-READ(可重复读)事务隔离级别下使用的是 Next-Key Lock 锁算法,因此可以避免幻读的产生,这与其他数据库 系 统 ( 如 SQL Server) 是 不 同 的 。 所 以 说 InnoDB 存 储 引 擎 的 默 认 支 持 的 隔 离 级 别 是REPEATABLE-READ(可重复读) 已经可以完全保证事务的隔离性要求,即达到了 SQL 标准的SERIALIZABLE(可串行化)隔离级别。因 为 隔 离 级 别 越 低 , 事 务 请 求 的 锁 越 少 , 所 以 大 部 分 数 据 库 系 统 的 隔 离 级 别 都 是
READ-COMMITTED( 读 取 已 提 交 ), 但 是 InnoDB 存 储 引 擎 默 认 使 用REPEATABLE-READ(可重复读) 并不会有任何性能损失。InnoDB 存储引擎在分布式事务的情况下一般会用到 SERIALIZABLE(可串行化)隔离级别。
3. 实现机制
从对待锁的态度划分:乐观锁、悲观锁
1.悲观锁(Pessimistic Locking
悲观锁是一种思想,顾名思义,就是很悲观,
对数据被其他事务的修改持保守态度,会通过数据库自身的锁机制来实现,从而保证数据操作的排它性。
悲观锁总是假设最坏的情况,每次去拿数据的时候都认为别人会修改, 所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会 阻塞 直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。 比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁,当其他线程想要访问数据时,都需要阻塞挂起。 Java中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。共享锁(S锁)
共享锁(读锁):其他事务可以读,但不能写
事务A对某数据加了共享锁后,其他事务只能对该数据加共享锁,但不能加排他锁。
排他锁(X锁)
排他锁(写锁) :其他事务不能读取,也不能写。
事务A对某数据加了排他锁后,其他事务对该数据既不能加共享锁,也不能加排他锁。
2.乐观锁(Optimistic Locking)
乐观锁认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,
但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,也就是不采用数据库自身的锁机制,而是通过程序来实现。
在程序上,我们可以采用 版本号机制 或者 CAS机制 实现。
乐观锁适用于多读的应用类型,这样可以提高吞吐量。在Java中 java.util.concurrent.atomic 包下的原子变量类就是使用了乐观锁的一种实现方式:CAS实现的。
乐观锁的版本号机制
在表中设计一个 版本字段 version ,第一次读的时候,会获取 version 字段的取值。
然后对数据进行更新或删除操作时,会执行
UPDATE ... SET version=version+1 WHERe version=version
此时如果已经有事务对这条数据进行了更改,修改就不会成功。 乐观锁的时间戳机制
时间戳和版本号机制一样,也是在更新提交的时候,将当前数据的时间戳和更新之前取得的时间戳进行
比较,如果两者一致则更新成功,否则就是版本冲突。你能看到乐观锁就是程序员自己控制数据并发操作的权限,基本是通过给数据行增加一个戳(版本号或
者时间戳),从而证明当前拿到的数据是否最新。
两种锁的适用场景
从这两种锁的设计思想中,我们总结一下乐观锁和悲观锁的适用场景:
乐观锁 适合 读操作多 的场景,相对来说写的操作比较少。
它的优点在于 程序实现 , 不存在死锁问题,不过适用场景也会相对乐观,因为它阻止不了除了程序以外的数据库操作。
悲观锁 适合 写操作多 的场景,因为写的操作具有 排它性 。
采用悲观锁的方式,可以在数据库层面阻止其他事务对该数据的操作权限,防止 读 - 写 和 写 - 写 的冲突。
通过XML配置,声明某方法的事务特征。通过注解,声明某方法的事务特征。 优缺点
操作简单,管理方便但会管理加上注解方法内的所有逻辑模块,无法实现局部控制,性能浪费
即如果,方法中有很多逻辑模块,但只有一两处需要进行事务管理,采用第二种自定义的编程式事务更好 代码实现示例 @Transactional(隔离级别,传播机制)
增加事务控制的注解
隔离级别选取:
isolation = Isolation.READ_COMMITTED
传播机制选取:
propagation = Propagation.REQUIREDREQUIRED
支持当前事务(外部事务),如果不存在则创建新事务A调B,A为外部事务,支持A事务,如果A没有事务控制,就使用B事务 REQUIRES_NEW
创建一个新事务,并且暂停当前事务(外部事务)不管A有没有事务,都使用B事务 NESTED
如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),
混合使用,B事务也独立执行 否则就会REQUIRED一样.
// REQUIRED: 支持当前事务(外部事务),如果不存在则创建新事务.
// REQUIRES_NEW: 创建一个新事务,并且暂停当前事务(外部事务).
// NESTED: 如果当前存在事务(外部事务),则嵌套在该事务中执行(独立的提交和回滚),否则就会REQUIRED一样.
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED)
public Object save1() {
// 新增用户
User user = new User();
user.setUsername("alpha");
user.setSalt(CommunityUtil.generateUUID().substring(0, 5));
user.setPassword(CommunityUtil.md5("123" + user.getSalt()));
user.setEmail("alpha@q.com");
user.setCreateTime(new Date());
user.setHeaderUrl("http://image.nowcoder.com/head/99t.png");
userMapper.insertUser(user);
// 新增帖子
DiscussPost post = new DiscussPost();
post.setUserId(user.getId());
post.setContent("新人报道!");
post.setTitle("Hello");
post.setCreateTime(new Date());
discussPostMapper.insertPosts(post);
// 新增错误逻辑
Integer.valueOf("abc");
return "OK";
}
@Test
public void testSav1() {
Object obj = alphaService.save1();
System.out.println(obj);
}
测试结果:均回滚,数据库中没有执行添加操作
编程式事务 实现方式通过 TransactionTemplate 管理事务,并通过它执行数据库的操作。 优缺点
方便局部控制代码操作简便性不如使用注解,xml配置 代码实现示例 TransactionTemplate
使用Spring自动的事务管理工具类 设置事务隔离级别和传播机制
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);transactionTemplate.execute
在此执行方法块中,进行事务控制
TransactionCallback
继承处理事务的接口并重新doInTransaction处理事务的方法,在此方法块中,管理事务
@Autowired private TransactionTemplate transactionTemplate;
// 编程式事务,处理局部事务
public Object save2() {
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
return transactionTemplate.execute(new TransactionCallback
@Test
public void testSave2() {
Object obj = alphaService.save2();
System.out.println(obj);
}
测试结果:均回滚,数据库中没有执行添加操作



