MySQL隔离性及Spring事务
一、数据库事务ACID特性
必须要掌握事务的4个特性,其中事务的隔离性之于MySQL,对应4级隔离级别。
原子性(Atomicity):
事务中的所有原子操作,要么都能成功完成,要么都不完成,不能停滞在中间环节。发生错误要回滚至事务开始前状态,仿佛事务没有发生过。
一致性(Consistency):
事务必须始终保持数据库系统处于一致的状态,无论并发多少事务。
隔离性(Isolation):
事务的隔离性是多个用户并发访问数据库时,数据库为每一个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要存在相互隔离。[MySQL隔离级别]
持久性(Durability):
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
二、MySQL隔离级别
select @@tx_isolation ; --查询MySQL隔离级别
set <level> tx_isolaction = 'READ-COMMITTED'; --设置事务隔离级别level分为session默认,global全局
start transaction ; -- 启动事务
set savepoint <point_name> ; --设置回滚点,mysql支持回滚点
rollback ; --回滚全部
rollback to <savepoint_name> ; --回滚至某回滚点
commit ; --提交事务
事务常用SQL命令
MySQL支持的隔离级别
读未提交(READ-UNCOMMITTED)
这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据。
读/写提交(READ-COMMITTED)
保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取该事务未提交的数据。
快照读(Snapshot Read)在事务A尚未提交时,其他事务仍然可以读到表中数据(不含未提交)即为快照读
与之对应的还有当前读(Current Read),若事务A不提交,当前读便会阻塞waiting... (select * from t1 for update )
在RC级别下快照读在更新,即若有其他事务提交,则更新数据快照,这也是不可重复读产生的原因。而RR级别下同一事务只有一版数据快照来实现可重复读
√ 可重复读(REPEATABLE-READ)(MySQL默认此级别)
MySql的默认隔离级别,当第一次读取到数据时就对数据加s锁(共享锁),就不允许其他事务进行“修改操作(Update 、delete会加x锁,排它锁 s与x互斥)”了,而“不可重复读”恰恰是因为两次读取之间进行了数据的修改。
多版本并发控制(MVCC,Multiversion Currency Control)见下文
序列化(SERIALIZABLE)
这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行,读取数据加s锁,写加x锁,读写互斥。可以有效避免不可重复读取、幻读、脏读问题,但会极大的降低数据库的并发能力。
常见错误
脏读:允许一个事务A去读取另外一个事务B未提交的数据。(未提交即未确认,所以A事务读到脏数据的可能性非常高)
不可重复读:同一条数据被多个事务影响,造成数据动态变化,不能重复读取。(事务A前后多次查询的同一条数据结果不一致,即该条数据不可重复读。原因是事务B在A多次读取间隙修改过该数据)
幻读:在一个事务中,第二次select多出了row就算幻读(MySQL官方定义),不可重复读针对的是数据的修改、删除,而幻读针对破坏数据整体性insert操作。
-- RR级别下:
-- 情景1
--事务A添加一条新数据,
select * from t1 ;
+----+--------------+------------+
| id | class_name | teacher_id |
+----+--------------+------------+
| 3 | 初二一班 | 2 |
| 4 | 初二二班 | 2 |
| 5 | 初三三班 | 2 |
+----+--------------+------------+
insert into t1 (class_name,teacher_id) values ('初三一班',3);
--1行被影响
select * from t1 ;
+----+--------------+------------+
| id | class_name | teacher_id |
+----+--------------+------------+
| 3 | 初二一班 | 2 |
| 4 | 初二二班 | 2 |
| 5 | 初三三班 | 2 |
| 6 | 初三一班 | 3 |
+----+--------------+------------+
commit ;
--事务B查询所有 (RR级别不发生不可重复读)
select * from t1 ;
+----+--------------+------------+
| id | class_name | teacher_id |
+----+--------------+------------+
| 3 | 初二一班 | 2 |
| 4 | 初二二班 | 2 |
| 5 | 初三三班 | 2 |
+----+--------------+------------+
--事务B插入操作
insert into t1 values(6,'初一一班',4);
--插入失败,但select 操作又不会发现id= 6 的数据
RR级别幻读情景1
-- 情景2
-- 事务A 查询表t1
select * from t1 ; -- 假设为3条数据
-- 事务B查询表t1(加s锁)
select * from t1 ; -- 假设为3条数据
-- 事务A向t1插入1数据
insert into t1 values(6,'初一一班',4);
select * from t1 ; -- 4条数据,注意是事务A查询
-- 当事务B修改'全部数据'时会发现
update t1 set teacher_id = 888 ; -- 4条数据被影响
select * from t1 ; --3条数据
-- 查询和修改的记录条数不一致,查询是查不到事务A新插入的数据的,但修改却可以成功修改事务A新插入的数,幻读。
RR级别幻读情景2
-- 在RC级别的下幻读操作很容易实现
-- 事务A修改操作
update t1 set class_name = '初三四班' where teacher_id= 2 ; -- 2行数据被影响
--这时事务B插入操作
insert into t1 (class_name,teacher_id) values ('初三三班',2); -- 1行数据被影响
commit ;
--当事务A查询
select * from t1 where teacher_id = 2 ;
+----+--------------+------------+
| id | class_name | teacher_id |
+----+--------------+------------+
| 3 | 初二一班 | 2 |
| 4 | 初二二班 | 2 |
| 5 | 初三三班 | 2 |
+----+--------------+------------+
--合计3条数据,出现幻读现象
RC级别幻读
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
MVCC相关的锁
多版本并发控制(MVCC,Multiversion Currency Control)
不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。上文说的,是使用悲观锁机制来处理这两种问题,但是MySQL、ORACLE、PostgreSQL等成熟的数据库,出于性能考虑,都是使用了以乐观锁为理论基础的MVCC来避免这两种问题。
悲观锁
正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时给加锁,其它事务无法修改这些数据。修改删除数据时也要加锁,其它事务无法读取这些数据(序列化级别)
乐观锁
相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。
而乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本( Version )记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本号与数据库表对应记录的当前版本号进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
要说明的是,MVCC的实现没有固定的规范,每个数据库都会有不同的实现方式,这里讨论的是InnoDB的MVCC。
NEXT—KEY锁
NEXT—KEY锁是行锁和gap(间隙锁)的合并,行锁可以避免不同事务对相同数据的修改冲突,但无法避免insert操作问题
--在RR级别下
--事务A修改操作
update t1 set class_name = '初二三班' where id = 4 ;
--这时行锁会锁定id = 4的数据行,不允许其他事务进行修改或删除操作
--同时gap锁会锁定id = 4数据行的相邻区间,屏蔽其他事务在该区间内的插入操作
--如果使用的是没有索引的字段,比如:
update t1 set class_name ='初二三班' where teacher_id = 2
--(即使没有匹配到任何数据)那么也会给全表加入gap锁。
--同时,它不能像行锁一样经过MySQL Server过滤自动解除不满足条件的锁
--因为没有索引,则这些字段也就没有排序,也就没有区间。除非该事务提交,否则其它事务无法插入任何数据。
--行锁防止别的事务修改或删除,GAP锁防止别的事务新增
--行锁和GAP锁结合形成的的Next-Key锁共同解决了RR级别在[写数据时的]幻读问题。
MVCC在MySQL的InnoDB中的实现
在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。 在可重读RR事务隔离级别下:
SELECT时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。(快照读)
INSERT时,保存当前事务版本号为行的创建版本号
DELETE时,保存当前事务版本号为行的删除版本号
UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行
通过MVCC,虽然每行记录都需要额外的存储空间,更多的行检查工作以及一些额外的维护工作,但可以减少锁的使用,大多数读操作都不用加锁,读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,也只锁住必要行。我们不管从数据库方面的教课书中学到,还是从网络上看到,大都是上文中事务的四种隔离级别这一模块列出的意思,RR级别是可重复读的,但无法解决幻读,而只有在Serializable级别才能解决幻读。于是我就加了一个事务C来展示效果。在事务C中添加了一条teacher_id=1的数据commit,RR级别中应该会有幻读现象,事务A在查询teacher_id=1的数据时会读到事务C新加的数据。但是测试后发现,在MySQL中是不存在这种情况的,在事务C提交后,事务A还是不会读到这条数据。可见在MySQL的RR级别中,是解决了部分幻读的读问题的。
三、Spring事务
有关Spring事务仅做简单实例说明,相关底层及原理不介绍,可能会觉得虎头蛇尾,日后会单独出一节关于Spring事务的帖子。
传播行为是指方法之间的调用事务策略的问题。
传播行为 | 含义 | 备注 |
---|---|---|
REQUIRED | 不存在创建新事务,存在沿用之前的事务 | Spring默认传播行为 |
SUPPORTS | 不存在不创建,存在沿用之前的事务 | - |
MANDATORY | 方法必须在事务中运行 | 没有事务,抛出异常 |
REQUIRES_NEW | 无论是否存在事务,都会在新的事务中运行 | 事务管理器自动创建新的 |
NOT_SUPPORTED | 不支持事务,存在事务会挂起,知道方法结束恢复 | 适用于不需要事务的SQL |
NEVER | 不支持事务,不能在事务环境下运行 | 存在事务,抛出异常 |
NESTED | 嵌套事务,支持内部事务回滚,不影响主事务 | savepoint保存点 |
四、Spring事务实例
//基于ssm整合之后的项目,完成批量操作示例d
//1.创建两个service A 、B A负责单条插入 B负责批量插入 B循环调用A的方法
//B为默认隔离级别,默认spring事务传播行为 A为默认隔离级别,REQUIRES_NEW的传播行为
//DAO层略
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED)
public int insertStudentList(List<Students> list) {
// TODO Auto-generated method stub
int count = 0 ;
for(Students stu : list) {
try {
count += service.insertStudent(stu);
}catch(DuplicateKeyException e) {
System.err.println(e);
System.err.println(stu+"该数据出现了异常 。。。。。。。。");
}
}
System.out.println("新增"+count+"条数据...");
return count ;
}
//注意需要异常处理,SpringTX根据是否出现异常判断
<!-- spring-service.xml 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置基于注解的声明式事务 -->
<tx:annotation-driven transaction-manager="transactionManager" />
spring.xml
五、常见Spring事务的使用错误
static 方法和非public方法 @Transactional注解失效
同一个类中(主要针对service层),A方法调用B方法 @Transactional失效
相同controller中调用两次相同事务Service,会产生两个事务
切勿时间占用事务
错误的异常捕捉
MySQL隔离性及Spring事务的更多相关文章
- 事务、事务特性、事务隔离级别、spring事务传播特性
事务.事务特性.事务隔离级别.spring事务传播特性 1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功, ...
- 什么是事务?事务特性?事务隔离级别?spring事务传播特性?
一.事务的概述 什么是事务? 在数据库中,所谓事务是指一组逻辑操作单元即一组sql语句,当这个单元中的一部分操作失败,整个事务回滚,只有全部正确才完成提交.判断事务是否配置成功的关键点在于出现异常时事 ...
- 数据库事务的四大特性以及事务的隔离级别-与-Spring事务传播机制&隔离级别
数据库事务的四大特性以及事务的隔离级别 本篇讲诉数据库中事务的四大特性(ACID),并且将会详细地说明事务的隔离级别. 如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: ⑴ ...
- 数据库的特性与隔离级别和spring事务的传播机制和隔离级别
首先数据库的特性就是 ACID: Atomicity 原子性:所有事务是一个整体,要么全部成功,要么失败 Consistency 一致性:在事务开始和结束前,要保持一致性状态 Isolation 隔离 ...
- 什么是事务、事务特性、事务隔离级别、spring事务传播特性
1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功,要么失败). 2.事务特性: 事务特性分为四个:原子性(At ...
- mysql 隔离性与隔离级别
提到事务,你肯定不陌生,和数据库打交道的时候,我们总是会用到事务.最经典的例子就是转账,你要给朋友小王转 100 块钱,而此时你的银行卡只有 100 块钱. 转账过程具体到程序里会有一系列的操作,比如 ...
- 19.10.11学习日记随笔 mysql事务隔离性
一天的感悟 学习事务的处理方式,其中反想自己学过的flask 默认是开启事务的,flask_sqlalchemy每次在提交时都是需要commit,或者失败是需要rollback回滚操作的,其实pyth ...
- MySQL:事务的隔离性
[参考文章]:数据库的事务特性及隔离级别 1. 事务的四大特性 1.1 原子性(Atomicity) 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用 ...
- 跟面试官侃半小时MySQL事务隔离性,从基本概念深入到实现
提到MySQL的事务,我相信对MySQL有了解的同学都能聊上几句,无论是面试求职,还是日常开发,MySQL的事务都跟我们息息相关. 而事务的ACID(即原子性Atomicity.一致性Consiste ...
随机推荐
- 多个数据库 migration
More than one DbContext was found. Specify which one to use. Use the '-Context' parameter for PowerS ...
- https://www.cnblogs.com/M-LittleBird/p/5902850.html
https://www.cnblogs.com/M-LittleBird/p/5902850.html
- Ubuntu搭建hugo博客
自己搭建了一个博客用hugo,后因自己搭建的博客上传文章,搞一些东西不方便,就创建了现在这个博客,不过还是把如何搭建hugo的过程记录以下. Ubuntu下的操作 1. 下载Git 打开终端 Ctrl ...
- 个人永久性免费-Excel催化剂功能第24波-批量发送邮件并指点不同附件不同变量
批量发送邮件功能,对高级OFFICE用户来说,第1时间会想到使用WORD的邮件合并功能.但对于需要发送附件来说,邮件合并功能就无能为力,同样还有的限制是用户电脑上没有安装OUTLOOK,同样也不能发送 ...
- 2-1. 基于OpenSSL的传输子系统实现
一. 基本传输子系统程序设计 客户端可上传文件至服务器,或下载服务器上的文件 系统程序构架: 客户端 服务器 TCP建立连接 menu()-> 上传命令.下载命令 close(socket) T ...
- md文档的书写《三》
markdown语法 官网 这是标题 "#加空格" 是标题,通常可以设置六级标题. 内容下 空格是换行 列表 无序列表:使用" - + * "任何一种加空格都可 ...
- php_review_day1
php中的小知识点(小白笔记整理):-----------------------------------------------------读取本地文件内的数据: file_get_contents ...
- Linux中的update和upgrade的作用
update 是同步 /etc/apt/sources.list 和 /etc/apt/sources.list.d 中列出的源的索引,这样才能获取到最新的软件包.update是下载源里面的metad ...
- Java的几种创建实例方法的性能对比
近来打算自己封装一个比较方便读写的Office Excel 工具类,前面已经写了一些,比较粗糙本就计划重构一下,刚好公司的电商APP后台原有的导出Excel实现出现了可怕的性能问题,600行的数据生成 ...
- java数组扩容
有些时候使用数组代替栈,玩意数组容量不够需要扩容 则: 1.Array.toString();直接遍历打印数组 2.数组扩容采用Array.copyOf(),直接实现数组扩容功能,非常强大 (实际 ...