一、数据库事务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事务的更多相关文章

  1. 事务、事务特性、事务隔离级别、spring事务传播特性

    事务.事务特性.事务隔离级别.spring事务传播特性   1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功, ...

  2. 什么是事务?事务特性?事务隔离级别?spring事务传播特性?

    一.事务的概述 什么是事务? 在数据库中,所谓事务是指一组逻辑操作单元即一组sql语句,当这个单元中的一部分操作失败,整个事务回滚,只有全部正确才完成提交.判断事务是否配置成功的关键点在于出现异常时事 ...

  3. 数据库事务的四大特性以及事务的隔离级别-与-Spring事务传播机制&隔离级别

    数据库事务的四大特性以及事务的隔离级别   本篇讲诉数据库中事务的四大特性(ACID),并且将会详细地说明事务的隔离级别. 如果一个数据库声称支持事务的操作,那么该数据库必须要具备以下四个特性: ⑴ ...

  4. 数据库的特性与隔离级别和spring事务的传播机制和隔离级别

    首先数据库的特性就是 ACID: Atomicity 原子性:所有事务是一个整体,要么全部成功,要么失败 Consistency 一致性:在事务开始和结束前,要保持一致性状态 Isolation 隔离 ...

  5. 什么是事务、事务特性、事务隔离级别、spring事务传播特性

    1.什么是事务: 事务是程序中一系列严密的操作,所有操作执行必须成功完成,否则在每个操作所做的更改将会被撤销,这也是事务的原子性(要么成功,要么失败). 2.事务特性: 事务特性分为四个:原子性(At ...

  6. mysql 隔离性与隔离级别

    提到事务,你肯定不陌生,和数据库打交道的时候,我们总是会用到事务.最经典的例子就是转账,你要给朋友小王转 100 块钱,而此时你的银行卡只有 100 块钱. 转账过程具体到程序里会有一系列的操作,比如 ...

  7. 19.10.11学习日记随笔 mysql事务隔离性

    一天的感悟 学习事务的处理方式,其中反想自己学过的flask 默认是开启事务的,flask_sqlalchemy每次在提交时都是需要commit,或者失败是需要rollback回滚操作的,其实pyth ...

  8. MySQL:事务的隔离性

    [参考文章]:数据库的事务特性及隔离级别 1. 事务的四大特性 1.1 原子性(Atomicity) 原子性是指事务包含的所有操作要么全部成功,要么全部失败回滚,因此事务的操作如果成功就必须要完全应用 ...

  9. 跟面试官侃半小时MySQL事务隔离性,从基本概念深入到实现

    提到MySQL的事务,我相信对MySQL有了解的同学都能聊上几句,无论是面试求职,还是日常开发,MySQL的事务都跟我们息息相关. 而事务的ACID(即原子性Atomicity.一致性Consiste ...

随机推荐

  1. 【DFS练习】Pku1950 Dessert-C++

    这道题和这道题很类似. 这里还是说一下坑点,因为前一道题'等式'的加数只有9个,但是这道题目最大到了15,所以在选择不加符号的时候需要判断是用100去乘还是用10去乘就可以了. 基本代码稍微把相关的9 ...

  2. python菜鸟基础知识(一)

    第三章 基础 3.1程控制语句 3.1.1if语句 1.单if ​ if 关键字 空格 条件 冒号 ​ 缩进 结果 print(1) if 3 > 2: print(9) print(8) 2 ...

  3. 题解 P3811 【【模板】乘法逆元】

    P3811 [模板]乘法逆元 一个刚学数论的萌新,总结了一下这题的大部分做法 //一.费马小定理+快速幂 O(nlogn) 64分 #include<cstdio> using names ...

  4. [PTA] 数据结构与算法题目集 6-4 链式表的按序号查找 & 6-5 链式表操作集 & 6-6 带头结点的链式表操作集

    带不带头结点的差别就是,在插入和删除操作中,不带头结点的链表需要考虑两种情况:1.插入(删除)在头结点.2.在其他位置. 6.4 //L是给定单链表,函数FindKth要返回链式表的第K个元素.如果该 ...

  5. Java 多线程部分面试题

    1..什么是线程,什么是进程,它们有什么区别和联系,一个进程里面是否必须有个线程 进程和线程的主要差别在于它们是不同的操作系统资源管理方式.进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它 ...

  6. DedeCMS自定义表单制作和调用办法

    [摘要]在很多建站需求中,需要一些额外的表单供前台用户提交,以便于收集.统计.分析及处理更多的数据,利用DedeCMS自带提供的自定义表单功能即可满足大多数的此类需求,本文就讲一下如何使用DedeCM ...

  7. PhpCms V9中的{date('Y-m-d',$r[inputtime])}问题解决方法

    不少朋友会碰到这个问题:在PhpCms V9中的首页或者文章内容页调用发布时间{date('Y-m-d',$r[inputtime])}调用显示1970-01-01,然后尝试用截断的方法也没有成功,应 ...

  8. linux初学者-磁盘阵列篇

    linux初学者-磁盘阵列篇 在磁盘的使用中,有时候需要提高磁盘的读写数据速度,就要用到磁盘组——raid,也就是磁盘阵列. 磁盘阵列是由最少两块以上的磁盘组成的,raid有许多模式,在这里将介绍其中 ...

  9. java并发笔记之java线程模型

    警告⚠️:本文耗时很长,先做好心理准备 java当中的线程和操作系统的线程是什么关系? 猜想: java thread —-对应-—> OS thread Linux关于操作系统的线程控制源码: ...

  10. [学习笔记] pymysql入门

    一.快速开始 对于会用MySQL的朋友来说,开始使用pymysql可以说真的so esay,只要用下面的代码,把想要对数据库的操作放在 sql = " " 里就可以了. 没有接触过 ...