如果壹个人从来没有享受过一次“线、面、体”带来的趋势红利,而是作为一个点在努力挣扎,赚取那微薄的计时收益,这就叫做“穷人勤奋的一生”
一、mysql事务
1、什么是事务
答案1:一个commit和一个rollback之间【commit】的一个或多个DML
答案2:提供一种“要么什么都不做,要么做全套”机制,保证数据库的安全性
答案3:在一次session会话中,所有的sql,要么一起成功,要么一起失败
事务其目的是为了「保证数据最终的一致性」。
默认情况:当一个连接对象被创建时,默认情况下是自动提交事务:每次执行一个 SQL 语句时,
如果执行成功,就会向数据库自动提交,而不能回滚
2、开启与提交事务
- begin和start transaction命令开启一个事务
-
commit 事务提交
BEGIN;
UPDATE t_user SET balance=balance-10 WHERE user_id=1;
UPDATE t_user SET balance=balance-10 WHERE user_id=1;
COMMIT;
显示事务:使用关键字:START TRANSACTION或BEGIN
START TRANSACTION语句相较于BEGIN特别之处在于,后边能跟随几个修饰符:
read only/read write(默认)/with consistent snapshot
翻译过来就是只读事务、读写事务(默认)、一致性读
①当我们在一个事务还没有提交或回滚时又使用start transaction,或者 begin 开启新的事物,会隐式提交上一个事务。
我们在autocommit为true的情况下,使用start transaction或begin开启事务,那么DML操作就不会自动提交数据
隐式事务 关闭自动提交
SET autocommit=FALSE;
当事务中所有的SQL执行完成后,通过commit将事务提交到数据库
ROLLBACK:默认回滚到上一次commit。
ddl不能回滚,即每一个ddl是一个事务。dml可以回滚【如2种删除表数据的区别】
-- 清空表 TRUNCATE 【表结构还存在,ddl,删了就彻底完蛋,不可以回滚】 TRUNCATE TABLE empy6; -- 删除表 删除表中数据,【表结构还有,dml,可以用rollback回滚表中数据】 DELETE FROM emp5;
保存点(savepoint)
保存点的创建、删除、回滚到某个保存点都有
3、连接与会话
获取数据库连接
Connection conn =DriverManager.getConnection(url,user,p);
默认情况下,一个connection被创建时,默认是auto-commit模式,statement执行完sql后自动commit。
MySQL 会话是指一次 MySQL 数据库交互过程。
当客户端应用程序与 MySQL 数据库建立连接后,就会创建一个 MySQL 会话。在一个 MySQL 会话中,客户端可以向数据库发送查询和命令,并从数据库获取结果。MySQL 会话在客户端断开连接后结束。
4、编程式事务
转账举例:
- A账户减去100
- B账户加100
假设转账过程中出现一个异常, int i =10/0;
转账前:AA用户和BB用户都有1000余额
转账过程:
因为俩个不同的连接,没法成为同一个事务,改变方式使得获取的连接是同一个
MyQueryRunner mqr = new MyQueryRunner(); @Test public void test2() throws Exception { Connection conn = JDBCUtils.getConnection(); String sql1 = "update user_table set balance = balance - 100 where user = ?"; mqr.update(conn, sql1, "AA"); //模拟故障 int i = 10 / 0; String sql2 = "update user_table set balance = balance + 100 where user = ?"; //在俩个不同的连接上是没法成为同一个事务的,所以 mqr.update(conn, sql2, "BB"); //事务提交 JDBCUtils.close(conn, null, null); }
结果就很不对,一个sql成功了一个sql失败了,按道理说程序报错了应该回滚才对。
解决这种并发问题的思路:这俩个DML操作要么都成功,要么都失败。让2个DML成为一个事务
对上面代码做出改动如下:
- 取消自动提交事务 connection连接对象.setAutoCommit(false)
- 成功使用commit,失败在异常处理里使用rollback
MyQueryRunner mqr = new MyQueryRunner(); @Test public void test1() { Connection conn = null; try { conn = JDBCUtils.getConnection(); conn.setAutoCommit(false);//取消自动提交(开启事务) String sql1 = "update user_table set balance = balance - 100 where user = ?"; mqr.update(conn, sql1, "AA"); //模拟故障 int i = 10 / 0; String sql2 = "update user_table set balance = balance + 100 where user = ?"; //在俩个不同的连接上是没法成为同一个事务的,所以 mqr.update(conn, sql2, "BB"); //事务提交 conn.commit(); } catch (SQLException e) { e.printStackTrace(); try { conn.rollback();//事务回滚 } catch (SQLException ex) { ex.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.close(conn, null, null); } }
再次测试结果:有了事务,在程序报错时,会回滚数据。这样就能做到“要么一起成功,要么一起失败”
二、事务的ACID特性
事务的原子性、一致性和持久性由事务的redo日志和undo日志来保证。
1、A (Atomicity) 原子性
原子性:一个事务中的所有操作要么都成功,要么都失败,即作为事物的一组操作不可分割
2、C (Consistency) 一致性
一致性: 一个事务执行之前,数据库中的数据整体是正确的;这个事务执行之后,数据库中的数据整体仍然是正确的。一致性的具体表现在:
- 事务执行成功:提交(commit)
- 事务执行失败:回滚(rollback)数据库要一直处于一致的状态,事务的运行不会改变数据库原本的一致性约束。
UND0 LOG称为回滚日志,回滚行记录到某个特定版本,用来保证事务的原子性、一致性。
undo理解:记录逻辑操作日志,比如某一行数据进行了insert语句操作,那么undo log就记录一条与之相反的DELETE操作
3、I (Isolation) 隔离性
隔离性:并发执行的各个事务之间不能互相干扰,隔离程度取决于隔离的级别。隔离性是由MySQL的各种锁来实现的,只是它屏蔽了加锁的细节。
事务查看数据更新时,数据所处的状态要么是另一事务修改它之前的状态,要么是另一事务修改它之后的状态,事务不会查看到中间状态的数据。
4、D (Durability) 持久性
持久性:一旦commit对数据库中数据的改变就是永久性的,就算数据库宕机也不应该对其有任何影响
REDO LOG称为重做日志,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性。
redo理解:数据1s后才做刷盘操作,为了保证内存中数据的修改一定能写入磁盘。
三、事务并发问题
脏读、不可重复读、幻读,都是数据库读一致性问题,必须由数据库提供一定的事务隔离机制来解决
1、脏读
脏读 : 事务A读取了事务B修改但是没有提交的数据,事务B一旦回滚,那么事务A读取到的数据是脏数据(不可用的假数据)
2、不可重复读
不可重复读 :事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致(分别读取到事务B提交前和提交后的)。
总结:不可重读是读取到了别人已经提交的数据,两次读取中间被某些记录已经被删除或者修改过了,导致两次读取的结果不一致,主要是修改或者删除行
3、幻读
幻:事务A在读取某个条件范围内的全部记录时,事务B又在该范围内插入了新的记录,导致事务A同样条件再次读取时,产生幻读
幻读指当用户读取某一范围的数据行时另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行
幻读主要是:指插入行
四、事务的隔离级别
隔离性本质就是加锁,除了加锁还有用MVCC解决。。。
1、查看数据库隔离级别
隔离级别的存在为了保证:一致性+并发性,达到一个平衡
一个事务与其他事务隔离的程度称为隔离级别。隔离级别越高, 数据一致性就越好, 但并发性越弱.
#查看当前数据库的事务隔离级别 show variables like 'tx_isolation';
2、 四种事务隔离级别
RU 读未提交 读取到的数据是数据库没有提交的数据 ,高并发解决了,安全性急剧下降
RC 读已提交(oracle默认) 满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变,换句话说就是事务提交之前对其余事务不可见
RR 可重复读(mysql默认)保证同一个事务,多次查询结果一致(加行锁-这一行不能再修改),但是对数据库的增删(行)操作,可能会产生幻读
串行化 冰糖葫芦串,百分百安全,但并发性最差
3、不同隔离等级的并发问题
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
读未提交(read-uncommitted) | √ | √ | √ |
读已提交(read-committed) | × | √ | √ |
可重复读(repeatable-read) | × | × | √ |
串行化(serializable) | × | × | × |
mysql 默认的事务隔离级别为: REPEATABLE READ(可重复读)
mysql数据库如果隔离级别是RR + Innodb引擎的前提下,该隔离层次可以解决幻读。
也就是说在Innodb引擎下MySQL里执行的事务,默认情况下不会发生脏读、不可重复读和幻读的问题,事务的执行都是并行的
sql92是一套规范
mysql是基于上述sql92规范落地的具体实现产品
在SQL级别RR是可以发生幻读的,mysql更进一步。比规范要求做得更好
五、Spring声明式事务传播行为
使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务。
事务传播行为指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。
例如:A事务方法调用B事务方法时,B是继续在调用者A的事务中运行呢,还是为自己开启一个新事务运行,这就是由B方法的事务传播行为决定的。
Spring定义了七种传播行为:参考 TransactionDefinition类
事务传播行为类型
说明
PROPAGATION_REQUIRED
使用调用本方法时已有的事务,如果没有自己新建一个 。默认
PROPAGATION_SUPPORTS
使用调用本方法时已有的事务,如果没有不使用事务执行
PROPAGATION_MANDATORY
使用调用本方法时已有的事务,如果没有抛出异常。
PROPAGATION_REQUIRES_NEW
如果调用本方法时已存在事务则挂起,自己新创建事务
PROPAGATION_NOT_SUPPORTED
不使用事务执行,如果调用本方法时已存在事务则挂起
PROPAGATION_NEVER
如果调用本方法时已存在事务抛出异常,不使用事务
PROPAGATION_NESTED
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。(外层事务抛出异常回滚,那么内层事务必须回滚,反之内层事务并不影响外层事务)
这七种事务传播机制最常用的就两种:
REQUIRED:一个事务,要么成功,要么失败
REQUIRES_NEW:两个不同事务,彼此之间没有关系。一个事务失败了不影响另一个事务
伪代码练习
传播行为伪代码模拟:
有a,b,c,d,e等5个方法,a中调用b,c,d,e方法的传播行为在小括号中标出
a(required){ b(required); c(requires_new); d(required); e(requires_new); // a方法的业务 }
问题:
a方法的业务出现异常,会怎样?a,b,d回滚 c,e不回滚
d方法出现异常,会怎样?a,b,d回滚 c,e不回滚
e方法出现异常,会怎样?a,b,d,e回滚 c不回滚,e方法出异常会上抛影响到上级方法
b方法出现异常,会怎样?a,b回滚 c,d,e未执行
aop事务的xml配置,请移步:
2021/10/31 北京 spring【4】 JdbcTemplate,spring声明式事务_£小羽毛的博客-CSDN博客
六、本地事务
本地事务也称为*数据库事务*或*传统事务*(相对于分布式事务而言)
本地事务有这么几个特征:
- 一次事务只连接一个支持事务的数据库(一般来说都是关系型数据库)
- 事务的执行结果保证ACID
- 会用到数据库锁
这类基于单个服务单一数据库资源访问的事务,被称为本地事务(Local Transaction)。