在实际项目开发时,后端编码少不了事务处理。
为什么要用事务,其中一个最直接的原因就是保持数据完整性和一致性
1、C#事务概念
1.1、逻辑单元
1)在C#中,事务是一组操作的逻辑单元,这些操作可以在一个单独的批处理中执行。
2)使用事务可以确保在操作期间的任何时候发生故障时,所有事务中的操作都将回滚(撤销),从而保持数据的一致性和完整性。
3)使用事务还可以确保在并发访问时,对数据的访问是原子性的,即事务中所有操作要么全部成功,要么全部失败。
1.2、Transaction对象
在C#中,可以使用ADO.NET中的事务来实现事务处理。可以通过定义一个Transaction对象,将多个操作包含在一个事务中。在事务中,执行的每个操作都需要使用Transaction对象的方法来指定操作的范围,这样当事务提交或者回滚时,所有操作都会被同时提交或者回滚。
它支持跨库事务。
1.3、事务简单实例
1)创建表
create table myTable(
name nvarchar(50),
email nvarchar(50)
)
2)事务测试
using System.Data.SqlClient;
// 链接数据库
SqlConnection connection = new SqlConnection("Data Source=服务器IP.com;Initial Catalog=数据库名;User ID=账号;Password=密码");
connection.Open();
// 开始事务
SqlTransaction transaction = connection.BeginTransaction();
try
{
// 执行数据库操作,并将它们添加到事务中
SqlCommand command1 = new SqlCommand("INSERT INTO myTable (name, email) VALUES ('张三', 'zhangsan@test.com')", connection, transaction);
SqlCommand command2 = new SqlCommand("UPDATE myTable SET email = 'zhang.san@test.com' WHERE name = '张三'", connection, transaction);
command1.ExecuteNonQuery();
command2.ExecuteNonQuery();
// 事务提交
transaction.Commit();
}
catch (Exception ex)
{
// 发生异常,事务回滚
transaction.Rollback();
}
finally
{
// 关闭数据库链接
connection.Close();
}
2、事务场景描述
假设有这样一个场景,在一个请求的接口方法里,进行A表添加操作,同时进行B表更新操作。
可以直观想象下,在没有使用事务情况下,A表添加操作成功了,B表更新失败,这个时候A表和B表就不能保持完整和一致,A表就会多了一条冗余数据。
1)创建表
create table table1 (
id int,
name nvarchar(50)
)
create table table2 (
id int,
name nvarchar(50)
)
1)事务代码
using (var scope = new TransactionScope())
{
// 在Database1中执行事务操作
using (var db1 = new SqlConnection("connection_string_1"))
{
db1.Open();
using (var transaction = db1.BeginTransaction())
{
// 执行SQL命令
var command1 = new SqlCommand("INSERT INTO table1 (id, name) VALUES (1, 'test')", db1, transaction);
command1.ExecuteNonQuery();
transaction.Commit();
}
}
// 在Database2中执行事务操作
using (var db2 = new SqlConnection("connection_string_2"))
{
db2.Open();
using (var transaction = db2.BeginTransaction())
{
// 执行SQL命令
var command2 = new SqlCommand("INSERT INTO table2 (id, name) VALUES (2, 'test')", db2, transaction);
command2.ExecuteNonQuery();
transaction.Commit();
}
}
// 提交分布式事务
scope.Complete();
}
3、事务流程图
3.1、没有事务流程
1)页面发起API请求
2)API执行内容逻辑代码
3)执行A表添加,不成功则响应信息回页面
4)A表添加成功,则继续执行下一步代码逻辑
5)执行B表更新,不成功则响应信息回页面
此时,A表记录添加成功,B表更新失败,数据则无法完整和一致
6)B表更新成功,则响应成功信息回页面
7)如果不是重要业务流程,影响不大
8)如果是支付流程,则影响就非常大了
假如支付成功,用户已经付款了,但是支付状态还是未支付状态,这个业务影响就大了
3.2、有事务流程
1)页面发起API请求
2)API执行内容逻辑代码
3)执行A表添加,不成功则响应信息回页面,回滚事务
4)A表添加成功,则继续执行下一步代码逻辑
5)执行B表更新,不成功则响应信息回页面,回滚事务
此时,事务已经回滚,A表记录添加成功则撤销,A和B表操作,要么都成功,要么都失败
6)B表更新成功,则响应成功信息回页面
此时A和B都执行成功,就可以提交事务,完成两次成功操作
4、常见事务类型
以下列举C#中常见的事务类型,对于其他开发语言应该也差不多
编号 | 事务类型 | 描述 |
---|---|---|
1 | 本地事务 | 本地事务是一个在单个数据库上运行的事务。该事务的提交或回滚仅对该数据库有效。 |
2 | 分布式事务 | 分布式事务是一个跨多个数据库或系统的事务。该事务涉及多个资源管理器,例如数据库、消息队列或应用服务器。分布式事务可以是两阶段提交(2PC)或三阶段提交(3PC)。 |
3 | 隐式事务 | 隐式事务是自动管理的事务,由某些API或框架创建和控制。这些事务的创建和提交通常是透明的,并且不需要明确的代码编写。 |
4 | 显式事务 | 显式事务是由应用程序开发人员明确创建和控制的事务。显式事务需要显式的开启、提交或回滚操作。 |
5 | 悲观事务 | 悲观事务认为在任何时间点都会有竞争条件发生。因此,它锁定整个资源并在整个操作期间保持锁定状态。这可以确保只有一个事务可以修改资源。 |
6 | 乐观事务 | 乐观事务认为在大多数情况下,竞争条件不会发生。因此,它不锁定资源,并且只在提交时检查是否存在竞争条件。如果检测到竞争条件,则事务将回滚。 |
这些是一些常见的C#事务类型。开发小伙伴应确保选择适当的事务类型以确保数据的一致性和完整性。