在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
DDL、DML、DCL、TCL
- DDL(Data Definition Languages)语句:即数据库定义语句,用来创建数据库中的表、索引、视图、存储过程、触发器等
- DML(Data Manipulation Language)语句:即数据操纵语句,用来查询、添加、更新、删除等,包括通用性的增删改查
- DCL(Data Control Language)语句:即数据控制语句,用于授权/撤销数据库及其字段的权限
- TCL(Transaction Control Language)语句:事务控制语句,用于控制事务
事务控制语句TCL
BEGIN 或 START TRANSACTION 显式地开启一个事务;
COMMIT 也可以使用 COMMIT WORK,不过二者是等价的。COMMIT 会提交事务,并使已对数据库进行的所有修改成为永久性的;
ROLLBACK 也可以使用 ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;
SAVEPOINT identifier,SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;
RELEASE SAVEPOINT identifier 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
ROLLBACK TO identifier 把事务回滚到标记点;
SET TRANSACTION 用来设置事务的隔离级别。InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。
一、可以在my.ini文件中使用transaction-isolation选项来设置服务器的缺省事务隔离级别
[mysqld]
transaction-isolation = READ-COMMITTED 注意有-分隔符
二、通过命令动态设置隔离级别
SET [GLOBAL | SESSION] TRANSACTION ISOLATION LEVEL <isolation-level>
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ; 注意没-分隔符
隔离级别选项:
– READ UNCOMMITTED
– READ COMMITTED
– REPEATABLE READ
– SERIALIZABLE
注:在事物进行过程中,未结束之前,DML语句是不会更改底层数据,只是将历史操作记录一下,在内存中完成记录。只有在事物结束的时候,而且是成功的结束的时候,才会修改底层硬盘文件中的数据
事务ACID
事务是一条或一组语句组成一个单元,这个单元要么都执行,要么都不执行。
事务四大特性:
- 原子性(Atomicity):一个事务中的所有操作,要么全都完成,要么全都不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。
- 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
创建一个事务
事务分为两大类:隐式事务和显式事务
隐式的事务:比如insert、delete、update、select这些语句都是隐式的事务。在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。也就是说,只要执行一条DML语句就开启了事物,并且提交了事务
显式的事务指的是带有明显的开始和结束的标记
步骤一:禁用自动提交功能
SET AUTOCOMMIT=0 禁止自动提交
SET AUTOCOMMIT=1 开启自动提交
步骤二:开启一个事务
start transaction;
步骤三:sql语句
update table user set age=18 where name = "luneo";
步骤四:结束事务
commit 提交
rollback 回滚
如果确定语句没有问题,那么我们就可以commit,如果认为语句有问题,那就rollback
事务的并发问题
单个事务一般不会出现什么问题,但多个事务并发执行时会出现如下问题:
- 脏读: 一个事务读到另一个事务没有提交的数据。事务A修改了一个数据,但未提交,事务B读到了事务A未提交的更新结果,事务B读到的就是脏数据
- 不可重复读:在事务A中先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读。脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。
- 幻读:在事务A中按同一条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。不可重复读与幻读的区别可以理解为:前者是数据变了,后者是数据的行数变了
事务的隔离级别
一般来说,隔离级别越低,系统开销越低,可支持的并发越高,但隔离性也越差
注意,在SQL标准中,RR是无法避免幻读问题的,但是InnoDB实现的RR避免了幻读问题。
查询隔离级别
select @@global.tx_isolation; 全局隔离级别
select @@tx_isolation; 本次会话隔离级别
MVCC-多版本的并发控制协议
全称Multi-Version Concurrency Control,在同一时刻,不同的事务读取到的数据可能是不同的(即多版本),用于RR解决脏读、不可重复读、幻读等问题。
- 对于使用InnoDB存储引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列:
trx_id:每次对某条索引记录进行改动时,都会把对应的事务id赋值给trx_id隐藏列。
roll_pointer:每次对某条索引记录进行改动时,都会把旧的版本写入到undo日志中,可以通过它来找到该记录修改前的信息。
- 对于READ UNCOMMITTED隔离级别的事务,直接读取记录的最新版本,对于SERIALIZABLE隔离级别的事务,使用加锁的方式来访问记录。对于READ COMMITTED和REPEATABLE READ隔离级别的事务,就需要用到版本链,核心问题就是:需要判断版本链中的哪个版本是当前事务可见的
- ReadView中主要包含当前系统中有哪些活跃的读写事务,把它们的事务id放到一个列表中,我们把这个列表命名为为m_ids
- 如果被访问版本的trx_id属性值小于m_ids列表中最小的事务id,表明生成该版本的事务在生成ReadView前已经提交,所以该版本可以被当前事务访问。
- 如果被访问版本的trx_id属性值大于m_ids列表中最大的事务id,表明生成该版本的事务在生成ReadView后才生成,所以该版本不可以被当前事务访问。
- 如果被访问版本的trx_id属性值在m_ids列表中最大的事务id和最小事务id之间,那就需要判断一下trx_id属性值是不是在m_ids列表中,如果在,说明创建ReadView时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView时生成该版本的事务已经被提交,该版本可以被访问。
- 如果某个版本的数据对当前事务不可见的话,那就顺着版本链找到下一个版本的数据,继续按照上边的步骤判断可见性,依此类推,直到版本链中的最后一个版本,如果最后一个版本也不可见的话,那么就意味着该条记录对该事务不可见,查询结果就不包含该记录。
注意:在MySQL中,READ COMMITTED和REPEATABLE READ隔离级别的的一个非常大的区别就是它们生成ReadView的时机不同,READ COMMITTD在每一次进行普通SELECT操作前都会生成一个ReadView,而REPEATABLE READ只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复这个ReadView就好了。
READ COMMITTED: 每次读取数据前都生成一个ReadView
REPEATABLE READ :在第一次读取数据时生成一个ReadView
Spring事务管理
- 编程式事务:使用TransactionTemplate或者直接使用底层的PlatformTransactionManager
编程式事务:
TransactionFilter{
try{
//获取连接
//设置非自动 提交
chain.doFilter();
//提交
}catch(Exception e){
//回滚
}finllay{
//关闭连接释放资源
}
}
AOP:环绕通知可以去做;
//获取连接
//设置非自动 提交
目标代码执行
//正常提交
//异常回滚
//最终关闭
- 声明式事务:利用AOP的环绕通知.
优点:相对于编程式事务,可以避免在业务逻辑代码中掺杂事务管理代码,只需要在配置文件中做配置或加注解@Transactional,也是Spring倡导的非侵入式开发方式。
缺点:声明式事务最细粒度只能作用到方法(解决办法是:将需要事务管理的模块放在一个单独方法中),编程式事务可以作用到代码块。
事务切面=事务管理器
声明式事务
-
基于注解
1)配置出这个事务管理器让他工作;
2)开启基于注解的事务
3)给事务方法加@Transactional注解
异常分类:
运行时异常(非检查异常):可以不用处理;默认都回滚;
编译时异常(检查异常):要么try-catch,要么在方法上声明throws,默认不回滚;noRollbackFor:可以让原来默认回滚的异常不回滚
noRollbackFor={ArithmeticException.class,NullPointerException.class}rollbackFor:原本不回滚的异常指定让其回滚;
readOnly=true:加快查询速度;不用管事务那一堆操作了。
<!-- 配置数据源-->
<bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="user" value="${jdbc.user}"></property>
<property name="password" value="${jdbc.password}"></property>
<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
<property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
<!-- 配置JdbcTemplate -->
<bean class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!-- 事务控制 -->
<!--1:配置事务管理器(切面)让其进行事务控制;一定导入面向切面编程的几个包
spring-aspects-4.0.0.RELEASE.jar
com.springsource.net.sf.cglib-2.2.0.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
-->
<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 控制住数据源 -->
<property name="dataSource" ref="pooledDataSource"></property>
</bean>
<!--2:开启基于注解的事务控制模式;依赖tx名称空间 -->
<tx:annotation-driven transaction-manager="tm"/>
<!--3:给事务方法加注解@Transactional -->
注意: @Transactional 注解应该只被应用到 public 方法上
- 基于XML事务配置
<aop:config>
<aop:pointcut expression="execution(* com.luneo.ser*.*.*(..))" id="txPoint"/>
<!-- 事务建议;事务增强 advice-ref:指向事务管理器的配置 -->
<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
</aop:config>
<!-- 配置事务管理器; 事务建议;事务增强;事务属性;
transaction-manager="transactionManager":指定是配置哪个事务管理器;
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--事务属性 -->
<tx:attributes>
<!-- 指明哪些方法是事务方法;切入点表达式只是说,事务管理器要切入这些方法,哪些方法加事务使用tx:method指定的 -->
<tx:method name="*"/>
<tx:method name="checkout" propagation="REQUIRED" timeout="-1"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<!-- 都用;重要的用配置,不重要的用注解 -->
spring事务的传播行为
事务的传播行为指的是:传播行为(事务的传播+事务的行为)
如果有多个事务进行嵌套运行,子事务是否要和大事务共用一个事务;
注意:任何处崩,已经执行的REQUIRES_NEW都会成功;
本类方法的嵌套调用就只是一个事务;
事务的那些名词
1)事务管理器也叫事务切面
spring事务配置文件:配置数据源,配置jdbc Template操作数据库;配置事务管理器,控制数据源;配置基于注解的事务
2)事务注解类似AOP的环绕通知
运行时异常可以不处理,默认都回滚;编译时异常,默认不回滚
3)有事务控制的对象是代理对象
4)事务的传播行为,是多个事务嵌套执行,子事务是否和大失误共用一个事务
已经执行的requires new不会崩;崩了之后的不会执行
如果是required,事务的属性都是继承父事务,子事务的timeout不起作用,需要父事务设;requires new是可以的
5)声明式事务,配置事务管理器(切面),配置事务方法,告诉Spring给哪些方法配置事务,配置事务管理器
事务切面按切入点表达式切入方法
切入点表达式只是说事务管理器要切入这些方法,哪些方法要加事务是用tx:method确定
6)重要的事务用配置文件,不重要的用注解