在 SQL 中,事务(Transaction)是数据库操作的基本单位,用于确保一组相关的数据库操作要么全部成功完成,要么全部失败并回滚。事务提供了一种机制来保证数据的一致性和完整性,尤其是在执行多个相关操作时。
事务的四个特性(ACID)
事务具有四个关键特性,通常称为 ACID 特性:
-
原子性(Atomicity):
- 事务中的所有操作被视为一个不可分割的单元。要么所有操作都成功执行,要么没有任何操作被执行。如果事务中的任何一个操作失败,整个事务将被回滚,恢复到事务开始之前的状态。
-
一致性(Consistency):
- 事务必须确保数据库从一个一致状态转换到另一个一致状态。事务执行前后,数据库的完整性约束必须保持不变。例如,事务不能破坏外键约束或唯一性约束。
-
隔离性(Isolation):
- 事务之间的执行是隔离的,即一个事务的中间状态对其他事务是不可见的。事务隔离级别决定了多个并发事务之间的可见性和交互方式。常见的隔离级别包括:
- 读未提交(Read Uncommitted):允许读取未提交的数据,可能会导致脏读、不可重复读和幻读。
- 读已提交(Read Committed):只允许读取已提交的数据,防止脏读,但仍然可能出现不可重复读和幻读。
- 可重复读(Repeatable Read):确保在同一事务中多次读取相同的数据时,结果一致,防止脏读和不可重复读,但仍然可能出现幻读。
- 可序列化(Serializable):最高的隔离级别,确保事务完全隔离,防止脏读、不可重复读和幻读。所有事务按顺序执行,避免并发问题。
- 事务之间的执行是隔离的,即一个事务的中间状态对其他事务是不可见的。事务隔离级别决定了多个并发事务之间的可见性和交互方式。常见的隔离级别包括:
-
持久性(Durability):
- 一旦事务成功提交,其对数据库的更改将永久保存,即使系统发生故障(如断电或崩溃),这些更改也不会丢失。
在 C# 中使用 SQLite 事务
在 C# 中,使用 SqliteTransaction
类可以显式地管理事务。通过事务,你可以确保多个 SQL 操作作为一个整体执行,避免部分操作成功而部分操作失败的情况。
示例代码
以下是一个使用 SqliteTransaction
的示例,展示了如何在 C# 中执行带有事务的多个 SQL 操作:
using System;
using System.Data.SQLite; // 确保使用正确的命名空间
using System.Threading;
using System.Threading.Tasks;
public class DatabaseService
{
private readonly string _connectionString;
public DatabaseService(string connectionString)
{
_connectionString = connectionString;
}
/// <summary>
/// 异步执行带有事务的多个 SQL 语句。
/// </summary>
/// <param name="sqlStatements">要执行的 SQL 语句数组。</param>
/// <param name="parameters">SQL 语句的参数数组。</param>
/// <param name="cancellationToken">用于取消异步操作的取消令牌。</param>
/// <returns>受影响的总行数。</returns>
/// <exception cref="SqliteException">在执行命令时发生数据库相关错误。</exception>
/// <exception cref="Exception">在执行命令时发生其他类型的错误。</exception>
public async Task<int> ExecuteWithTransactionAsync(
string[] sqlStatements,
SQLiteParameter[][] parametersArray,
CancellationToken cancellationToken = default)
{
int totalRowsAffected = 0;
try
{
using var connection = new SQLiteConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
// 开始事务
using var transaction = await connection.BeginTransactionAsync();
try
{
foreach (var (sql, parameters) in sqlStatements.Zip(parametersArray ?? Array.Empty<SQLiteParameter[]>(), (s, p) => (s, p)))
{
using var command = new SQLiteCommand(sql, connection, transaction);
if (parameters != null && parameters.Length > 0)
{
command.Parameters.AddRange(parameters);
}
// 执行每个 SQL 语句,并累加受影响的行数
int rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken);
totalRowsAffected += rowsAffected;
}
// 提交事务
await transaction.CommitAsync();
}
catch (Exception)
{
// 如果任何操作失败,回滚事务
await transaction.RollbackAsync();
throw;
}
}
catch (SqliteException ex)
{
// 记录数据库相关的异常
Console.WriteLine($"SQLite error: {ex.Message}\nSQL Statements: {string.Join(";", sqlStatements)}");
throw;
}
catch (OperationCanceledException ex)
{
// 记录取消操作的异常
Console.WriteLine($"Operation was canceled: {ex.Message}");
throw;
}
catch (Exception ex)
{
// 记录其他类型的异常
Console.WriteLine($"Unexpected error: {ex.Message}\nSQL Statements: {string.Join(";", sqlStatements)}");
throw;
}
return totalRowsAffected;
}
}
代码说明
-
事务的开始和提交:
- 使用
connection.BeginTransactionAsync()
开始一个新的事务。 - 在所有 SQL 语句成功执行后,调用
transaction.CommitAsync()
提交事务,使所有更改永久生效。
- 使用
-
事务的回滚:
- 如果在执行过程中发生任何异常,调用
transaction.RollbackAsync()
回滚事务,撤销所有未提交的更改,确保数据库保持一致状态。
- 如果在执行过程中发生任何异常,调用
-
多个 SQL 语句的执行:
- 使用
foreach
循环遍历sqlStatements
和parametersArray
,逐个执行每个 SQL 语句,并将受影响的行数累加到totalRowsAffected
中。
- 使用
-
取消支持:
- 将
cancellationToken
传递给OpenAsync
、BeginTransactionAsync
、ExecuteNonQueryAsync
和CommitAsync
,确保整个操作都可以被取消。
- 将
-
异常处理:
- 捕获并记录不同类型的异常(
SqliteException
、OperationCanceledException
和其他异常),并在适当的情况下重新抛出异常,以便调用方可以处理。
- 捕获并记录不同类型的异常(
事务的好处
-
数据一致性:
- 事务确保多个相关操作作为一个整体执行,避免部分操作成功而部分操作失败的情况。这对于维护数据库的一致性至关重要,尤其是在涉及多个表或多条记录的操作中。
-
错误恢复:
- 如果事务中的任何一个操作失败,所有未提交的更改都会被回滚,确保数据库不会处于不一致的状态。这有助于防止数据损坏或不完整。
-
并发控制:
- 事务提供了隔离性,确保多个并发操作不会相互干扰。通过设置适当的隔离级别,你可以控制事务之间的可见性和交互方式,避免并发问题(如脏读、不可重复读和幻读)。
-
简化复杂操作:
- 事务使得复杂的数据库操作更加简单和可靠。你可以在事务中执行多个插入、更新或删除操作,而不需要担心个别操作的失败会影响整个操作的结果。
事务的性能考虑
-
事务的开销:虽然事务提供了强大的功能,但它也会带来一定的性能开销。每次开始和提交事务都需要额外的资源(如日志记录和锁定)。因此,在设计应用程序时,应尽量减少不必要的事务使用,特别是在高并发场景下。
-
批量操作:如果你需要执行大量类似的 SQL 语句(如批量插入或更新),可以考虑将它们放在一个事务中,以提高性能。事务可以减少与数据库的交互次数,并且一次提交所有更改。
-
隔离级别的选择:根据你的业务需求,选择合适的隔离级别。较高的隔离级别(如
Serializable
)可以提供更强的隔离性,但也可能导致更多的锁争用和性能下降。较低的隔离级别(如Read Committed
)可以提高并发性能,但可能会引入一些并发问题(如不可重复读)。
总结
事务是确保数据库操作一致性和完整性的强大工具。通过在 C# 中使用 SqliteTransaction
,你可以轻松地管理多个 SQL 操作,确保它们作为一个整体执行。事务的 ACID 特性保证了数据的可靠性,而合理的事务设计和隔离级别选择可以帮助你在性能和安全性之间找到平衡。
通过理解事务的工作原理和最佳实践,你可以编写更健壮、高效的数据库应用程序。
SQLite 中执行事务的步骤
在 SQLite 中执行事务的步骤相对简单,但确保正确使用事务可以大大提高数据库操作的安全性和性能。SQLite 事务允许你将多个 SQL 操作打包成一个原子单元,确保要么所有操作都成功完成,要么没有任何操作被执行。以下是 SQLite 中执行事务的详细步骤和最佳实践。
1. 开始事务
要开始一个事务,你可以使用 BEGIN TRANSACTION
或其变体来显式地启动事务。SQLite 支持三种类型的事务:
BEGIN TRANSACTION
:默认情况下,这会启动一个延迟(deferred)事务,即 SQLite 会在必要时锁定表。BEGIN IMMEDIATE TRANSACTION
:立即获取一个写锁,防止其他写事务开始,但允许读事务继续。BEGIN EXCLUSIVE TRANSACTION
:立即获取一个独占锁,阻止其他读和写事务。
示例:
BEGIN TRANSACTION;
或者,使用更严格的锁定模式:
BEGIN IMMEDIATE TRANSACTION;
-- 或者
BEGIN EXCLUSIVE TRANSACTION;
2. 执行 SQL 操作
在事务开始后,你可以执行任意数量的 SQL 语句(如 INSERT
、UPDATE
、DELETE
等)。这些操作会被视为事务的一部分,直到你提交或回滚事务。
示例:
INSERT INTO Users (Id, Name) VALUES (1, 'Alice');
UPDATE Orders SET Status = 'Shipped' WHERE UserId = 1;
DELETE FROM Logins WHERE UserId = 1;
3. 提交事务
如果你确定所有操作都成功执行,并且希望将更改永久保存到数据库中,可以使用 COMMIT
来提交事务。提交后,所有更改将被写入数据库,并且事务结束。
示例:
COMMIT;
4. 回滚事务
如果在事务执行过程中发生错误,或者你决定不保存更改,可以使用 ROLLBACK
来回滚事务。回滚会撤销所有未提交的更改,恢复到事务开始之前的状态。
示例:
ROLLBACK;
5. 自动提交模式
SQLite 默认处于自动提交模式(autocommit mode),即每个 SQL 语句都会被视为一个独立的事务,并在执行后自动提交。如果你想在多个语句之间保持一致性,必须显式地开始事务。一旦你显式地开始了一个事务,SQLite 会进入手动提交模式,直到你显式地调用 COMMIT
或 ROLLBACK
。
示例:
-- 自动提交模式下,每个语句都是独立的事务
INSERT INTO Users (Id, Name) VALUES (1, 'Alice'); -- 自动提交
UPDATE Orders SET Status = 'Shipped' WHERE UserId = 1; -- 自动提交
6. 使用 C# 执行事务
在 C# 中,使用 SqliteTransaction
类可以更方便地管理 SQLite 事务。以下是一个完整的示例,展示了如何在 C# 中执行带有事务的多个 SQL 操作。
示例代码:
using System;
using System.Data.SQLite; // 确保使用正确的命名空间
using System.Threading;
using System.Threading.Tasks;
public class DatabaseService
{
private readonly string _connectionString;
public DatabaseService(string connectionString)
{
_connectionString = connectionString;
}
/// <summary>
/// 异步执行带有事务的多个 SQL 语句。
/// </summary>
/// <param name="sqlStatements">要执行的 SQL 语句数组。</param>
/// <param name="parametersArray">SQL 语句的参数数组。</param>
/// <param name="cancellationToken">用于取消异步操作的取消令牌。</param>
/// <returns>受影响的总行数。</returns>
public async Task<int> ExecuteWithTransactionAsync(
string[] sqlStatements,
SQLiteParameter[][] parametersArray,
CancellationToken cancellationToken = default)
{
int totalRowsAffected = 0;
try
{
using var connection = new SQLiteConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
// 开始事务
using var transaction = await connection.BeginTransactionAsync();
try
{
foreach (var (sql, parameters) in sqlStatements.Zip(parametersArray ?? Array.Empty<SQLiteParameter[]>(), (s, p) => (s, p)))
{
using var command = new SQLiteCommand(sql, connection, transaction);
if (parameters != null && parameters.Length > 0)
{
command.Parameters.AddRange(parameters);
}
// 执行每个 SQL 语句,并累加受影响的行数
int rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken);
totalRowsAffected += rowsAffected;
}
// 提交事务
await transaction.CommitAsync();
}
catch (Exception)
{
// 如果任何操作失败,回滚事务
await transaction.RollbackAsync();
throw;
}
}
catch (SqliteException ex)
{
// 记录数据库相关的异常
Console.WriteLine($"SQLite error: {ex.Message}\nSQL Statements: {string.Join(";", sqlStatements)}");
throw;
}
catch (OperationCanceledException ex)
{
// 记录取消操作的异常
Console.WriteLine($"Operation was canceled: {ex.Message}");
throw;
}
catch (Exception ex)
{
// 记录其他类型的异常
Console.WriteLine($"Unexpected error: {ex.Message}\nSQL Statements: {string.Join(";", sqlStatements)}");
throw;
}
return totalRowsAffected;
}
}
7. 注意事项
a. 事务的隔离性
SQLite 的事务隔离级别是 串行化(Serializable),这意味着在同一时刻只有一个写事务可以进行,而其他写事务必须等待当前事务完成。读事务可以在写事务进行时继续,但它们看到的是事务开始时的快照。
b. 事务的持久性
SQLite 使用写前日志(Write-Ahead Logging, WAL)模式来提高并发性能并确保事务的持久性。WAL 模式允许多个读事务同时进行,而不会阻塞写事务。你可以通过设置 PRAGMA journal_mode = WAL;
来启用 WAL 模式。
c. 事务的超时
如果你的事务长时间持有锁,可能会导致其他事务无法进行。为了避免这种情况,SQLite 提供了 busy_timeout
参数,允许你在遇到锁冲突时等待一段时间再重试。你可以通过以下方式设置超时时间(以毫秒为单位):
PRAGMA busy_timeout = 5000; -- 设置超时时间为 5 秒
d. 事务的性能
虽然事务提供了强大的功能,但它也会带来一定的性能开销。每次开始和提交事务都需要额外的资源(如日志记录和锁定)。因此,在设计应用程序时,应尽量减少不必要的事务使用,特别是在高并发场景下。
e. 批量操作
如果你需要执行大量类似的 SQL 语句(如批量插入或更新),可以考虑将它们放在一个事务中,以提高性能。事务可以减少与数据库的交互次数,并且一次提交所有更改。
8. 示例:完整的事务流程
以下是一个完整的事务流程示例,展示了如何在 SQLite 中执行多个 SQL 操作,并确保它们作为一个整体成功或失败。
SQL 语句:
BEGIN TRANSACTION;
-- 插入新用户
INSERT INTO Users (Id, Name) VALUES (1, 'Alice');
-- 更新订单状态
UPDATE Orders SET Status = 'Shipped' WHERE UserId = 1;
-- 删除登录记录
DELETE FROM Logins WHERE UserId = 1;
-- 提交事务
COMMIT;
C# 代码:
using (var connection = new SQLiteConnection("Data Source=mydatabase.db"))
{
connection.Open();
// 开始事务
using (var transaction = connection.BeginTransaction())
{
try
{
// 创建命令
using (var command = connection.CreateCommand())
{
// 设置事务
command.Transaction = transaction;
// 插入新用户
command.CommandText = "INSERT INTO Users (Id, Name) VALUES (1, 'Alice')";
command.ExecuteNonQuery();
// 更新订单状态
command.CommandText = "UPDATE Orders SET Status = 'Shipped' WHERE UserId = 1";
command.ExecuteNonQuery();
// 删除登录记录
command.CommandText = "DELETE FROM Logins WHERE UserId = 1";
command.ExecuteNonQuery();
}
// 提交事务
transaction.Commit();
}
catch (Exception ex)
{
// 如果发生错误,回滚事务
transaction.Rollback();
Console.WriteLine($"Error: {ex.Message}");
throw;
}
}
}
总结
在 SQLite 中执行事务的步骤如下:
- 开始事务:使用
BEGIN TRANSACTION
或其变体(如BEGIN IMMEDIATE TRANSACTION
或BEGIN EXCLUSIVE TRANSACTION
)来显式地启动事务。 - 执行 SQL 操作:在事务中执行多个 SQL 语句(如
INSERT
、UPDATE
、DELETE
等)。 - 提交事务:使用
COMMIT
将所有更改永久保存到数据库中。 - 回滚事务:如果发生错误或决定不保存更改,使用
ROLLBACK
撤销所有未提交的更改。 - 自动提交模式:SQLite 默认处于自动提交模式,每个 SQL 语句都是独立的事务。要执行多个操作作为一组,必须显式地开始事务。
通过正确使用事务,你可以确保数据库操作的一致性和完整性,避免部分操作成功而部分操作失败的情况。事务的 ACID 特性保证了数据的可靠性,而合理的事务设计和隔离级别选择可以帮助你在性能和安全性之间找到平衡。
范例:
SQLSERVER中执行事务的步骤
在 SQL Server 中执行事务的步骤与 SQLite 类似,但 SQL Server 提供了更多的功能和选项来管理事务。SQL Server 事务确保多个数据库操作要么全部成功完成,要么全部回滚,以保持数据的一致性和完整性。以下是 SQL Server 中执行事务的详细步骤和最佳实践。
1. 开始事务
要开始一个事务,可以使用 BEGIN TRANSACTION
或其简写形式 BEGIN TRAN
。SQL Server 支持多种事务模式和隔离级别,允许你根据需求选择合适的事务行为。
示例:
BEGIN TRANSACTION;
-- 或者
BEGIN TRAN;
2. 执行 SQL 操作
在事务开始后,你可以执行任意数量的 SQL 语句(如 INSERT
、UPDATE
、DELETE
等)。这些操作会被视为事务的一部分,直到你提交或回滚事务。
示例:
INSERT INTO Users (Id, Name) VALUES (1, 'Alice');
UPDATE Orders SET Status = 'Shipped' WHERE UserId = 1;
DELETE FROM Logins WHERE UserId = 1;
3. 提交事务
如果你确定所有操作都成功执行,并且希望将更改永久保存到数据库中,可以使用 COMMIT TRANSACTION
来提交事务。提交后,所有更改将被写入数据库,并且事务结束。
示例:
COMMIT TRANSACTION;
-- 或者
COMMIT TRAN;
4. 回滚事务
如果在事务执行过程中发生错误,或者你决定不保存更改,可以使用 ROLLBACK TRANSACTION
来回滚事务。回滚会撤销所有未提交的更改,恢复到事务开始之前的状态。
示例:
ROLLBACK TRANSACTION;
-- 或者
ROLLBACK TRAN;
5. 命名事务(可选)
SQL Server 允许你为事务指定一个名称,这有助于在复杂的嵌套事务中进行管理和调试。命名事务可以帮助你在回滚时明确指定要回滚的事务范围。
示例:
BEGIN TRANSACTION MyTransaction;
-- 执行 SQL 操作
INSERT INTO Users (Id, Name) VALUES (1, 'Alice');
-- 回滚命名事务
IF @@ERROR <> 0
ROLLBACK TRANSACTION MyTransaction;
ELSE
COMMIT TRANSACTION MyTransaction;
6. 自动提交模式
SQL Server 默认处于自动提交模式(autocommit mode),即每个 SQL 语句都会被视为一个独立的事务,并在执行后自动提交。如果你想在多个语句之间保持一致性,必须显式地开始事务。一旦你显式地开始了一个事务,SQL Server 会进入手动提交模式,直到你显式地调用 COMMIT
或 ROLLBACK
。
示例:
-- 自动提交模式下,每个语句都是独立的事务
INSERT INTO Users (Id, Name) VALUES (1, 'Alice'); -- 自动提交
UPDATE Orders SET Status = 'Shipped' WHERE UserId = 1; -- 自动提交
7. 事务隔离级别
SQL Server 支持多种事务隔离级别,允许你控制事务之间的可见性和交互方式。常见的隔离级别包括:
- 读未提交(Read Uncommitted):允许读取未提交的数据,可能会导致脏读、不可重复读和幻读。
- 读已提交(Read Committed):只允许读取已提交的数据,防止脏读,但仍然可能出现不可重复读和幻读。这是 SQL Server 的默认隔离级别。
- 可重复读(Repeatable Read):确保在同一事务中多次读取相同的数据时,结果一致,防止脏读和不可重复读,但仍然可能出现幻读。
- 可序列化(Serializable):最高的隔离级别,确保事务完全隔离,防止脏读、不可重复读和幻读。所有事务按顺序执行,避免并发问题。
- 快照隔离(Snapshot Isolation):允许事务读取事务开始时的数据库快照,防止脏读、不可重复读和幻读,而不需要锁定数据。
设置隔离级别:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 或者
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
8. 使用 C# 执行事务
在 C# 中,使用 SqlTransaction
类可以更方便地管理 SQL Server 事务。以下是一个完整的示例,展示了如何在 C# 中执行带有事务的多个 SQL 操作。
示例代码:
using System;
using System.Data.SqlClient;
using System.Threading;
using System.Threading.Tasks;
public class DatabaseService
{
private readonly string _connectionString;
public DatabaseService(string connectionString)
{
_connectionString = connectionString;
}
/// <summary>
/// 异步执行带有事务的多个 SQL 语句。
/// </summary>
/// <param name="sqlStatements">要执行的 SQL 语句数组。</param>
/// <param name="parametersArray">SQL 语句的参数数组。</param>
/// <param name="cancellationToken">用于取消异步操作的取消令牌。</param>
/// <returns>受影响的总行数。</returns>
public async Task<int> ExecuteWithTransactionAsync(
string[] sqlStatements,
SqlParameter[][] parametersArray,
CancellationToken cancellationToken = default)
{
int totalRowsAffected = 0;
try
{
using var connection = new SqlConnection(_connectionString);
await connection.OpenAsync(cancellationToken);
// 开始事务
using var transaction = connection.BeginTransaction();
try
{
foreach (var (sql, parameters) in sqlStatements.Zip(parametersArray ?? Array.Empty<SqlParameter[]>(), (s, p) => (s, p)))
{
using var command = new SqlCommand(sql, connection, transaction);
if (parameters != null && parameters.Length > 0)
{
command.Parameters.AddRange(parameters);
}
// 执行每个 SQL 语句,并累加受影响的行数
int rowsAffected = await command.ExecuteNonQueryAsync(cancellationToken);
totalRowsAffected += rowsAffected;
}
// 提交事务
await transaction.CommitAsync();
}
catch (Exception)
{
// 如果任何操作失败,回滚事务
await transaction.RollbackAsync();
throw;
}
}
catch (SqlException ex)
{
// 记录数据库相关的异常
Console.WriteLine($"SQL Server error: {ex.Message}\nSQL Statements: {string.Join(";", sqlStatements)}");
throw;
}
catch (OperationCanceledException ex)
{
// 记录取消操作的异常
Console.WriteLine($"Operation was canceled: {ex.Message}");
throw;
}
catch (Exception ex)
{
// 记录其他类型的异常
Console.WriteLine($"Unexpected error: {ex.Message}\nSQL Statements: {string.Join(";", sqlStatements)}");
throw;
}
return totalRowsAffected;
}
}
9. 注意事项
a. 事务的隔离性
SQL Server 提供了多种隔离级别,可以根据你的业务需求选择合适的隔离级别。较高的隔离级别(如 Serializable
)可以提供更强的隔离性,但也可能导致更多的锁争用和性能下降。较低的隔离级别(如 Read Committed
)可以提高并发性能,但可能会引入一些并发问题(如不可重复读)。
b. 事务的持久性
SQL Server 使用日志记录(Logging)机制来确保事务的持久性。每个事务的操作都会被记录到事务日志中,即使系统发生故障,SQL Server 也可以通过日志恢复未提交的事务。
c. 事务的超时
SQL Server 提供了 @@TIMEOUT
和 COMMAND_TIMEOUT
参数,允许你在遇到长时间运行的查询时设置超时时间。你可以在连接字符串中设置 Connection Timeout
,或者在 SqlCommand
对象上设置 CommandTimeout
属性。
d. 分布式事务
如果你需要跨多个数据库或资源管理器执行事务,SQL Server 支持分布式事务(Distributed Transactions)。你可以使用 Microsoft 分布式事务协调器(MS DTC)来管理分布式事务。分布式事务可以通过 System.Transactions
命名空间中的 TransactionScope
类来实现。
e. 批量操作
如果你需要执行大量类似的 SQL 语句(如批量插入或更新),可以考虑将它们放在一个事务中,以提高性能。事务可以减少与数据库的交互次数,并且一次提交所有更改。
10. 示例:完整的事务流程
以下是一个完整的事务流程示例,展示了如何在 SQL Server 中执行多个 SQL 操作,并确保它们作为一个整体成功或失败。
SQL 语句:
BEGIN TRANSACTION;
-- 插入新用户
INSERT INTO Users (Id, Name) VALUES (1, 'Alice');
-- 更新订单状态
UPDATE Orders SET Status = 'Shipped' WHERE UserId = 1;
-- 删除登录记录
DELETE FROM Logins WHERE UserId = 1;
-- 提交事务
COMMIT TRANSACTION;
C# 代码:
using (var connection = new SqlConnection("Data Source=myserver;Initial Catalog=mydatabase;Integrated Security=True"))
{
connection.Open();
// 开始事务
using (var transaction = connection.BeginTransaction())
{
try
{
// 创建命令
using (var command = connection.CreateCommand())
{
// 设置事务
command.Transaction = transaction;
// 插入新用户
command.CommandText = "INSERT INTO Users (Id, Name) VALUES (1, 'Alice')";
command.ExecuteNonQuery();
// 更新订单状态
command.CommandText = "UPDATE Orders SET Status = 'Shipped' WHERE UserId = 1";
command.ExecuteNonQuery();
// 删除登录记录
command.CommandText = "DELETE FROM Logins WHERE UserId = 1";
command.ExecuteNonQuery();
}
// 提交事务
transaction.Commit();
}
catch (Exception ex)
{
// 如果发生错误,回滚事务
transaction.Rollback();
Console.WriteLine($"Error: {ex.Message}");
throw;
}
}
}
11. 总结
在 SQL Server 中执行事务的步骤如下:
- 开始事务:使用
BEGIN TRANSACTION
或BEGIN TRAN
显式地启动事务。 - 执行 SQL 操作:在事务中执行多个 SQL 语句(如
INSERT
、UPDATE
、DELETE
等)。 - 提交事务:使用
COMMIT TRANSACTION
将所有更改永久保存到数据库中。 - 回滚事务:如果发生错误或决定不保存更改,使用
ROLLBACK TRANSACTION
撤销所有未提交的更改。 - 命名事务(可选):为事务指定一个名称,以便在复杂的嵌套事务中进行管理和调试。
- 事务隔离级别:根据需求选择合适的隔离级别,以控制事务之间的可见性和交互方式。
通过正确使用事务,你可以确保数据库操作的一致性和完整性,避免部分操作成功而部分操作失败的情况。事务的 ACID 特性保证了数据的可靠性,而合理的事务设计和隔离级别选择可以帮助你在性能和安全性之间找到平衡。