链接
数据库事务隔离级别“读未提交(Read Uncommitted)”代码演示
数据库事务隔离级别“读已提交(Read Committed)”代码演示
数据库事务隔离级别“可重复读(Repeatable Read)”代码演示
数据库事务隔离级别“串行化(Serializable)”代码演示
目录
一、简介
据库事务的隔离级别用于控制多个事务并发访问数据库时的相互影响程度,不同的隔离级别在性能和数据一致性上有不同的权衡。SQL 标准定义了四种隔离级别,从低到高分别为:读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。
串行化(Serializable)(行读作xing)是事务隔离级别的最高级别,它确保事务按照顺序依次执行,避免了并发事务之间的相互干扰。在这种隔离级别下,系统会强制所有事务以一种完全串行的方式执行,即一个事务必须在另一个事务完成之后才能开始。这种方式可以彻底避免脏读、不可重复读和幻读等问题,从而保证数据的一致性和可靠性。
1. 优点
数据一致性最高,能确保数据库的状态始终符合业务规则和逻辑:串行化隔离级别提供了最高的数据一致性保障。由于事务是按顺序依次执行的,不会出现并发事务之间的相互干扰,因此可以确保数据库的状态始终符合业务规则和逻辑。这意味着在一个事务执行期间,其他事务无法修改或读取其正在操作的数据,从而避免了任何潜在的数据不一致问题。
2. 缺点
并发性能最差,因为事务只能串行执行,会导致系统的吞吐量大幅下降,在高并发场景下可能会造成严重的性能瓶颈:尽管串行化隔离级别提供了最高的数据一致性,但它的并发性能却是最差的。由于事务必须按顺序依次执行,不能并行处理,这会导致系统的吞吐量大幅下降。特别是在高并发场景下,这种限制可能会导致严重的性能瓶颈,影响系统的响应时间和整体效率。
3. 示例描述
假设我们有两个事务 A 和事务 B,它们都需要对同一组账户余额进行操作。在串行化隔离级别下,事务 A 和事务 B 不能并发执行,必须一个接着一个执行。例如,事务 A 首先开始,并更新了一些账户余额。只有当事务 A 完成并提交后,事务 B 才能开始其操作。这样,事务 B 只能看到事务 A 提交后的最终结果,而不会受到事务 A 中间状态的影响。因此,无论事务 A 和事务 B 如何操作,都不会出现数据不一致的问题。然而,这种严格的顺序执行方式也意味着系统的并发处理能力受到了极大的限制,尤其是在高并发场景下,可能会显著降低系统的整体性能。
二、代码示例
在该示例中,我们重点关注客户端B的事务隔离级别,将在客户端B的代码中演示串行化(Serializable)这种隔离级别下,事务B的操作会在事务A完成后才能开始执行。
C# 版本
客户端A的代码
客户端A会启动一个事务,在事务中查询并更新账户余额。该示例中,事务A可以设置任意隔离级别。
using System;
using System.Data.SqlClient;
class ClientA
{
static void Main(string[] args)
{
string connectionString = "your_connection_string_here"; // 替换为你的数据库连接字符串
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// 开始事务A,设置隔离级别为Serializable
SqlTransaction transactionA = connection.BeginTransaction(System.Data.IsolationLevel.Serializable);
try
{
SqlCommand commandA = connection.CreateCommand();
commandA.Transaction = transactionA;
// 第一次查询账户余额大于100元的记录
commandA.CommandText = "SELECT COUNT(*) FROM Accounts WHERE Balance > 100";
int countBefore = (int)commandA.ExecuteScalar();
Console.WriteLine($"Client A: 初始查询到的记录数是 {countBefore} 条"); // 假设初始有10条记录
// 更新一条账户余额
commandA.CommandText = "UPDATE Accounts SET Balance = 300 WHERE AccountId = 1";
commandA.ExecuteNonQuery();
Console.WriteLine("Client A: 已将AccountId为1的账户余额更新为300元");
// 提交事务A
transactionA.Commit();
Console.WriteLine("Client A: 事务已提交");
}
catch (Exception ex)
{
Console.WriteLine("Client A异常: " + ex.Message);
transactionA.Rollback(); // 如果发生异常,回滚事务
}
}
}
}
客户端B的代码
客户端B会在客户端A执行期间尝试查询和更新账户余额,但由于B事务设置了串行化隔离级别,因此它必须等待事务A完成才能执行。
using System;
using System.Data.SqlClient;
class ClientB
{
static void Main(string[] args)
{
string connectionString = "your_connection_string_here"; // 替换为你的数据库连接字符串
using (SqlConnection connection = new SqlConnection(connectionString))
{
connection.Open();
// 开始事务B,设置隔离级别为Serializable
SqlTransaction transactionB = connection.BeginTransaction(System.Data.IsolationLevel.Serializable);
try
{
SqlCommand commandB = connection.CreateCommand();
commandB.Transaction = transactionB;
// 查询账户余额大于100元的记录
commandB.CommandText = "SELECT COUNT(*) FROM Accounts WHERE Balance > 100";
int countBefore = (int)commandB.ExecuteScalar();
Console.WriteLine($"Client B: 初始查询到的记录数是 {countBefore} 条"); // 应输出事务A提交后的结果
// 更新另一条账户余额
commandB.CommandText = "UPDATE Accounts SET Balance = 400 WHERE AccountId = 2";
commandB.ExecuteNonQuery();
Console.WriteLine("Client B: 已将AccountId为2的账户余额更新为400元");
// 提交事务B
transactionB.Commit();
Console.WriteLine("Client B: 事务已提交");
}
catch (Exception ex)
{
Console.WriteLine("Client B异常: " + ex.Message);
transactionB.Rollback(); // 如果发生异常,回滚事务
}
}
}
}
Java 版本
客户端A的代码
客户端A会启动一个事务,在事务中查询并更新账户余额。该示例中,事务A可以设置任意隔离级别。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class ClientA {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/your_database"; // 替换为你的数据库连接字符串
String user = "root";
String password = "password";
Connection conn = null;
Statement stmt = null;
try {
conn = DriverManager.getConnection(url, user, password);
// 设置隔离级别为Serializable
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
conn.setAutoCommit(false);
stmt = conn.createStatement();
// 第一次查询账户余额大于100元的记录
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Accounts WHERE Balance > 100");
if (rs.next()) {
int countBefore = rs.getInt(1);
System.out.println("Client A: 初始查询到的记录数是 " + countBefore + " 条"); // 假设初始有10条记录
}
// 更新一条账户余额
stmt.executeUpdate("UPDATE Accounts SET Balance = 300 WHERE AccountId = 1");
System.out.println("Client A: 已将AccountId为1的账户余额更新为300元");
// 提交事务A
conn.commit();
System.out.println("Client A: 事务已提交");
} catch (SQLException e) {
e.printStackTrace();
if (conn != null) {
try {
System.err.print("Transaction is being rolled back");
conn.rollback();
} catch (SQLException excep) {
excep.printStackTrace();
}
}
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
/* ignored */
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
/* ignored */
}
}
}
}
}
客户端B的代码
客户端B会在客户端A执行期间尝试查询和更新账户余额,但由于B事务设置了串行化隔离级别,因此它必须等待事务A完成才能执行。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class ClientB {
public static void main(String[] args) {
String url = "jdbc:mysql://localhost:3306/your_database"; // 替换为你的数据库连接字符串
String user = "root";
String password = "password";
Connection conn = null;
Statement stmt = null;
try {
conn = DriverManager.getConnection(url, user, password);
// 设置隔离级别为Serializable
conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
conn.setAutoCommit(false);
stmt = conn.createStatement();
// 查询账户余额大于100元的记录
ResultSet rs = stmt.executeQuery("SELECT COUNT(*) FROM Accounts WHERE Balance > 100");
if (rs.next()) {
int countBefore = rs.getInt(1);
System.out.println("Client B: 初始查询到的记录数是 " + countBefore + " 条"); // 应输出事务A提交后的结果
}
// 更新另一条账户余额
stmt.executeUpdate("UPDATE Accounts SET Balance = 400 WHERE AccountId = 2");
System.out.println("Client B: 已将AccountId为2的账户余额更新为400元");
// 提交事务B
conn.commit();
System.out.println("Client B: 事务已提交");
} catch (SQLException e) {
e.printStackTrace();
if (conn != null) {
try {
System.err.print("Transaction is being rolled back");
conn.rollback();
} catch (SQLException excep) {
excep.printStackTrace();
}
}
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException e) {
/* ignored */
}
}
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
/* ignored */
}
}
}
}
}