事务&ThreadLocal
- 理解事务的概念
- 理解脏读,不可重复读,幻读的概念及解决办法
- 能够在MySQL中使用事务
- 能够在JDBC中使用事务
- 能够在DBUtils中使用事务
- 能够理解ThreadLocal的作用
第一章 事务操作
事物概述
- 事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败
- 事务作用:保证在一个事务中多次SQL操作要么全都成功,要么全都失败.
- 事务前提:保证是在一个Connection下操作
1.1 mysql事务操作
sql语句 | 描述 |
---|---|
start transaction | 开启事务 |
commit | 提交事务 |
rollback | 回滚事务 |
- 准备数据
# 创建一个表:账户表.
create database webdb;
# 使用数据库
use webdb;
# 创建账号表
create table account(
id int primary key auto_increment,
name varchar(20),
money double
);
# 初始化数据
insert into account values (null,'jack',10000);
insert into account values (null,'rose',10000);
insert into account values (null,'tom',10000);
- 操作
- MYSQL中可以有两种方式进行事务的管理:
- 自动提交:MySql默认自动提交。及执行一条sql语句提交一次事务。
- 手动提交:先开启,再提交
- MYSQL中可以有两种方式进行事务的管理:
- 方式1:手动提交
start transaction;
update account set money=money-1000 where name='jack';
update account set money=money+1000 where name='rose';
commit;
#或者
rollback;
- 方式2:自动提交,通过修改mysql全局变量“autocommit”进行控制
show variables like '%commit%';
* 设置自动提交的参数为OFF:
set autocommit = 0; -- 0:OFF 1:ON
1.2 jdbc事务操作
Connection 对象的方法名 | 描述 |
---|---|
conn.setAutoCommit(false) | 开启事务 |
conn.commit() | 提交事务 |
conn.rollback() | 回滚事务 |
代码演示
/*
使用原生JDBC(6步)完成转账案例
update account set money=money-1000 where name='jack';
update account set money=money+1000 where name='rose';
事务:保证一组(一个conn)sql语句要么全部执行成功,要么全部执行失败
数据库是有事务概念,执行sql语句,就会开启事务,执行成功会提交事务,执行失败会回滚事务
mysql数据库事务默认都是自动的,自动开启事务,自动提交事务,自动回滚事务
oracle数据库事务默认都是手动的,手动开启事务,手动提交事务,手动回滚事务
注意:
事务一旦结束(提交,回滚),那么数据就永久的保存在数据库
在Connection接口中有操作事务的方法
void setAutoCommit(boolean autoCommit) 将此连接的自动提交模式设置为给定状态。
autoCommit - 为 true 表示启用自动提交模式;不写默认
为 false 表示禁用自动提交模式,
执行sql语句之前,手动的开启事务
void commit() 一组sql语句都执行成功,提交事务
void rollback() 一组sql中有一条执行失败,回滚事务; 把数据回滚到事务开启之前
在Connection接口中有设置事务隔离级别的方法
void setTransactionIsolation(int level) 试图将此 Connection 对象的事务隔离级别更改为给定的级别。
参数:
int level:
serializable(8) > repeatable read(4) > read committed(2) > read uncommitted(1)
Connection接口中定义的常量(建议)
int TRANSACTION_NONE = 0;
int TRANSACTION_READ_UNCOMMITTED = 1;
int TRANSACTION_READ_COMMITTED = 2;
int TRANSACTION_REPEATABLE_READ = 4;
int TRANSACTION_SERIALIZABLE = 8;
*/
public class Demo01JDBC {
public static void main(String[] args) {
Connection conn = null;
Statement stat = null;
try {
//注册驱动
Class.forName("com.mysql.jdbc.Driver");
//获取数据库连接对象Connection
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/day05","root","root");
//设置数据库的隔离级别为read committed(2)
//conn.setTransactionIsolation(2);
conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
//开启事务
conn.setAutoCommit(false);
//获取执行者对象Statement
stat = conn.createStatement();
//执行sql语句,获取结果
int row1 = stat.executeUpdate("update account set money=money-1000 where name='jack';");
System.out.println(0/0);
int row2 = stat.executeUpdate("update account set money=money+1000 where name='rose';");
//处理结果
if(row1>0 && row2>0){
System.out.println("转账成功!");
//提交事务
conn.commit();
}
} catch (Exception e) {
System.out.println("转账失败!");
e.printStackTrace();
try {
//回滚事务
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
//释放资源
if(stat!=null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
}
1.3 DBUtils事务操作
Connection对象的方法名 | 描述 |
---|---|
conn.setAutoCommit(false) | 开启事务 |
new QueryRunner() | 创建核心类,不设置数据源(手动管理连接) |
query(conn , sql , handler, params ) 或update(conn, sql , params) | 手动传递连接, 执行SQL语句CRUD |
DbUtils.commitAndCloseQuietly(conn) | 提交并关闭连接,不抛异常 |
DbUtils.rollbackAndCloseQuietly(conn) | 回滚并关闭连接,不抛异常 |
代码演示
//事务模板代码
public void demo02() throws SQLException{
// 获得连接
Connection conn = null;
try {
//#1 开始事务
conn.setAutoCommit(false);
//.... 加钱 ,减钱
//#2 提交事务
DbUtils.ommitAndCloseQuietly(conn);
} catch (Exception e) {
//#3 回滚事务
DbUtils.rollbackAndCloseQuietly(conn);
e.printStackTrace();
}
}
1.4 案例:JDBC事务分层(dao、service)传递Connection
分析
- 开发中,常使用分层思想
- 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统
- 不同层级结构彼此平等
- 分层的目的是:
- 解耦
- 可维护性
- 可扩展性
- 可重用性
代码实现
- 工具类C3P0Utils
/*
C3P0连接池的工具类XML版本:使用C3P0连接池获取数据库连接对象Connection并返回
连接池有一个规范接口
javax.sql.DataSource接口
定义了一个从连接池中获取连接的方法
Connection getConnection() 尝试建立与此 DataSource 对象所表示的数据源的连接。
C3P0实现了连接池的规范接口DataSource
com.mchange.v2.c3p0.ComboPooledDataSource类 implements DataSource接口
重写了getConnection方法
使用步骤:
1.在成员位置创建一个静态的ComboPooledDataSource对象
2.把c3p0-config.xml复制到当前模块的src下;
C3P0就会自动的解析xml,获取数据库连接信息给ComboPooledDataSource对象赋值
3.定义一个静态方法,从ComboPooledDataSource对象中获取数据库连接对象Connection并返回
4.定义一个释放资源的方法
*/
public class C3P0UtilsXML {
//1.在成员位置创建一个静态的ComboPooledDataSource对象
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
//3.定义一个静态方法,从ComboPooledDataSource对象中获取数据库连接对象Connection并返回
public static Connection getConnection(){
try {
return dataSource.getConnection();
} catch (SQLException e) {
/*
获取数据库连接对失败,让程序停止下来
把编译异常,转换为运行时异常
*/
throw new RuntimeException("获取数据库连接对象失败"+e);
}
}
//定义一个方法,返回连接池对象,给QueryRunner使用
public static DataSource getDataSource(){
return dataSource;
}
//4.定义一个释放资源的方法
public static void close(ResultSet rs, Statement stat, Connection conn){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stat!=null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();//把连接在归还给连接池
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
- c3p0-config.xml
<c3p0-config>
<!-- 使用默认的配置读取连接池对象 -->
<default-config>
<!-- 连接参数 -->
<property name="driverClass">com.mysql.jdbc.Driver</property>
<property name="jdbcUrl">jdbc:mysql://localhost:3306/day05</property>
<property name="user">root</property>
<property name="password">root</property>
<!-- 连接池参数 -->
<!-- 初始连接数 -->
<property name="initialPoolSize">5</property>
<!-- 最大连接数 -->
<property name="maxPoolSize">10</property>
<!-- 最大等待时间 -->
<property name="checkoutTimeout">2000</property>
<!-- 最大空闲回收时间 -->
<property name="maxIdleTime">1000</property>
</default-config>
</c3p0-config>
- 步骤1:编写入口程序
/*
创建转账案例的web层
使用Scanner获取用户输入的数据(付款人姓名,收款人姓名,转账金额)
创建AccountSerivce对象
调用转账方法,接收转账结果
对结果进行判断,给用户展示结果
*/
public class AccountWeb {
public static void main(String[] args) {
//使用Scanner获取用户输入的数据(付款人姓名,收款人姓名,转账金额)
Scanner sc = new Scanner(System.in);
System.out.print("请输入付款人姓名:");
String fromName = sc.next();
System.out.print("请输入收款人姓名:");
String toName = sc.next();
System.out.print("请输入转账金额:");
double money = sc.nextDouble();
//创建AccountSerivce对象
AccountService serivce = new AccountService();
//调用转账方法,接收转账结果
boolean b = serivce.transferAccount(fromName, toName, money);
//对结果进行判断,给用户展示结果
if (b){
System.out.println("转账成功!");
}else{
System.out.println("转账失败!");
}
}
}
- service层
/*
转账案例的Service层:接收web的传递的数据,调用dao层的方法,接收结果;把结果返回给web层
定义一个转账方法:
参数接收web传递的数据(付款人姓名,收款人姓名,转账金额)
使用C3P0连接池获取Connection
开启事务
创建AccountDao对象
调用减钱和加钱方法,接收结果
对结果进行判断
都执行成功,提交事务
有异常,回滚事务
把结果返回给web层
释放资源
*/
public class AccountService {
//定义一个转账方法
public boolean transferAccount(String fromName,String toName,double momey){
//使用C3P0连接池获取Connection
Connection conn = C3P0UtilsXML.getConnection();
//定义返回的结果
boolean flag = false;
try {
//开启事务
conn.setAutoCommit(false);
//创建AccountDao对象
AccountDao dao = new AccountDao();
//调用减钱和加钱方法,接收结果
int row1 = dao.fromAccount(conn, fromName, momey);
System.out.println(0/0);
int row2 = dao.toAccount(conn, toName, momey);
//对结果进行判断
if(row1>0 && row2>0){
flag = true;
//都执行成功,提交事务
conn.commit();
}
} catch (Exception e) {
e.printStackTrace();
//有异常,回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
//释放资源
DbUtils.closeQuietly(conn);
}
//把结果返回给web层
return flag;
}
}
- dao层
/*
创建转账案例的Dao层:用于对account表进行增删改查
注意:
一张表-->一个dao
定义两个方法:
一个减钱,一个加钱
*/
public class AccountDao {
/*
定义减钱方法
参数:
Connection conn:两个方法使用同一个Connection,从而保证使用同一个事务
String fromName:付款人姓名
double money:转账金额
返回值:
int:影响数据库的有效行数
*/
public int fromAccount(Connection conn,String fromName,double money) throws SQLException {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用update方法执行sql语句,接收结果
int row = qr.update(conn,"update account set money=money-? where name=?;",money,fromName);
//返回结果
return row;
}
/*
定义加钱方法
参数:
Connection conn:两个方法使用同一个Connection,从而保证使用同一个事务
String toName:收款人姓名
double money:转账金额
返回值:
int:影响数据库的有效行数
*/
public int toAccount(Connection conn,String toName,double money) throws SQLException {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用update方法执行sql语句,接收结果
int row = qr.update(conn,"update account set money=money+? where name= ?;",money,toName);
//返回结果
return row;
}
}
第二章 ThreadLocal
2.1分析
在“事务传递Connection参数案例”中,我们必须传递Connection对象,才可以完成整个事务操作。如果不传递参数,是否可以完成?在JDK中给我们提供了一个工具类:ThreadLocal,此类可以在一个线程中共享数据。
java.lang.ThreadLocal
该类提供了线程局部 (thread-local) 变量,用于在当前线程中共享数据。
2.2相关知识:ThreadLocal
java.lang.ThreadLocal 该类提供了线程局部(thread-local) 变量,用于在当前线程中共享数据。ThreadLocal工具类底层就是相当于一个Map,key存放的当前线程,value存放需要共享的数据。
举例
/*
java.lang.ThreadLocal<T>类:该类提供了线程局部 (thread-local) 变量。
ThreadLocal的底层原理:使用一个Map集合,对Map进行了封装
Map<当前线程Thread,Object> map = new HashMap<当前线程Thread,Object>();
当前线程Thread:static Thread currentThread()
在使用方法的时候,省略了Map集合的key,只使用了Map集合的vlaue,key默认就是用的当前正在执行的线程
程序在main方法中执行:main线程
程序在run方法中执行:新创建的线程
ThreadLocal构造方法:
ThreadLocal() 创建一个线程本地变量。
ThreadLocal的成员方法:
void set(T value) 往ThreadLocal中添加数据
此方法相当于Map集合的put方法
put(Thread.currentThread(),Value);
T get() 通过当前线程,获取当前线程保存的值
此方法相当于Map集合的get方法
value = get(Thread.currentThread());
void remove() 通过当前线程,移除ThreadLocal中保存的健值
此方法相当于Map集合的remove方法
remove(Thread.currentThread())
线程局部(自己) (thread-local) 变量。
哪个线程往ThreadLocal中存储的数据,只有哪个线程能使用,其他线程不能使用
mian线程存储的数据,只有main线程能使用
Thread-0线程存储的数据,只有Thread-0线程能使用
...
*/
public class Demo01ThreadLocal {
public static void main(String[] args) {
//创建ThreadLocal对象,泛型使用String
ThreadLocal<String> tl = new ThreadLocal<>();
//使用set方法往ThreadLocal中添加数据
//底层 map.put(main线程,"main-->添加的数据")
tl.set(Thread.currentThread().getName()+"-->添加的数据");
//使用get方法获取ThreadLocal中保存的数据
//底层 map.get(main线程)
String s = tl.get();
System.out.println(s);//main-->添加的数据
new Thread(new Runnable() {
@Override
public void run() {
//使用get方法获取ThreadLocal中保存的数据
//底层 map.get(Thread-0线程)
System.out.println(tl.get());//null
//使用set方法往ThreadLocal中添加数据
//底层 map.put(Thread-0线程,"main-->添加的数据")
tl.set(Thread.currentThread().getName()+"-->添加的数据");
System.out.println(tl.get());//Thread-0添加的数据
}
}).start();
}
}
使用ThreadLocal存储Connection
/*
好处:
一个线程存储的Connection,多次获取使用的是同一个
多线程并发的使用ThreadLocal存取数据,互相之间互不影响
*/
public class Demo02ThreadLocal {
public static void main(String[] args) {
//创建ThreadLocal对象,泛型使用Connection
ThreadLocal<Connection> tl = new ThreadLocal<>();
//使用连接池获取Connection
Connection conn1 = C3P0UtilsXML.getConnection();
//使用set方法往ThreadLocal中添加Connection
tl.set(conn1);
//使用get方法获取ThreadLocal中存储的Connection
Connection conn2 = tl.get();
System.out.println("---------------main方法中-->main线程--------------------");
System.out.println(conn1);//@2f8dad04
System.out.println(conn2);//@2f8dad04
System.out.println(conn1==conn2);//true
System.out.println(tl.get());//@2f8dad04
System.out.println(tl.get());//@2f8dad04
System.out.println(tl.get());//@2f8dad04
System.out.println(tl.get());//@2f8dad04
System.out.println(tl.get());//@2f8dad04
new Thread(new Runnable() {
@Override
public void run() {
//使用set方法往ThreadLocal中添加Connection
tl.set(C3P0UtilsXML.getConnection());
//使用get方法获取ThreadLocal中存储的Connection
System.out.println("---------------run方法中-->Thread-x线程--------------------");
System.out.println(tl.get());//@394bad33
System.out.println(tl.get());//@394bad33
System.out.println(tl.get());//@394bad33
System.out.println(tl.get());//@394bad33
System.out.println(tl.get());//@394bad33
System.out.println(tl.get());//@394bad33
System.out.println(tl.get());//@394bad33
System.out.println(tl.get());//@394bad33
}
}).start();
}
}
结论:向ThreadLocal对象中添加的数据只能在当前线程下使用。
结合案例使用
代码实现
工具类
/*
使用ThreadLocal优化程序
*/
public class C3P0UtilsXMLTL {
private static ComboPooledDataSource dataSource = new ComboPooledDataSource();
//创建一个静态的ThreadLocal对象,存储Connection
private static ThreadLocal<Connection> tl = new ThreadLocal<>();
public static Connection getConnection(){
//从ThreadLocal中获取Connection
Connection conn = tl.get();// get(当前线程)
if(conn==null){
try {
//从连接池中获取Connection
conn = dataSource.getConnection();
//把获取到conn存储到ThreadLocal-->初始化一个Connection
tl.set(conn); // put(当前线程,conn)
} catch (SQLException e) {
//将编译时异常 转换 运行时 , 以后开发中运行时异常使用比较多的。
throw new RuntimeException(e);
}
}
return conn;
}
public static DataSource getDataSource(){
return dataSource;
}
public static void close(ResultSet rs, Statement stat, Connection conn){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stat!=null){
try {
stat.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();//把连接在归还给连接池
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
service层
/*
转账案例的Service层:接收web的传递的数据,调用dao层的方法,接收结果;把结果返回给web层
定义一个转账方法:
参数接收web传递的数据(付款人姓名,收款人姓名,转账金额)
使用C3P0连接池获取Connection
开启事务
创建AccountDao对象
调用减钱和加钱方法,接收结果
对结果进行判断
都执行成功,提交事务
有异常,回滚事务
把结果返回给web层
释放资源
*/
public class AccountService {
//定义一个转账方法
public boolean transferAccount(String fromName,String toName,double momey){
//使用C3P0连接池获取Connection
Connection conn = C3P0UtilsXMLTL.getConnection();
//定义返回的结果
boolean flag = false;
try {
//开启事务
conn.setAutoCommit(false);
//创建AccountDao对象
AccountDao dao = new AccountDao();
//调用减钱和加钱方法,接收结果
int row1 = dao.fromAccount(fromName, momey);
int row2 = dao.toAccount(toName, momey);
//对结果进行判断
if(row1>0 && row2>0){
flag = true;
//都执行成功,提交事务
conn.commit();
}
} catch (Exception e) {
e.printStackTrace();
//有异常,回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
//释放资源
DbUtils.closeQuietly(conn);
}
//把结果返回给web层
return flag;
}
}
/*
转账案例的Service层:接收web的传递的数据,调用dao层的方法,接收结果;把结果返回给web层
定义一个转账方法:
参数接收web传递的数据(付款人姓名,收款人姓名,转账金额)
使用C3P0连接池获取Connection
开启事务
创建AccountDao对象
调用减钱和加钱方法,接收结果
对结果进行判断
都执行成功,提交事务
有异常,回滚事务
把结果返回给web层
释放资源
*/
public class AccountService2 {
//定义一个转账方法
public boolean transferAccount(String fromName,String toName,double momey){
//使用C3P0连接池获取Connection
Connection conn = C3P0UtilsXMLTL.getConnection();
//定义返回的结果
boolean flag = false;
try {
//开启事务
conn.setAutoCommit(false);
//创建AccountDao对象
AccountDao dao = new AccountDao();
//调用减钱和加钱方法,接收结果
int row1 = dao.fromAccount(fromName, momey);
System.out.println(0/0);
int row2 = dao.toAccount(toName, momey);
//对结果进行判断
if(row1>0 && row2>0){
flag = true;
//都执行成功,提交事务
conn.commit();
}
} catch (Exception e) {
e.printStackTrace();
//有异常,回滚事务
try {
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
//释放资源
DbUtils.closeQuietly(conn);
}
//把结果返回给web层
return flag;
}
}
dao层
/*
创建转账案例的Dao层:用于对account表进行增删改查
注意:
一张表-->一个dao
定义两个方法:
一个减钱,一个加钱
*/
public class AccountDao {
/*
定义减钱方法
参数:
String fromName:付款人姓名
double money:转账金额
返回值:
int:影响数据库的有效行数
*/
public int fromAccount(String fromName,double money) throws SQLException {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用update方法执行sql语句,接收结果
int row = qr.update(C3P0UtilsXMLTL.getConnection(),"update account set money=money-? where name=?;",money,fromName);
//返回结果
return row;
}
/*
定义加钱方法
参数:
String toName:收款人姓名
double money:转账金额
返回值:
int:影响数据库的有效行数
*/
public int toAccount(String toName,double money) throws SQLException {
//创建QueryRunner对象
QueryRunner qr = new QueryRunner();
//调用update方法执行sql语句,接收结果
int row = qr.update(C3P0UtilsXMLTL.getConnection(),"update account set money=money+? where name= ?;",money,toName);
//返回结果
return row;
}
}
编写入口程序
/*
创建转账案例的web层
使用Scanner获取用户输入的数据(付款人姓名,收款人姓名,转账金额)
创建AccountSerivce对象
调用转账方法,接收转账结果
对结果进行判断,给用户展示结果
*/
public class AccountWeb {
public static void main(String[] args) {
//使用Scanner获取用户输入的数据(付款人姓名,收款人姓名,转账金额)
/*Scanner sc = new Scanner(System.in);
System.out.print("请输入付款人姓名:");
String fromName = sc.next();
System.out.print("请输入收款人姓名:");
String toName = sc.next();
System.out.print("请输入转账金额:");
double money = sc.nextDouble();*/
//创建AccountSerivce对象
AccountService serivce = new AccountService();
//调用转账方法,接收转账结果
boolean b = serivce.transferAccount("jack", "rose", 1000);
//对结果进行判断,给用户展示结果
if (b){
System.out.println("jackToRose转账成功!");
}else{
System.out.println("jackToRose转账失败!");
}
new Thread(new Runnable() {
@Override
public void run() {
//创建AccountSerivce对象
AccountService2 serivce = new AccountService2();
//调用转账方法,接收转账结果
boolean b = serivce.transferAccount("jack", "tom", 1000);
//对结果进行判断,给用户展示结果
if (b){
System.out.println("jackToTom转账成功!");
}else{
System.out.println("jackToTom转账失败!");
}
}
}).start();
}
}
第三章 事务总结
3.1 事务特性:ACID
- 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
- 一致性(Consistency)事务前后数据的完整性必须保持一致。
- 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
- 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
3.2 并发访问问题
如果不考虑隔离性,事务存在3中并发访问问题。
- 脏读:一个事务读到了另一个事务未提交的数据.
- 不可重复读:一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结
果不一致。
- 虚读 /幻读:一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果
不一致。
3.3 隔离级别:解决问题
- 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
- read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
a)存在:3个问题(脏读、不可重复读、虚读)。
b)解决:0个问题 - read committed 读已提交,一个事务读到另一个事务已经提交的数据。
a)存在:2个问题(不可重复读、虚读)。
b)解决:1个问题(脏读) - repeatable read:可重复读,在一个事务中读到的数据始终保持一致,无论另一个事务是否提交。
a)存在:1个问题(虚读)。
b)解决:2个问题(脏读、不可重复读) - serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
a)存在:0个问题。
b)解决:3个问题(脏读、不可重复读、虚读)
- 安全和性能对比
- 安全性:
serializable > repeatable read > read committed > read uncommitted
- 性能:
serializable < repeatable read < read committed < read uncommitted
- 安全性:
- 常见数据库的默认隔离级别
- MySql:
repeatable read
- Oracle:
read committed
- MySql:
3.4 演示演示
- 隔离级别演示参考:资料/隔离级别操作过程.doc【增强内容,了解】
- 查询数据库的隔离级别
show variables like '%isolation%';
或
select @@tx_isolation;
- 设置数据库的隔离级别
set session transactionisolation level
级别字符串- 级别字符串:
readuncommitted
read committed
repeatable read
serializable
- 例如:
set session transaction isolation level read uncommitted;
- 读未提交:readuncommitted
- A窗口设置隔离级别
- AB同时开始事务
- A 查询
- B 更新,但不提交
- A 再查询?-- 查询到了未提交的数据
- B 回滚
- A 再查询?-- 查询到事务开始前数据
- A窗口设置隔离级别
- 读已提交:read committed
- A窗口设置隔离级别
- AB同时开启事务
- A查询
- B更新、但不提交
- A再查询?–数据不变,解决问题【脏读】
- B提交
- A再查询?–数据改变,存在问题【不可重复读】
- A窗口设置隔离级别
- 可重复读:repeatable read
- A窗口设置隔离级别
- AB 同时开启事务
- A查询
- B更新, 但不提交
- A再查询?–数据不变,解决问题【脏读】
- B提交
- A再查询?–数据不变,解决问题【不可重复读】
- A提交或回滚
- A再查询?–数据改变,另一个事务
- A窗口设置隔离级别
- 串行化:serializable
- A窗口设置隔离级别
- AB同时开启事务
- A查询
- B更新?–等待(如果A没有进一步操作,B将等待超时)
- A回滚
- B 窗口?–等待结束,可以进行操作