SQL语句中的事务控制
事务是由一步或者几步数据库操作序列组成的逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行,程序和事务时两个不同的概念,通常来说一段程序可以包含多个事务,事务具备4个特性即原子性、一致性、隔离性和持续性,统称为事务的ACID特性
ACID特性
- 原子性(Atomicity):事务是应用中最小的执行单位,具有不可再分的特征,它是应用中不可再分的最小逻辑执行体
- 一致性(Consitency):事务执行的结果,必须使数据库从一个一致性状态,变成另一个一致性状态,当数据库只包含事务成功提交的结果时,数据库处于一致性状态,如果系统运行发生中断,某个事务尚未完成而被迫中断,而该未完成的事务对数据库所做的修改已被写入数据库,这样数据库就处于一种不正确的状态或者说是不一致的状态;如果全部执行成功,数据库处于一致性状态,如果执行了一部分而另一部分未成功执行则数据库处于不一致状态,因此一致性是通过原子性来保证的
- 隔离性(Isolation):各个事务的执行互不干扰,任意一个事务的内部操作对其他并发的事务都是隔离的,也就是说并发执行的事务之间不能看到对方的中间状态,并发执行的事务之间不能互相影响
- 持续性(Durability):持续性也成为持久性,说的是事务一旦提交,对数据所做的任何改变都要记录到永久存储器中,通常就是保存到物理数据库中。
数据库事务的组成
- 一组DML语句,经过这组DML语句修改后的数据将保持较好的一致性
- 一条DDL语句
- 一条DCL语句
DDL和DCL语句最多只能有一条,因为DDL和DCL语句都会导致事务立即提交
提交事务
当事务所包含的全部数据库操作都成功执行后,应该提交(commit)事务,使这些修改永久生效,事务的提交有两中方式
- 显示提交:使用commit
- 自动提交:执行DDL或DCL语句,或者程序正常退出
事务回滚
当事物所包含的任意一个数据库操作执行失败后,应该回滚(rollback)事务,使该事务中所做的修改全部失效,事务回滚有两种方式
- 显示回滚:使用rollback
- 自动回滚:系统错误或者强行退出
MySQL事务
MySQL默认关闭事务(即打开自动提交),在默认情况下在MySQL控制台输入一条DML语句,这条DML语句将会立即保存到数据库里,MySQL开启对事务的支持,可以通过显示调用如下命令
SET AUTOCOMMIT = {0|1} 0为关闭自动提交,也就开启了对事务的支持
自动提交和开启事务恰好相反,如果开启自动提交就是关闭事务,关闭自动提交就是开启事务
- 一旦在MySQL的命令行窗口中输入set autocommit=0开启了事务,该命令行窗口里的所有DML语句都不会立即生效,上一个事务结束收第一条DML语句将开始一个新的事务,而后续执行的所有SQL语句都处于该事务中,除非显示使用commit来提交事务,或者正常退出,或者运行DDL或DCL语句导致事务隐式提交
- 也可以使用rollback回滚来结束事务,使用rollback结束事务将导致本次事务中DML语句所做的修改全部失效
一个MySQL命令行窗口代表一次链接Session,在该窗口里设置set autocommit=0相当于关闭了该链接Session的自动提交,对其他链接不会有任何影响
临时性事务
如果不想关闭整个命令行窗口的自动提交,而只是临时性的开启事务,则可以使用MySQL提供的start transaction或begin两个命令,他们都表示临时性的开启一次事务,处于start transaction或begin后的DML语句不会立即生效,除非使用commit显示提交事务或者执行DDL或DCL语句来隐式的提交事务
begin;
insert into davieyang_test values(null, 'xxx', 1);
insert into davieyang_test values(null, 'yyy', 1);
insert into davieyang_test values(null, 'zzz', 1);
select * from davieyang_test; # 此次查询将看到上边的三条insert结果,但如果新开一个MySQL命令行窗口将看不到这3条insert
rollback; # 回滚事务中的全部修改
select * from davieyang_test; # 再执行查询,那3条insert就看不到了,恢复到begin;之前了
提交:无论是显示提交还是隐式提交都会结束当前事务
回滚:无论是显示回滚还是隐式回滚都会结束当前事务
事务的中间点
MySQL提供了savepoin来设置事务的中间点,通过使用savepoint设置事务的中间点可以让事务回滚到指定中间点,而不是回滚全部事务
savepoint a;
rollback to a;
普通的提交、回滚都会结束当前事务,但回滚到指定中间点因为依然处于事务当中,所以不会结束当前事务
JDBC编程中的事务控制
JDBC链接也提供了事务支持,JDBC链接的事务支持由Connection提供,它默认也打开了自动提交,即关闭事务,如此每条SQL语句一旦执行,便会立即提交到数据库,永久生效无法队医进行回滚操作
可以调用connection的setAutoCommit()方法来关闭自动提交即开启事务
conn.setAutoCommit(false);
还可以调用connection的getAutoCommit()方法来返回该链接的自动提交模式
一旦事务开始之后,程序可以像平常一样创建Statement对象,创建了Statement对象后,可以执行任意多条DML语句
stmt.executeUpdate(...);
stmt.executeUpdate(...);
stmt.executeUpdate(...);
stmt.executeUpdate(...);
这些语句虽然被执行了,但语句所做的修改不会生效,因为事务还没有结束,程序可以调用connection的commit()方法来提交事务
conn.commit();
如果任意一条SQL执行失败,则应该调用connection的rollback()方法来回滚事务
conn.rollback();
实际上,当Connection遇到一个未处理的SQLException异常时候,系统会非正常退出,事务也会自动回滚,但如果程序捕获了该异常,则需要在异常处理块中显示的回滚事务
import java.sql.*;
import java.io.*;
import java.util.*;
public class TransactionTest
{
private String driver;
private String url;
private String user;
private String pass;
public void initParam(String paramFile) throws Exception
{
// 使用Properties类来加载属性文件
var props = new Properties();
props.load(new FileInputStream(paramFile));
driver = props.getProperty("driver");
url = props.getProperty("url");
user = props.getProperty("user");
pass = props.getProperty("pass");
}
public void insertInTransaction(String[] sqls) throws Exception
{
// 加载驱动
Class.forName(driver);
try (
Connection conn = DriverManager.getConnection(url, user, pass))
{
// 关闭自动提交,开启事务
conn.setAutoCommit(false);
try (
// 使用Connection来创建一个Statment对象
Statement stmt = conn.createStatement())
{
// 循环多次执行SQL语句
for (var sql : sqls)
{
stmt.executeUpdate(sql);
}
}
// 提交事务
conn.commit();
}
}
public static void main(String[] args) throws Exception
{
var tt = new TransactionTest();
tt.initParam("mysql.ini");
var sqls = new String[]{
"insert into student_table values(null, 'aaa', 1)",
"insert into student_table values(null, 'bbb', 1)",
"insert into student_table values(null, 'ccc', 1)",
// 下面这条SQL语句将会违反外键约束,
// 因为teacher_table中没有ID为5的记录。
"insert into student_table values(null, 'ccc', 5)"
};
tt.insertInTransaction(sqls);
}
}
程序中只是开启事务,提交事务,并没有回滚事务的代码,当程序执行到"insert into student_table values(null, 'ccc', 5)"时,会引起外键约束异常,并且未处理该异常,引起程序非正常结束,所以事务自动回滚
CMD
创建cmd文件,由windows执行
set CLASSPATH=%CLASSPATH%;../mysql-connector-java-8.0.13.jar
java TransactionTest
cmd
connection也提供了中间点方法setSavepoint(),设置中间点有两种方式
- Savepoint setSavepoint(): 在当前事务中创建一个未命名的中间点,并返回代表该中间点的Savepoint对象
- Savepoint setSavepoint(String name):在当前事务中创建一个具有指定名称的中间点,并返回代表该中间点的Savepoint对象
connection提供了回滚中间点方法rollback(Savepoint savepoint),它是根据中间点对象回滚的并非中间点名称
Java8批量更新
JDBC提供了一个批量更新的功能,使用批量更新时,多条SQL语句将被作为一批操作同时收集提交,但批量更新必须得到底层数据库的支持(可以通过调用DatabaseMetaData的supportsBatchUpdates()方法来查看底层数据库是否支持批量更新)
Statment stmt = conn.createStatement();
stmt.addBatch(sql1);
stmt.addBatch(sql2);
stmt.addBatch(sql3);
stmt.addBatch(sql4);
stmt.addBatch(sql5);
...
stmt.executeLargeBatch();
- 可以使用executeBatch()方法执行也可以使用executeLargeBatch()方法,区别在于如果批量操作中任何一条SQL语句影响的记录条数可能超过Integer.MAX_VALUE则应该使用executeLargeBatch()
- 执行executeLargeBatch()将返回一个long[]数组,使用Statement执行DDL、DML语句都将返回一个long值,执行多条DDL、DML语句则返回过个long值,它们组成了这个long[]数组
- 如果在addBatch()方法中添加了select语句,程序将直接报错
为了让批量操作可以正确的处理错误,必须把批量执行的操作视为单个事务,如果批量更新在执行过程中失败,则让事务回滚到批量操作之前的状态,为此,程序应该在开始批量操作前先关闭自动提交开启事务,然后收集更新语句,当批量操作执行结束后提交事务,并恢复之前的自动提交模式
import java.sql.*;
import java.io.*;
import java.util.*;
public class BatchTest
{
private String driver;
private String url;
private String user;
private String pass;
public void initParam(String paramFile) throws Exception
{
// 使用Properties类来加载属性文件
var props = new Properties();
props.load(new FileInputStream(paramFile));
driver = props.getProperty("driver");
url = props.getProperty("url");
user = props.getProperty("user");
pass = props.getProperty("pass");
}
public void insertBatch(String[] sqls) throws Exception
{
// 加载驱动
Class.forName(driver);
try (
Connection conn = DriverManager.getConnection(url, user, pass))
{
// 关闭自动提交,开启事务
conn.setAutoCommit(false);
// 保存当前的自动的提交模式
boolean autoCommit = conn.getAutoCommit();
// 关闭自动提交
conn.setAutoCommit(false);
try (
// 使用Connection来创建一个Statement对象
Statement stmt = conn.createStatement())
{
// 循环多次执行SQL语句
for (var sql : sqls)
{
stmt.addBatch(sql);
}
// 同时提交所有的SQL语句
stmt.executeLargeBatch();
// 提交修改
conn.commit();
// 恢复原有的自动提交模式
conn.setAutoCommit(autoCommit);
}
// 提交事务
conn.commit();
}
}
public static void main(String[] args) throws Exception
{
var tt = new TransactionTest();
tt.initParam("mysql.ini");
var sqls = new String[]{
"insert into student_table values(null, 'aaa', 1)",
"insert into student_table values(null, 'bbb', 1)",
"insert into student_table values(null, 'ccc', 1)",
};
tt.insertInTransaction(sqls);
}
}
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/select_test?useSSL=false&serverTimezone=UTC
user=root
pass=32147
CMD
创建cmd文件,由windows执行
set CLASSPATH=%CLASSPATH%;../mysql-connector-java-8.0.13.jar
java BatchTest
cmd
本文详细讲解了SQL事务的构成、ACID特性、事务控制方法,包括自动提交与显式提交/回滚,以及如何在JDBC编程中管理和处理事务。特别关注了MySQL事务、批量更新和中间点操作,确保数据一致性。
1090

被折叠的 条评论
为什么被折叠?



