事务无法通过 Task.WhenAll 处理并行命令
Transaction can't handle parallel commands via Task.WhenAll
我的 Postgres 数据库中有一些主要的 table(如公司)和许多从属 table(如 CompanyAddresses、CompanyPaymentInfos 等):
CREATE TABLE Companies (
Id uuid NOT NULL PRIMARY KEY,
...);
CREATE TABLE CompanyAddresses(
CompanyId uuid NOT NULL PRIMARY KEY REFERENCES Companies(Id),
...);
CREATE TABLE CompanyPaymentInfos(
CompanyId uuid NOT NULL PRIMARY KEY REFERENCES Companies(Id),
...);
我在我的 C# 代码中使用标准库中的事务:
private TransactionScope GeTransactionScope()
{
return new TransactionScope(
TransactionScopeOption.RequiresNew,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted
},
TransactionScopeAsyncFlowOption.Enabled);
}
private async Task DoChange(...)
{
using (var scope = GeTransactionScope())
{
await Insert(Company);
await Task.WhenAll(
Insert(CompanyPaymentInfo),
Insert(CompanyAddress),
Insert(CompanyTags),
// so on
);
scope.Complete();
}
}
每个 Insert
命令只执行 SQL 代码 没有 任何内部事务。
在执行 DoChange 后我得到这个错误:
Npgsql.PostgresException (0x80004005): 23503: insert or update on
table "companyaddresses" violates foreign key constraint
"companyaddresses_companyid_fkey"
当然,我有很多问题,例如:
- 为什么会出现错误?
- 为什么我在插入 CompanyAddress 时出现错误,而不是 CompanyPaymentInfo?
如果我将 DoChange
更改为顺序执行,一切正常:
private void DoChange()
{
using (var scope = GeTransactionScope())
{
await Insert(Company);
await Insert(CompanyPaymentInfo);
await Insert(CompanyAddress);
await Insert(CompanyTags);
// ...
scope.Complete();
}
}
也许有帮助:
- 我用的是Net Core 2.0
- 我使用具有标准设置的 Postgres 10.4(ReadCommitted 作为隔离级别等)。此外,我已将
enlist=true
添加到我的连接字符串中以使交易正常进行。
- 我在
Insert
命令中使用 Npgsql 3.2.5 和 Dapper 1.50.2
这里没有什么神奇的,你得到错误是因为你在插入 CompanyAddress 时使用的连接不是你认为的那个。
这是一个新连接。当 ComapnyPaymentInfo insert 是 运行 时,您正在使用已经绑定到您的 t运行saction 的连接。它正在等待新命令,因为您在上一步中已经等待。
另一方面,Task.WhenAll() 的使用将尝试使用多线程。如果连接忙 运行 一个命令将不会被使用,一个新的命令将被生成。
请记住,当使用 T运行saction 时,您只有一个连接可用,您无法从并行性中获益。
我的 Postgres 数据库中有一些主要的 table(如公司)和许多从属 table(如 CompanyAddresses、CompanyPaymentInfos 等):
CREATE TABLE Companies (
Id uuid NOT NULL PRIMARY KEY,
...);
CREATE TABLE CompanyAddresses(
CompanyId uuid NOT NULL PRIMARY KEY REFERENCES Companies(Id),
...);
CREATE TABLE CompanyPaymentInfos(
CompanyId uuid NOT NULL PRIMARY KEY REFERENCES Companies(Id),
...);
我在我的 C# 代码中使用标准库中的事务:
private TransactionScope GeTransactionScope()
{
return new TransactionScope(
TransactionScopeOption.RequiresNew,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted
},
TransactionScopeAsyncFlowOption.Enabled);
}
private async Task DoChange(...)
{
using (var scope = GeTransactionScope())
{
await Insert(Company);
await Task.WhenAll(
Insert(CompanyPaymentInfo),
Insert(CompanyAddress),
Insert(CompanyTags),
// so on
);
scope.Complete();
}
}
每个 Insert
命令只执行 SQL 代码 没有 任何内部事务。
在执行 DoChange 后我得到这个错误:
Npgsql.PostgresException (0x80004005): 23503: insert or update on table "companyaddresses" violates foreign key constraint "companyaddresses_companyid_fkey"
当然,我有很多问题,例如:
- 为什么会出现错误?
- 为什么我在插入 CompanyAddress 时出现错误,而不是 CompanyPaymentInfo?
如果我将 DoChange
更改为顺序执行,一切正常:
private void DoChange()
{
using (var scope = GeTransactionScope())
{
await Insert(Company);
await Insert(CompanyPaymentInfo);
await Insert(CompanyAddress);
await Insert(CompanyTags);
// ...
scope.Complete();
}
}
也许有帮助:
- 我用的是Net Core 2.0
- 我使用具有标准设置的 Postgres 10.4(ReadCommitted 作为隔离级别等)。此外,我已将
enlist=true
添加到我的连接字符串中以使交易正常进行。 - 我在
Insert
命令中使用 Npgsql 3.2.5 和 Dapper 1.50.2
这里没有什么神奇的,你得到错误是因为你在插入 CompanyAddress 时使用的连接不是你认为的那个。
这是一个新连接。当 ComapnyPaymentInfo insert 是 运行 时,您正在使用已经绑定到您的 t运行saction 的连接。它正在等待新命令,因为您在上一步中已经等待。
另一方面,Task.WhenAll() 的使用将尝试使用多线程。如果连接忙 运行 一个命令将不会被使用,一个新的命令将被生成。
请记住,当使用 T运行saction 时,您只有一个连接可用,您无法从并行性中获益。