前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >C# 从代码入门 Mysql 数据库事务

C# 从代码入门 Mysql 数据库事务

作者头像
痴者工良
发布于 2023-12-14 05:38:22
发布于 2023-12-14 05:38:22
41600
代码可运行
举报
文章被收录于专栏:痴者工良痴者工良
运行总次数:0
代码可运行

在业务开发中,使用数据库事务是必不可少的。而开发中往往会使用各种 ORM 执行数据库操作,简化代码复杂度,不过,由于各种 ORM 的封装特性,开发者的使用方式也不一样,开发者想要了解 ORM 对事务做了什么处理是比较难的。因此,本文介绍数据库事务基础、Ado.net 事务、如何封装 DbContext ,读者掌握以后,可以加深对 C# 使用事务的理解,使用各种 ORM 时也会更应手。

生成数据库数据

为了演示各种事务操作,我们想要先创建 demo 数据,打开 filldb 官网,根据操作提示生成模拟数据。

filldb 地址: https://filldb.info/dummy/step1

FillDB 是一款免费工具,可快速生成大量 MySql 格式的自定义数据,用于测试软件和使用随机数据填充数据库。

然后按照 authors、posts 的顺序,点击 Generate ,生成数据库数据。

因为 posts 有 authors 的外键,因此生成数据的顺序是 authors、posts。

最后点击 Export database 导出 SQL 即可。

然后在数据库中导入数据。

为了连接 Mysql 数据库,这里使用 MySqlConnector 驱动,请在创建控制台项目之后,通过 nuget 引入此包。

MySqlConnector 的主要部件和 API 如下:

ADO.NET 类型

说明

异步方法

同步方法

DbConnection

连接器

OpenAsync

Open

DbConnection

BeginTransactionAsync

BeginTransaction

DbCommand

执行命令

ExecuteNonQueryAsync

ExecuteNonQuery

DbCommand

ExecuteReaderAsync

ExecuteReader

DbCommand

ExecuteScalarAsync

ExecuteScalar

DbDataReader

读取数据

NextResultAsync

NextResult

DbDataReader

ReadAsync

Read

DbTransaction

数据库事务

CommitAsync

Commit

DbTransaction

RollbackAsync

Rollback

使用同步方法可能会对托管线程池产生不利影响,如果没有正确调优,还会导致速度减慢或锁定。

Mysql 连接字符串配置示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const string connectionString = "Server=localhost;Port=3306;User ID=mysqltest;Password=Password123;Database=mysqldb";

或使用 MySqlConnectionStringBuilder 构建连接字符串:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var connectionBuilder = new MySqlConnectionStringBuilder()
	{
		Server = "localhost",
		Port = 3306,
		UserID = "mysqltest",
		Password = "Password123",
		Database = "mysqldb"
};
var connectionString = connectionBuilder.ConnectionString;

详细连接字符串配置可以在 https://mysqlconnector.net/connection-options/ 中找到。

为了让 MysqlConnetor 可以记录日志,需要手动配置日志程序。

完整的 nuget 包如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
  <ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging" Version="8.0.0" />
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="8.0.0" />
    <PackageReference Include="MySqlConnector" Version="2.3.1" />
    <PackageReference Include="MySqlConnector.Logging.Microsoft.Extensions.Logging" Version="2.1.0" />
  </ItemGroup>

配置连接字符串、配置日志、创建数据库连接,完整代码示例如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger<Program>();
var dataSourceBuilder = new MySqlDataSourceBuilder(connectionString);
dataSourceBuilder.UseLoggerFactory(loggerFactory);
await using var dataSource = dataSourceBuilder.Build();

using var connection = dataSource.CreateConnection();

经过以上配置之后,我们拥有了模拟数据库以及基础代码,下面我们来正式学习 MysqlConnetor 和数据库事务相关的知识。

Mysql 数据库事务基础

百度百科:数据库事务( transaction)是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。

数据库事务有四个特性:

  • 原子性:原子性是指包含事务的操作要么全部执行成功,要么全部失败回滚。
  • 一致性:一致性指事务在执行前后状态是一致的。
  • 隔离性:一个事务所进行的修改在最终提交之前,对其他事务是不可见的。
  • 持久性:数据一旦提交,其所作的修改将永久地保存到数据库中。

相信大家对数据库事务都不陌生,因此这里就不扯淡了,下面来讲解不同数据库事务的特征。

数据库的并发一致性问题

虽然数据库事务可以帮助我们执行数据库操作、回滚操作,但是数据库事务并发执行时,事务之间可能会相互干扰,比如脏读、幻读等现象,我们使用数据库事务时,要根据严格程度和性能之间相互平衡选择事务隔离级别。

当多个事务并发执行时,可能会出现以下问题:

脏读

​ 事务 A 更新了数据,但还没有提交,这时事务 B 读取到事务 A 更新后的数据,然后事务 A 回滚了,事务 B 读取到的数据就成为脏数据了。

不可重复读

​ 事务 A 对数据进行多次读取,事务 B 在事务 A 多次读取的过程中执行了更新操作并提交了,导致事务 A 多次读取到的数据并不一致。

不可重复读,特征是相同的数据,在事务 A 的不同阶段读取的数据不一样。

幻读

​ 事务 A 在读取数据后,事务 B 向事务A读取的数据中插入了几条数据,事务 A 再次读取数据时发现多了几条数据,和之前读取的数据不一致。

幻读,前后数据量不一样。

丢失修改

​ 事务 A 和事务 B 都对同一个数据进行修改,事务 A 先修改,事务 B 随后修改,事务 B 的修改覆盖了事务 A 的修改。

不可重复度和幻读看起来比较像,它们主要的区别是:在不可重复读中,发现数据不一致主要是数据被更新了。在幻读中,发现数据不一致主要是数据增多或者减少了。

数据库事务的隔离级别

数据库事务的隔离级别有以下四种,按隔离级别从低到高:

  • 未提交读:一个事务在提交前,它的修改对其他事务也是可见的。
  • 提交读:一个事务提交之后,它的修改才能被其他事务看到。
  • 可重复读:在同一个事务中多次读取到的数据是一致的。
  • 串行化:需要加锁实现,会强制事务串行执行。

Ado.net 中使用 System.Data.IsolationLevel 枚举表示以上几种数据库事务隔离级别:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public enum IsolationLevel
	{
        // 未指定
		Unspecified = -1,
        // 不能覆盖来自更高度隔离的事务的挂起的更改。
		Chaos = 16,
        // 未提交读,脏读是可能的,这意味着不会发出共享锁,也不会使用独占锁。
		ReadUncommitted = 256,
        // 提交读,在读取数据时持有共享锁,以避免脏读,但是数据可以在事务结束之前更改,从而导致不可重复读取或幻像数据。
		ReadCommitted = 4096,
        // 可重复读,锁被放置在查询中使用的所有数据上,防止其他用户更新数据。防止不可重复读取,但仍然可以使用幻像行。
		RepeatableRead = 65536,
        // 串行化,将在 DataSet 上放置一个范围锁,以防止其他用户在事务完成之前更新数据集或将行插入数据集。
		Serializable = 1048576,
        // 通过存储一个应用程序可以读取而另一个应用程序正在修改相同数据的数据版本来减少阻塞。
        // 指示即使重新查询,也无法从一个事务中看到在其他事务中所做的更改。
		Snapshot = 16777216
	}

数据库的隔离级别分别可以解决数据库的脏读、不可重复读、幻读等问题。

隔离级别

脏读

不可重复读

幻读

未提交读

允许

允许

允许

提交读

不允许

允许

允许

可重复读

不允许

不允许

允许

串行化

不允许

不允许

不允许

其实也不必纠结这些问题,可以按照读写锁的情况来理解。

编程中由于多个线程并发操作两个字典:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
Dictionary<string, string> a;
Dictionary<string, string> b;

第一个问题时,并发操作一个字典时,会出现线程并发异常。

所以,我们想要使用并发字典:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	ConcurrentDictionary<string, string> a;
	ConcurrentDictionary<string, string> b;

可是,当 T1 线程修改 a 完成,接着修改 b 时,线程 T2 把字典 a 修改了。这就导致了数据不一致。

使用读写锁优化,将 a、b 两个数据包在一起:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	ConcurrentDictionary<string, string> a;
	ConcurrentDictionary<string, string> b;

	private static ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();
	// 读
	private void Read()
	{
		try
		{
			_lock.EnterReadLock(); 
			// 读
		}
		catch { }
		finally
		{
			_lock.ExitReadLock();            // 释放读取锁
		}
	}

	// 写
	public void Write(int key, int value)
	{
		try
		{
			_lock.EnterUpgradeableReadLock();
			_lock.EnterWriteLock();
			// 写
			_lock.ExitWriteLock();
		}
		catch { }
		finally
		{
			_lock.ExitUpgradeableReadLock();
		}
	}

读写锁的原理很简单,读和写是两个冲突的操作。当没有线程 时,多个线程可以并发 ,此时不会有任何问题。当有一个线程 时,既不允许有其它线程同时在 ,也不允许其它线程同时在 。也就是说, 是可以并发的,但是写是独占的。

串行化

当然对于数据库事务就复杂了很多。如果要按照读写锁的形式去做,那么其隔离级别相当于 串行化,整个表都被锁住,不允许事务并发执行,此时不会有 脏读不可重复读幻读 这些情况。

可是,这样对于数据库来说压力是很大的,会严重拖垮数据库的性能,以及严重降低了业务程序的并发量。

当事务 A 只需要修改 id=1,2,3 的数据时,使用 串行化 级别,会锁住整个表。这样似乎有点太浪费了。

可重复读

那么,我们只需要锁住事务 A 正在修改的那几行记录不就行了吗?那么我们把数据库事务下降一个级别,使用 可重复读

使用 可重复读 事务级别,其被锁住的数据,依然保持安全,也就是不会被其它事务所修改。所以,不会出现 脏读不可重复读。但是因为不是锁住整个表,因此其它事务是可以插入数据的,这就导致了会出现 幻读。当然,可重复读 出现的问题,一般来说只需要保证事务中只处理自己想要的数据即可。

可重复读 导致的 幻读 问题,比如 A 事务在 笔记本 分类下给联想笔记本型号都打 9 折优惠,可是此时 B 事务从 笔记本 分类下,增加了几个理想笔记本型号。结果,事务 A 最后一查询,把 B 事务插入的数据查询出来了。那么事务 A 查询的数据就包含了打折和未打折的数据了。

InnoDB 使用 MVCC 来实现高并发性,并实现了所有 4 个SQL标准隔离级别。InnoDB 默认为 REPEATABLE READ (可重复读)隔离级别,并且通过间隙锁(next-key locking)策略来防止在这个隔离级别上的幻读。InnoDB 不只锁定在查询中涉及的行,还会对索引结构中的间隙进行锁定,以防止幻行被插入。

提交读

使用示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
UPDATE  pet SET NAME = 'A';
SELECT SLEEP(5);
SELECT * from pet;
COMMIT;

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
UPDATE  pet SET NAME = 'B';
SELECT SLEEP(5);
SELECT * from pet;
COMMIT;

A 事务和 B 事务运行时,大家都对 name 做了修改,但是事务只能看到自己做出的修改,也就是说,B 事务未提交之前,A、B 都修改了数据,但是是隔离的。

A 事务修改了 name = A ,B 事务修改了 name = B ,未提交之前,A、B 事务读到的分别是 A、B,这没问题,不会干扰。

但是如果 A 先提交了事务,那么数据库的 name 值就为 A,此时 B 事务还没有提交,B 查询到的 name = A,这就是不可重复读。

提交读 只能保证事务未提交前的数据隔离。当另一个事务提交后,会导致当前事务看到的数据前后不一样。

未提交读

这就离谱了。啥也不能保证。

对于数据库事务的理解,大家倒序建议就比较容易理解了。

BeginTransaction() 和 TransactionScope 的区别

在 C# Ado.net 中,主要有两种事务使用方式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 方式 1:
using var tran = await connection.BeginTransactionAsync();

// 方式 2:
using (TransactionScope transactionScope = new TransactionScope())
{

}

BeginTransaction() 由 IDbConnection 连接对象开启,只能作用于当前 IDbConnection 。通过调用数据库连接对象的 BeginTransaction() 方法,显式地启动了一个数据库事务,因此与同步方法异步方法不冲突。

TransactionScope 内部封装了一些 API,在TransactionScope设置的范围内,不需要显式地调用 Commit()Rollback() 方法,可以跨 IDbConnection 使用,在异步方法下使用需要做额外配置。

主要区别在于 BeginTransaction() 是显式地管理事务,而 TransactionScope 则是在编程模型上提供了更为方便的自动事务管理机制。

在 System.Transactions 命名空间中存在很多与事务相关的代码封装。读者可以自行了解:

https://learn.microsoft.com/en-us/dotnet/api/system.transactions?view=net-8.0

下面来详细说明两种事务开启方式的使用区别。

BeginTransaction()

先说 BeginTransaction() ,其返回的是 DbTransaction 类型。

BeginTransaction() 开启事务比较简单,不过需要手动给 IDbCommand 设置事务属性。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			await connection.OpenAsync();
           // 先开启事务,再创建命令
			using var tran = await connection.BeginTransactionAsync();
			using var command = new MySqlCommand()
			{
				Connection = connection,
                // 注意这里
				Transaction = tran
			};

			try
			{
				command.CommandText = "... ...";
				await command.ExecuteNonQueryAsync();

				if(...)
				{
					await tran.CommitAsync();
				}else
				{
					await tran.RollbackAsync();
				}
			}
			catch (Exception ex)
			{
				await tran.RollbackAsync();
                logger.LogError(ex, "Tran error");
			}

BeginTransaction() 定义如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
ValueTask<MySqlTransaction> BeginTransactionAsync(IsolationLevel isolationLevel, 
                                                  CancellationToken cancellationToken = default)

DbTransaction 还可以设置保存点。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			using var tran = await connection.BeginTransactionAsync();
			try
			{
				command.CommandText = "... ...";
				await command.ExecuteNonQueryAsync();

				// 保存点
				await tran.SaveAsync("stepa");

				// 释放保存点、回滚到该保存点
				if(...)
				{
					await tran.ReleaseAsync("stepa");
				}
			}

BeginTransaction() 的使用比较简单,也不太容易出错。

可以不手动撤销

很多时候我们会在 catch{} 回滚事务,如下代码所示。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			try
			{
                ... ...
				await tran.CommitAsync();
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error");
				await tran.RollbackAsync();
			}

实际上是当一个事务在 IDbConnection 中或者在此 IDbCommand 中没有主动提交时,当对象生命周期结束或主动断开连接时、被回收到连接池时,事务会自动回滚。只要没有主动提交,则之前的操作皆无效。

比如,我们执行下面的 SQL 时,posts 表会被插入一条新的数据,id 为 101。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-- 开启事务
BEGIN; -- 或者使用 START TRANSACTION;
INSERT INTO demo.posts (id, author_id, title, description, content, date)
VALUES (101, 1, '测试', '测试', '测试', '2023-12-08');
COMMIT ;

而执行以下代码时,因为没有调用 CommitAsync() 方法提交事务,因此程序结束后,插入数据库的数据并不会起效。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			using var connection = dataSource.CreateConnection();
			await connection.OpenAsync();
			using var tran = await connection.BeginTransactionAsync();
			using var command = new MySqlCommand()
			{
				Connection = connection,
				Transaction = tran
			};

			try
			{
				command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (102, 1, '测试', '测试', '测试', '2023-12-08');
				""";
				await command.ExecuteNonQueryAsync();
                // await tran.CommitAsync();
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error");
			}
TransactionScope

如以下代码所示,虽然代码执行不会报错,但是其不受事务所控制,也就是说,虽然没有提交,但是数据库实实在在的插入了一条新的数据。

这是因为事务完全没有起效,因为只有在 TransactionScope 中打开的数据库连接,才会起效

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			using var connection = dataSource.CreateConnection();
			await connection.OpenAsync();

			using (TransactionScope transactionScope = new TransactionScope())
			{
				var command = connection.CreateCommand();
				try
				{
					command.CommandText = 
                        """
                        INSERT INTO demo.posts (id, author_id, title, description, content, date) 
                        VALUES (103, 1, '测试', '测试', '测试', '2023-12-08');
                        """;
					await command.ExecuteNonQueryAsync();
					//transactionScope.Complete();
				}
				catch (Exception ex)
				{
					logger.LogError(ex, "Tran error");
				}
			}

修正之后:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			using (TransactionScope transactionScope = new TransactionScope())
			{
				using var connection = dataSource.CreateConnection();
				await connection.OpenAsync();

				var command = connection.CreateCommand();
				try
				{
					command.CommandText = 
                        """
                        INSERT INTO demo.posts (id, author_id, title, description, content, date) 
                        VALUES (104, 1, '测试', '测试', '测试', '2023-12-08');
                        """;
					await command.ExecuteNonQueryAsync();
					//transactionScope.Complete();
				}
				catch (Exception ex)
				{
					logger.LogError(ex, "Tran error");
				}
			}

但是,上面的代码还是会报错。这是因为 TransactionScope 默认不支持异步方法,而该代码使用了异步,导致释放时没有使用相同的线程。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
System.InvalidOperationException:A TransactionScope must be disposed on the same thread that it was created.

当然,TransactionScope 是支持异步的,我们只需要启用配置即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			using (TransactionScope transactionScope = 
			new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
			{
				using var connection = dataSource.CreateConnection();
				await connection.OpenAsync();

				var command = connection.CreateCommand();
				try
				{
					command.CommandText = 
                        """
                        INSERT INTO demo.posts (id, author_id, title, description, content, date) 
                        VALUES (104, 1, '测试', '测试', '测试', '2023-12-08');
                        """;
					await command.ExecuteNonQueryAsync();
					//transactionScope.Complete();
				}
				catch (Exception ex)
				{
					logger.LogError(ex, "Tran error");
				}
			}

如下代码所示,当执行代码之后,因为我们没有主动提交事务,因此,数据库中不会真的插入数据。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			using (TransactionScope transactionScope = 
                   // 使其支持异步
                   new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
			{
				using var connection = dataSource.CreateConnection();
				await connection.OpenAsync();

				var command = connection.CreateCommand();

				command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (105, 1, '测试', '测试', '测试', '2023-12-08');
				""";
                    
				await command.ExecuteNonQueryAsync();
				//transactionScope.Complete();
			}

有了经验之后,我们发现,如果我们不调用 Complete() 方法,那么数据库中不会真的插入数据。

可是问题来了,因为是在 TransactionScope 中创建 IDbConnection 并打开连接,也就是说 TransactionScope 作用域范围大于 IDbConnection ,那么 IDbConnection 释放之后,再提交 TransactionScope ,是否可以?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
			{
				using (var connection = dataSource.CreateConnection())
				{
					await connection.OpenAsync();
					var command = connection.CreateCommand();
					command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (105, 1, '测试', '测试', '测试', '2023-12-08');
				""";
					await command.ExecuteNonQueryAsync();
				}

				transactionScope.Complete();
			}

答案是一切正常。

简化代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			using (TransactionScope transactionScope = ...)
			{
				using (var connection = dataSource.CreateConnection())
				{
					await connection.OpenAsync();
					await command.ExecuteNonQueryAsync();
				}

				transactionScope.Complete();
			}

虽然, IDbConnection 在 using 中,transactionScope.Complete() 在 using 之外,但是事务依然可以起效。如果调用 .Complete(),则事务提交。如果不调用 .Complete() 则事务不会提交。

回到本小节第一个代码示例中,事务不起效的问题。我们已经知道了是因为 IDbConnection 没有在 TransactionScope 内创建,所以导致事务不能作用。

但是,对于 ASP.NET Core 程序、Context 形式的 ORM、仓储形式的 ORM 等,由于其封装在上下文内,不太可能在开发者使用 TransactionScope 时,再手动打开 IDbConnection.Open() 。不过这些 ORM 框架大多数都做了封装,而本文末尾也介绍了几种封装方式。

总结

通过 BeginTransaction() 创建的事务,不会因为异步等出现问题,因为其是明确在一个 IDbCommand 、IDbConnection 中起效。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			using var tran = await connection.BeginTransactionAsync();
			using var command = new MySqlCommand()
			{
				Connection = connection,
                // 注意这里
				Transaction = tran
			};

所以说,通过 .BeginTransactionAsync() 使用事务,是最简单、最不容易出错的,而且其明确在哪个 IDbCommand 中使用事情,出现问题时,排除起来也相对简单。

而对于 TransactionScope 来说,笔者花费了比较多的篇幅去实验和解释,TransactionScope 是使用事务作用域实现隐式事务的,使用起来有一定难度,也容易出错。

DML 是否可以使用事务

开始的时候,笔者并没有想到这个事情,在跟同事偶然吹水时,提到了这个事情。

Mysql 的事务对删除表、创建表这些 DML 命令,其事务是无效的,起效的是表数据相关的操作,即 insert、update、delete 语句。

如下 SQL 所示,虽然回滚了事务,但是最后还是创建了视图。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
-- 开启事务
use  demo;
BEGIN;
create view v_posts AS  SELECT * FROM posts;
ROLLBACK;
-- COMMIT ;

顺序多操作

先从 TransactionScope 说起,情况如下代码所示:

TransactionScope 中包含、创建了两个 IDbConnection ,并且两个 IDbConnection 都插入了数据。

也就是说使用 TransactionScope 同时管理多个 IDbConnection 。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
			{
				using (var connection = dataSource.CreateConnection())
				{
					await connection.OpenAsync();
					var command = connection.CreateCommand();
					command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (108, 1, '测试', '测试', '测试', '2023-12-08');
				""";
					await command.ExecuteNonQueryAsync();
				}

				using (var connection = dataSource.CreateConnection())
				{
					await connection.OpenAsync();
					var command = connection.CreateCommand();
					command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (109, 1, '测试', '测试', '测试', '2023-12-08');
				""";
					await command.ExecuteNonQueryAsync();
				}

				//transactionScope.Complete();
			}

这样是可以的,TransactionScope 管理在期内的所有 IDbConnection,让他们在当前的事务中保持一致。

但是 BeginTransaction() 是使用 IDbConnection.BeginTransaction() 创建的,不能跨 IDbConnection 使用。

比如,以下代码会报错:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
			using var connection1 = dataSource.CreateConnection();
			using var connection2 = dataSource.CreateConnection();
			await connection1.OpenAsync();
			await connection2.OpenAsync();

			try
			{
				var tran1 = connection1.BeginTransaction();

				var command1 = connection1.CreateCommand();
				command1.Transaction = tran1;
				var command2 = connection2.CreateCommand();
				command2.Transaction = tran1;

				command1.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (108, 1, '测试', '测试', '测试', '2023-12-08');
				""";
				await command1.ExecuteNonQueryAsync();
				command2.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (108, 1, '测试', '测试', '测试', '2023-12-08');
				""";
				await command2.ExecuteNonQueryAsync();
				tran1.Commit();
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error");
			}

所以,这里又有一个区别。

嵌套事务

.BeginTransaction() 不支持嵌套事务,代码如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		static async Task Main(string[] args)
        {
            using var connection = dataSource.CreateConnection();
			await connection.OpenAsync();
			var tran = connection.BeginTransaction();

			try
			{
				var command = connection.CreateCommand();
				command.Transaction = tran;
				command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (110, 1, '测试', '测试', '测试', '2023-12-08');
				""";
				await command.ExecuteNonQueryAsync();

				// 嵌套事务
				try
				{
					await InsertAsync(connection);
				}
				catch (Exception ex)
				{
					logger.LogError(ex, "Tran error.");
					await tran.RollbackAsync();
					return;
				}

				await tran.RollbackAsync();
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error");
			}
		}

		// 嵌套的子事务
		private static async Task InsertAsync(MySqlConnection connection)
		{
			var tran = connection.BeginTransaction();
			var command = connection.CreateCommand();
			command.Transaction = tran;
			command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (112, 1, '测试', '测试', '测试', '2023-12-08');
				""";
			await command.ExecuteNonQueryAsync();
			await tran.CommitAsync();
		}

当一个 IDbConnection 调用两次 .BeginTransaction() 时,代码会报错。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
 System.InvalidOperationException: Transactions may not be nested.

所以,我们只能寄望于 TransactionScope。

使用 TransactionScope 做嵌套事务,可以做到灵活的逻辑定制,每个嵌套子事务都有自己的逻辑。

每个子事务只需要正常编写自己的 TransactionScope 即可,即使子事务的 TransactionScope 已提交,如果最外层的 TransactionScope 事务没有提交,则所有的事务都不会提交。

如下代码所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	static async Task Main(string[] args)
	{
		using var connection = dataSource.CreateConnection();
		using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
		{
			await connection.OpenAsync();
			var command = connection.CreateCommand();
			command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (110, 1, '测试', '测试', '测试', '2023-12-08');
				""";
			await command.ExecuteNonQueryAsync();

			// 嵌套事务
			try
			{
				await InsertAsync(connection);
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error.");
				return;
			}
			// transactionScope.Complete();
		}
	}

	// 嵌套的子事务
	private static async Task InsertAsync(MySqlConnection connection)
	{
		using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
		{
			var command = connection.CreateCommand();
			command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (112, 1, '测试', '测试', '测试', '2023-12-08');
				""";
			await command.ExecuteNonQueryAsync();
			transactionScope.Complete();
		}
	}

虽然 InsertAsync() 中的事务已经提交,但是由于其受到外层 TransactionScope 事务的影响,因此当外层事务不提交时,子事务也不会提交。

当然,即使不是同一个 IDbConnection 也是可以的。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	static async Task Main(string[] args)
	{
		using var connection = dataSource.CreateConnection();
		using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
		{
			await connection.OpenAsync();
			var command = connection.CreateCommand();
			command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (110, 1, '测试', '测试', '测试', '2023-12-08');
				""";
			await command.ExecuteNonQueryAsync();

			// 嵌套事务
			try
			{
				await InsertAsync();
			}
			catch (Exception ex)
			{
				logger.LogError(ex, "Tran error.");
				return;
			}
			// transactionScope.Complete();
		}
	}

	// 嵌套的子事务
	private static async Task InsertAsync()
	{
		using var connection = dataSource.CreateConnection();
		using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
		{
			await connection.OpenAsync();
			var command = connection.CreateCommand();
			command.CommandText = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (112, 1, '测试', '测试', '测试', '2023-12-08');
				""";
			await command.ExecuteNonQueryAsync();
			transactionScope.Complete();
		}
	}

所以,每个方法的代码,只需要关注自己的逻辑即可。对于模块分离、职责分离的代码很有用。

事务范围

前面我们提到了 TransactionScope 的嵌套事务。

TransactionScope 对于嵌套事务的处理,有一个 TransactionScopeOption 枚举配置。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public enum TransactionScopeOption
	{
        // 该范围需要一个事务。 如果已经存在环境事务,则使用该环境事务。 否则,在进入范围之前创建新的事务。 这是默认值。
		Required = 0,
        
        // 总是为该范围创建新事务。
		RequiresNew = 1,
        
        // 如果使用 Suppress 实例化范围,则无论是否存在环境事务,该范围都不会参与事务。使用此值实例化的范围始终将 null 作为其环境事务。
		Suppress = 2
	}

使用示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
using(TransactionScope scope1 = new TransactionScope())
{
    // 默认支持嵌套
    using(TransactionScope scope2 = new TransactionScope(TransactionScopeOption.Required))
    {
        //...
    }
    
    // 不受 scope1 的影响
    using(TransactionScope scope3 = new TransactionScope(TransactionScopeOption.RequiresNew))
    {
        //...  
    }
  
    // 如果使用 Suppress 实例化范围,则无论是否存在环境事务,该范围都不会参与事务。
    using(TransactionScope scope4 = new TransactionScope(TransactionScopeOption.Suppress))
    {
        //...  
    }
}

对于嵌套事务作用域范围,读者可以从这篇文章中了解更多:https://learn.microsoft.com/en-us/previous-versions/ms172152(v=vs.90)?redirectedfrom=MSDN#Y1642

封装 DbContext

前面提到过,IDbConnection 需要在 TransactionScope 中打开连接,TransactionScope 才能管控其连接的事务。

不过,有一些数据库驱动已经支持了 TransactionScope ,即使不在其内打开链接也可以。比如 EFCore 框架,EFCore 自动管理 IDbConnection 的生命周期,因此我们往往不会手动管理连接,因此事务事务时,我们不太可能这样做:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MyContext _context;

using (TransactionScope transactionScope = ...)
{
    _context.Connection.Open()
}

在使用数据库事务之前,往往连接早就已经打开了。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
MyContext _context;
_context.SelectAsync()....
_context.User.SectAsync()....
using (TransactionScope transactionScope = ...)
{
}

所以,我们需要封装一个上下文类型,能够在连接打开后,自动使用上下文的事务。

TransactionScope

封装一个数据库上下文,执行命令时,如果发现其在事务范围内,则主动使用上下文事务。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public class DbContext
	{
		private readonly DbConnection _connection;

		public DbContext(DbConnection connection)
		{
			_connection = connection;
		}

		public async Task ExecuteAsync(string sql)
		{
			var command = _connection.CreateCommand();
            // 获取当前事务
			var tran = Transaction.Current;
			if (tran != null)
			{
                // 注意这里。
				_connection.EnlistTransaction(tran);
			}

			command.CommandText = sql;
            
			await command.ExecuteNonQueryAsync();
		}
	}

使用示例:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		using var connection = dataSource.CreateConnection();
// 在之外打开
		await connection.OpenAsync();
		var context = new DbContext(connection);

		using (TransactionScope transactionScope = new TransactionScope(asyncFlowOption: TransactionScopeAsyncFlowOption.Enabled))
		{
			var sql = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (111, 1, '测试', '测试', '测试', '2023-12-08');
				""";

			await context.ExecuteAsync(sql);
		}
BeginTransaction()

使用上下文的形式封装 BeginTransaction() 开启的事务比较简单,只需要手动维护 DbTransaction 即可。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
	public class DbContext
	{
		private readonly DbConnection _connection;
		private DbTransaction? _tran;
		public DbContext(MySqlConnection connection)
		{
			_connection = connection;
		}

		public async Task OpenTran()
		{
			if (_tran != null) throw new Exception("请勿重复开启事务");
			_tran = await _connection.BeginTransactionAsync();
		}

		public async Task ExecuteAsync(string sql)
		{
			var command = _connection.CreateCommand();
			command.CommandText = sql;

			if (_tran != null)
			{
				command.Transaction = _tran;
			}
			await command.ExecuteNonQueryAsync();
		}

		public async Task EndTran()
		{
			if (_tran == null) throw new Exception("未开启事务");
			await _tran.CommitAsync();
			_tran.Dispose();
			_tran = null;
		}
	}

使用方法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
		using var connection = dataSource.CreateConnection();
		await connection.OpenAsync();
		DbContext context = new DbContext(connection);

		await context.OpenTran();
		var sql = """
				INSERT INTO demo.posts (id, author_id, title, description, content, date) 
				VALUES (111, 1, '测试', '测试', '测试', '2023-12-08');
				""";
		await context.ExecuteAsync(sql);

当然,由于不同的 ORM 封装的数据库事务方法不一样,因此 ORM 的差异比较大。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-12-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
腾讯看点CTO徐羽: QQ浏览器背后的推荐AI中台 | AICon
随着信息流和短视频应用的发展,推荐系统已经从传统的单目标浅层网络的中小型架构演进为多目标超大规模深度学习的复杂架构。这种复杂的系统的演进对大规模的训练推理和在线排序提出了非常高的技术要求。 另外随着用户消费内容类型以及业务复杂度的增加,如何更好地理解文字、图片、视频到多模态,乃至用户画像的进一步演进,到最终可以给用户推送最合适的内容,也成为了一个在不断迭代和优化的过程。 11 月 5-6 日,AICon 全球人工智能与机器学习技术大会(北京站)2021 将落地北京。我们邀请到了腾讯看点 CTO 兼副总经理、
博文视点Broadview
2023/05/06
8360
腾讯看点CTO徐羽: QQ浏览器背后的推荐AI中台 | AICon
响铃:人人都在喊“ALL IN AI”,真姿势和假动作到底差在哪?
曾几何时,不论规模体量大小,要是一个互联网科技企业不对外宣称自己“AI了一下”,都不好意思呆在科技企业圈子里。
曾响铃
2018/08/21
2980
响铃:人人都在喊“ALL IN AI”,真姿势和假动作到底差在哪?
算法开发人员的安身之本:如何将机器学习与各行各业进行深度结合
近10年来,机器学习发展势头迅猛,被广泛应用于搜索系统,推荐系统、垃圾邮件检索、信用评分、欺诈检测、股票交易、医疗、自动驾驶、人脸识别等多个方面。机器学习所散发出的魅力遍及了人工智能的各个领域。
AI科技大本营
2018/10/22
9440
算法开发人员的安身之本:如何将机器学习与各行各业进行深度结合
2018全球机器学习技术大会40位大神即将开讲!
​​​"Can Machine Think?" 1936年阿兰· 图灵提出「图灵机」以及机器具备「思维」的可能性。历经82年,以机器学习为代表的人工智能经过近几年的飞速发展,已成为当今世界最为火热的技
活动家
2018/07/12
9470
2018全球机器学习技术大会40位大神即将开讲!
AISummit全球人工智能技术大会顺利开幕:首日精彩回顾
盛夏八月,骄阳似火,草木蓊郁,一切都彰显着野蛮而诗意的生命力。夏天是一个探索、成长、革新的季节。在这个属于实践者的时节里,51CTO带来了一场以“驱动、创新、数智”为主题的AI盛会。
火星情报局
2022/08/08
4010
业界 | 爆华为要发布人工智能手机 黑科技还是噱头?
今日,业内人士潘九堂爆料,华为要在下个月发布一款比 Mate 9 更强悍的概念人工智能手机,此款手机将配备三星定制的曲屏和感器。该项目由华为 CEO 徐直军直接领导、2012 实验室深度参与研发。 据
AI科技评论
2018/03/08
8340
业界 | 爆华为要发布人工智能手机  黑科技还是噱头?
阿里、京东、快手、华为......他们是如何构建一个个推荐系统“帝国”的?
推荐系统在人们的日常生活中随处可见,成为我们生命中不可或缺的一部分。作为当今应用最为广泛和成熟的 AI 技术之一,它是信息生产者、传播者与用户之间的桥梁,可以让信息最精准、最高效地到达需求不一的用户面前。
AI科技大本营
2019/08/09
1.6K0
探索AI实践最优解,AISummit全球人工智能技术大会完美落幕
北京时间2022年8月7日下午17:30,由51CTO精心策划以“驱动•创新•数智”为主题的AISummit全球人工智能技术大会2022线上直播活动圆满成功!
火星情报局
2022/08/08
7280
开发者转型AI看过来,这是一场汇聚中美顶尖专家的AI盛会
随着人工智能技术的飞速发展及应用推广,AI在日趋成熟之余,已然成为行业竞争的重点方向。就中国而言,从《新一代人工智能发展规划》的实施,到人工智能教材的推出,AI无疑离我们越来越近,“AI+”产业应用更是成为国家经济增长新引擎。未来,所有企业都将互联网化,所有互联网企业都将 AI 化。
AI科技大本营
2018/09/28
3840
开发者转型AI看过来,这是一场汇聚中美顶尖专家的AI盛会
快手技术VP王仲远对于AI、大模型、深度学习的预测
嘉宾 | 王仲远 编辑 | 薛梁 在策划 11 月 5-6 日 AICon 全球人工智能与机器学习技术大会(北京站)2021 的时候,我们联系到了快手技术 VP、人工智能团队 MMU&Y-tech 负责人王仲远博士,邀请他来担任 AICon 会议的 Co-Chairman。他本人在 NLP、知识图谱、多模态、搜索推荐、深度学习等领域有多年的研究应用经验,曾任美团核心技术团队搜索与 NLP 部负责人,带领团队主要负责美团 App 和大众点评 App 的搜索系统及人工智能核心技术研发。 加入快手后,王仲远所带
深度学习与Python
2023/04/01
9180
快手技术VP王仲远对于AI、大模型、深度学习的预测
活动推荐 | 全球AI技术开放日,7月14日上海(含优惠码)
全球AI技术开放日,是由AICamp发起的学习和练习AI技术的一系列交流学习活动。组织国内外AI专家学者走进优秀的AI技术公司,一起交流学习AI技术具体实践。 7月14日走进携程专场,将探索携程在线旅游业务背后的AI大脑。由来自硅谷和携程的讲师围绕相关业务发展过程中遇到的问题和解决方案展开,从技术挑战与选型、架构设计与阶段性演进、新技术应用探索等多个层面进行分享。 活动信息 ---- 【时间】7月14日(周六)12:30-17:00 【地点】上海市长宁区金钟路968号,凌空SOHO12号楼 【报名】点击文
携程技术
2018/07/05
1.3K0
Al时代,阿里、诺基亚、同花顺等技术大咖带你直击人工智能多场景应用实践!
但是,编程真的能够被深度学习、人工智能所取代?场主认为: 新的技术总是会驱动更多的岗位和机会,技术是生产力,技术人则是核心生产力!
养码场
2018/08/10
1.1K0
中国AI开发者真实现状:写代码这条路,会走多久?
2016 年起,人工智能成为中国开发者重点关注的技术领域,以深度学习驱动的计算机视觉、自然语言处理、语音相关技术成为渗透最广的三个 AI 技术领域。然而,在这样的背景下,AI 仍是一个非常前沿的学科,对于中国开发者而言有很多需要克服的障碍,首当其冲的就是算法成熟度问题。此外,不同领域不同产业的 AI 应用场景复杂度与日俱增,给很多开发者树立了天然门槛。
昱良
2019/05/15
6830
中国AI开发者真实现状:写代码这条路,会走多久?
大牛书单 | 人工智能方向好书推荐
导语:读书是一生的功课,技术人通过读书实现自我提升,学习优秀知识沉淀。TEG书知道本期特邀腾讯TEG AI Lab专家姚建华、腾讯TEG AI平台部工程平台中心负责人罗敏、腾讯TEG AI Lab专家李志鋒,为大家带来AI方向好书推荐。来看看技术大牛在读什么,收藏优质内容,愿本期书单助您更专业。 姚建华博士是医学影像AI领域的专家,在AI Lab负责AI+医疗领域的前沿研究及产品落地。在脊柱影像分析,肿瘤生长预测,结肠癌检测以及影像引导机器人手术方向取得过突出成绩。编辑过多本期刊特刊和专著,并组织
腾讯技术工程官方号
2019/01/10
1.4K0
大牛书单 | 人工智能方向好书推荐
【AI女神节特稿】你不能不知道的13位中国人工智能女性
【新智元导读】一年一度的女神节,新智元整理出几位在人工智能领域工作的杰出女性,她们的研究和工作让人工智能更加美好。 从门禁刷脸到拍照购物再到自动驾驶汽车,人工智能正在以惊人的趋势席卷世界,改变我们的生活。不过,你或许还不够了解在背后往前推动人工智能的研究人员和科学家,尤其是女性研究员和科学家。 与大多数理工学科一样,AI/ML领域,女性从业者的数量呈压倒性的劣势,管理岗位和C级职位尤其。根据最新的一份调查,在谷歌和Facebook,女性工程师的数量仅有20%,而这已经算是多的——根据女性机器学习
新智元
2018/03/12
1.9K0
【AI女神节特稿】你不能不知道的13位中国人工智能女性
联邦学习助力人工智能新模型进化(附:金融隐私计算实战项目)
2016年是人工智能(ArtificiaIntelligence,AI)成熟的一年。随着AlphaGo击败人类顶级围棋手,我们真正见证了人工智能的巨大潜力,并开始期待更复杂、更尖端的人工智能技术可以应用在更多的领域,包括无人驾驶、生物医疗、金融等。 如今,人工智能技术在各行各业都显示出了优势。人们自然希望像AlphaGo这样的由大数据驱动的人工智能技术能够很快在生活中应用起来。然而,现实有些令人失望:除了少数行业,大多数领域只拥有有限的数据或质量较差的数据,这使AI技术的落地比我们想象的更困难。是否通过跨组
机器学习AI算法工程
2022/08/26
8730
联邦学习助力人工智能新模型进化(附:金融隐私计算实战项目)
技术争鸣!七大主题报告,四大技术专题,AI开发者大会首日议程全回顾
11 月 8 日,北京诺金酒店,2018 AI开发者大会(AI NEXTCon)第一天议程圆满结束,这是值得铭记的一天。
AI科技大本营
2018/12/10
1.1K0
技术争鸣!七大主题报告,四大技术专题,AI开发者大会首日议程全回顾
刚发布华智冰的那家AI初创公司,背后有着怎么样的故事?
2021 年 6 月 1 日,由北京智源人工智能研究院(以下简称“智源”)主办的 2021 北京智源大会在北京中关村国家自主创新示范区会议中心成功开幕。包括 Yoshua Bengio、David Patterson 等图灵奖获得者在内的两百余位国内外人工智能领域顶尖学术和产业领袖,齐聚一堂,共同见证 AI 大模型时代的启航,探索人工智能发展的美好明天。
AI科技评论
2021/07/02
1.1K0
深度学习深陷可解释性泥淖,而这个研究领域正逐步焕发生机
提到 AI,大家马上想到计算机视觉、语音识别、自动驾驶、自然语言处理、芯片这些热门技术领域,这些领域的技术人才如今正受到企业们的疯抢。不过近年来,随着知识图谱技术不断被提及,作为 AI 领域底层的技术,其升温之势已经开始显现。不仅是 NLP 领域,大数据甚至是计算机视觉领域的背后都需要知识图谱技术的支持,企业内部更是开始组建专业的技术团队来支持、优化自己的产品。
AI科技大本营
2018/10/22
8820
深度学习深陷可解释性泥淖,而这个研究领域正逐步焕发生机
AISummit全球人工智能技术大会,洞悉AI技术的现在与未来
北京时间2022年8月6日上午9点,由51CTO精心策划,以“驱动•创新•数智”为主题的AISummit全球人工智能技术大会将在线上正式拉开帷幕!此次大会专题覆盖“计算机视觉、自然语言处理、算法与模型、推荐系统、机器学习、智慧金融”等众多技术细分领域。论道人工智能的行业驱动力,研讨人工智能的前沿创新技术,共话人工智能时代下的“数智”浪潮。
火星情报局
2022/08/04
4870
推荐阅读
腾讯看点CTO徐羽: QQ浏览器背后的推荐AI中台 | AICon
8360
响铃:人人都在喊“ALL IN AI”,真姿势和假动作到底差在哪?
2980
算法开发人员的安身之本:如何将机器学习与各行各业进行深度结合
9440
2018全球机器学习技术大会40位大神即将开讲!
9470
AISummit全球人工智能技术大会顺利开幕:首日精彩回顾
4010
业界 | 爆华为要发布人工智能手机 黑科技还是噱头?
8340
阿里、京东、快手、华为......他们是如何构建一个个推荐系统“帝国”的?
1.6K0
探索AI实践最优解,AISummit全球人工智能技术大会完美落幕
7280
开发者转型AI看过来,这是一场汇聚中美顶尖专家的AI盛会
3840
快手技术VP王仲远对于AI、大模型、深度学习的预测
9180
活动推荐 | 全球AI技术开放日,7月14日上海(含优惠码)
1.3K0
Al时代,阿里、诺基亚、同花顺等技术大咖带你直击人工智能多场景应用实践!
1.1K0
中国AI开发者真实现状:写代码这条路,会走多久?
6830
大牛书单 | 人工智能方向好书推荐
1.4K0
【AI女神节特稿】你不能不知道的13位中国人工智能女性
1.9K0
联邦学习助力人工智能新模型进化(附:金融隐私计算实战项目)
8730
技术争鸣!七大主题报告,四大技术专题,AI开发者大会首日议程全回顾
1.1K0
刚发布华智冰的那家AI初创公司,背后有着怎么样的故事?
1.1K0
深度学习深陷可解释性泥淖,而这个研究领域正逐步焕发生机
8820
AISummit全球人工智能技术大会,洞悉AI技术的现在与未来
4870
相关推荐
腾讯看点CTO徐羽: QQ浏览器背后的推荐AI中台 | AICon
更多 >
LV.1
北京博文视点资讯有限公司
目录
  • 生成数据库数据
  • Mysql 数据库事务基础
    • 数据库的并发一致性问题
    • 数据库事务的隔离级别
  • BeginTransaction() 和 TransactionScope 的区别
    • BeginTransaction()
    • 可以不手动撤销
    • TransactionScope
    • 总结
  • DML 是否可以使用事务
  • 顺序多操作
  • 嵌套事务
  • 事务范围
  • 封装 DbContext
    • TransactionScope
    • BeginTransaction()
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档