欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

事务-学习笔记

程序员文章站 2022-07-04 23:41:43
...

一、事务(Transaction)概述

  • 其实指的一组操作,里面包含许多个单一的逻辑。只要有一个逻辑没有执行成功,那么都算失败。 所有的数据都回归到最初的状态(回滚)。
  • 为什么要有事务?
    为了确保逻辑的成功。 如: 银行转账。

二、演示事务

  • 命令行方式:
开启事务:
	start transaction;
提交:
	commit; 提交事务, 数据将会写到磁盘上的数据库
回滚:
	rollback ;  数据回滚,回到最初的状态。
  • 代码里面的事务,主要是针对连接来的。
 - 通过conn.setAutoCommit(false )来关闭自动提交的设置。
 - 提交事务  conn.commit();
 - 回滚事务 conn.rollback();
@Test
public void testTransaction(){
	Connection conn = null;
	PreparedStatement ps = null;
	ResultSet rs = null;
	try {
		conn = JDBCUtil.getConn();
		
		//连接,事务默认就是自动提交的。 关闭自动提交。
		conn.setAutoCommit(false);
			
		String sql = "update account set money = money - ? where id = ?";
		ps = conn.prepareStatement(sql);
			
		//扣钱, 扣ID为1 的100块钱
		ps.setInt(1, 100);
		ps.setInt(2, 1);
		ps.executeUpdate();
			
		int a = 10 /0 ;//有了异常,下面代码就不会执行了
			
		//加钱, 给ID为2 加100块钱
		ps.setInt(1, -100);
		ps.setInt(2, 2);
		ps.executeUpdate();
			
		//成功: 提交事务。
		conn.commit();
			
	} catch (SQLException e) {
		try {
			//失败: 回滚事务
			conn.rollback();
		} catch (SQLException e1) {
			e1.printStackTrace();
		}
		e.printStackTrace();
			
	}finally {
		JDBCUtil.release(conn, ps, rs);
	}
}

三、事务特性ACID(面试)

  • 原子性(Atom)
    指的是 事务中包含的逻辑,不可分割。
  • 一致性(Consistent)
    指的是 事务执行前后,数据完整性。
  • 隔离性(Isolate)
    指的是 事务在执行期间不应该受到其他事务的影响。
  • 持久性(Durable)
    指的是 事务执行成功,那么数据应该持久保存到磁盘上。

四、安全问题&隔离级别(面试)

  • 不考虑隔离级别设置,那么会出现以下问题。

读 问题

  • 脏读
    一个事务读到另外一个事务还未提交的数据。
  • 不可重复读
    一个事务读到了另外一个事务提交的数据 ,造成了前后两次查询结果不一致。
  • 幻读
    一个事务读到了另一个事务已提交的插入的数据,导致多次查询结果不一致。

写 问题

  • 丢失更新:
    B事务如果提交,会造成A事务的操作无效。
    B事务回滚,也会造成A事务更新失效。
  • 解决方法:悲观锁,乐观锁
  1. 悲观锁
    指事务在一开始就认为丢失更新一定会发生, 这是一件很悲观的事情。 具体操作步骤如下:
3. 所以事务在执行操作前,先查询一次数据, 查询语句如下:
	select * from student  for update  ;           
	后面的for update 其实是数据库锁机制 、 一种排他锁。
4. 哪个事务先执行这个语句, 哪个事务就持有了这把锁, 可以查询出来数据, 后面的事务想再执行这条语句,不会有任何数据显示,就只能等着。 
5. 一直等到前面的那个事务提交数据后, 后面的事务数据才会出来,那么才可以往下接着操作。 
补充:就像排队上厕所一样,只有里面的人出来了,才能进去。 这其实就是 java 中的同步的概念。
  1. 乐观锁
    乐观锁是指,从来不会觉得丢失更新会发生。要求程序员在数据库中添加字段,然后在后续更新的时候,对该字段进行判定比对, 如果一致才允许更新。
例:
1. 数据库表中,额外添加了一个version字段, 用于记录版本, 默认从0 开始, 只要有针对表中数据进行修改的,那么version就+1.
2. 开启A事务, 然后开启B事务 。 
3. A 先执行数据库表操作。 因为以前都没有人修改过。 所以是允许A事务修改数据库的,但是修改完毕,就把version的值变成  1 了 。
4. B事务, 这时候如果想执行修改,那么是不允许修改的。 因为B事务以前是没有查询过数据库内容的,所以它认为数据库版本还是0 。 但是数据库的版本经过A修改,已经是1了。
所以这时候不允许修改, 要求其重新查询 。
5. B重新查询后, 将会得到version 为 1的数据,这份数据就是之前A 事务修改的数据了, B 在进行修改,也是在A的基础上修改的。 所以就不会有丢失更新的情况出现了。

补充:乐观锁的机制 ,其实是通过比对版本或者比对字段的方式来实现的,与版本控制软件【SVN , GIT】机制是一样的。

隔离级别

- 按效率划分,从高到低
	读未提交  > 读已提交  > 可重复读  > 可串行化

- 按拦截程度 ,从高到底
	可串行化 > 可重复读  > 读已提交  >  读未提交
  • Read Uncommited【读未提交】
    指的是 : 一个事务可以读取到另一个事务还未提交的数据。 这就会引发 “脏读” 读取到的是数据库内存中的数据,而并非真正磁盘上的数据。
例子: 
1. 开启一个命令行窗口A, 开始事务,然后查询表中记录。  
设置当前窗口的事务隔离级别为:读未提交  命令如下:
	set session transaction isolation level read uncommitted;
2. 另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交
3. 在A窗口重新执行查询, 会看到B窗口没有提交的数据。 
  • Read Commited 【读已提交】
    与前面的读未提交刚好相反,这个隔离级别是 ,只能读取到其他事务已经提交的数据,那些没有提交的数据是读不出来的。屏蔽了脏读的情况,但是这会造成一个问题是: 前后读取到的结果不一样。 发生了不可重复!!!, 所谓的不可重复读,就是不能执行多次读取,否则出现结果不一样。
例子:
1. 开启一个命令行窗口A, 开始事务,然后查询表中记录。  
 设置当前窗口的事务隔离级别为:读已提交  命令如下:
	set session transaction isolation level read committed;
2. 另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交 
3. 在A窗口重新执行查询, 是不会看到B窗口刚才执行sql 语句的结果,因为它还没有提交。
4. 在B窗口执行提交。
5. 在A窗口中执行查看, 这时候才会看到B窗口已经修改的结果。
6. 但是这会造成一个问题是: 在A窗口中, 第一次查看数据和第二次查看数据,结果不一样。
  • Repeatable Read 【重复读】
    MySql 默认的隔离级别就是这个。该隔离级别, 可以让事务在自己的会话中重复读取数据,并且不会出现结果不一样的状况,即使其他事务已经提交了,也依然还是显示以前的数据。(读到的不是最新更新的数据,确保本事务不受其他事务影响)
例子:
1. 开启一个命令行窗口A, 开始事务,然后查询表中记录。  
2. 设置当前窗口的事务隔离级别为:重复读  命令如下:
	set session transaction isolation level repeatable read;
3. 另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交 
4. 在A窗口重新执行查询, 是不会看到B窗口刚才执行sql 语句的结果,因为它还没有提交。
5. 在B窗口执行提交。
6. 在A窗口中执行查看, 这时候查询结果,和以前的查询结果一致。不会发生改变。
  • Serializable 【可串行化(序列化)】
    该事务级别是*的事务级别了,如果有一个连接设置隔离级别为可串行化,那么谁先打开事务,谁就有了先执行的权利,谁后打开事务,就只能等着,等前面的那个事务,提交或者回滚后才会执行。这种隔离级别比前面几种都要强大一点,也就是前面几种的问题【脏读、不可重复读、幻读】都能够解决。但是都使用该隔离级别也会有些问题。 比如造成并发的性能问题。 其他的事务必须得等当前正在操作表的事务先提交,才能接着往下,否则只能一直在等着。所以比较少用,容易造成性能上的问题,效率比较低。
例子:
1. 开启一个命令行窗口A, 开始事务,然后查询表中记录。  
2. 设置当前窗口的事务隔离级别为:serializable 命令如下:
	set session transaction isolation level read serializable;
3. 另外在打开一个窗口B, 也开启事务, 然后执行 sql 语句, 但是不提交 
4. 在A窗口重新执行查询, 会卡主,没有任何信息显示。 
5. 在B窗口执行提交。
6. 在A窗口中执行查看, 这时候才会显示结果。

五、总结

  1. 在代码里面使用事务
	conn.setAutoCommit(false);
	conn.commit();
	conn.rollback();
  1. 事务只是针对连接连接对象,如果再开一个连接对象,那么那是默认的提交。
  2. 事务是会自动提交的。
  3. 安全隐患:
读
	脏读:一个事务读到了另一个事务未提交的数据。
	不可重复读:一个事务读到了另一个事务已提交的数据,造成前后两次查询结果不一致。
	幻读:一个事务读到了另一个事务insert的数据 ,造成前后查询结果不一致 。
写
	丢失更新。
  1. 隔离级别
读未提交
> 引发问题: 脏读 
读已提交
> 解决: 脏读 , 引发: 不可重复读
可重复读
> 解决: 脏读 、 不可重复读 , 未解决: 幻读
可串行化
> 解决: 脏读、 不可重复读 、 幻读。  导致:性能下降
  1. 补充:
mySql 默认的隔离级别是 可重复读
Oracle 默认的隔离级别是  读已提交
相关标签: 数据库