Я сталкиваюсь с некоторыми проблемами взаимоблокировки в EFCore, когда у меня высокая частота обновлений в одних и тех же таблицах.
БД размещена на Azure.
У нас есть несколько различных служб, которыеиметь несколько потоков, каждый из которых имеет уникальный DataContext, который обновляет базу данных. Иногда транзакции застряли навсегда в состоянии LCK_M_U.
Запросы довольно просты:
UPDATE [SomeTable] SET [Status] = @p0, [StatusLastChanged] = @p1 WHERE [Id] = @p2 AND [Status] = @p3 AND [StatusLastChanged] = @p4
Таблицы настроены в контексте следующим образом.
modelBuilder.Entity<SomeTable>().ToTable(nameof(SomeTable));
modelBuilder.Entity<SomeTable>(entity =>
{
entity.Property(e => e.Id).UseIdentityColumn();
entity.Property(e => e.EmailId);
entity.Property(e => e.OrganisationId);
entity.Property(e => e.MessageId);
entity.Property(e => e.MimeType).HasMaxLength(256);
entity.Property(e => e.Name).HasMaxLength(256);
entity.Property(e => e.Status).IsConcurrencyToken();
entity.Property(e => e.StatusLastChanged).IsConcurrencyToken();
entity.Property(e => e.ErpOrderNumber);
});
В пакете только 1 обновление. Упрощенная версия логики:
using (var innerScope = _services.CreateScope())
{
var _context = innerScope.ServiceProvider.GetService<IDataContext>();
var row = _context.SomeTable.FirstAsync(_ => _.Id == someint && _.Status = somestring);
// Some business logic
row.Status = newStatusString;
var cts = new CancellationTokenSource(5000);
await _context.SaveChangesAsync(cts.Token);
}
Когда транзакция застревает, поток останавливается, и ничего не происходит, CancellationToken не генерирует.
Никогда не будет транзакции записи для той же самойстроки, но с одной и той же таблицей может быть много параллельных транзакций.
Все записи заблокированы для идентификатора строки.
Есть ли способ включить блокировку строки в efcore какспособ смягчения блокировки таблицы?
Обновление 1:
Вот все индексы в рассматриваемой таблице.
ALTER TABLE [dbo].[SomeTable] ADD PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ONLINE = OFF) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [nci_wi_SomeTable_D30B07CA6D9DA2DF1D34A2E0631935A7] ON [dbo].[SomeTable]
(
[EmailId] ASC
)
INCLUDE([ErpOrderNumber],[MimeType],[Name],[Status],[StatusLastChanged]) WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX [IX_SomeTable_OrganisationId] ON [dbo].[SomeTable]
(
[OrganisationId] ASC
)WITH (STATISTICS_NORECOMPUTE = OFF, DROP_EXISTING = OFF, ONLINE = OFF) ON [PRIMARY]
GO
В таблице нет триггеров
Вот план запроса
<?xml version="1.0" encoding="utf-16"?>
<ShowPlanXML xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.539" Build="15.0.1900.210" xmlns="http://schemas.microsoft.com/sqlserver/2004/07/showplan">
<BatchSequence>
<Batch>
<Statements>
<StmtSimple StatementCompId="3" StatementEstRows="1" StatementId="1" StatementOptmLevel="TRIVIAL" CardinalityEstimationModelVersion="140" StatementSubTreeCost="0.0232854" StatementText="UPDATE [SomeTable] SET [Status] = @p0, [StatusLastChanged] = @p1
WHERE [Id] = @p2 AND [Status] = @p3 AND [StatusLastChanged] = @p4" StatementType="UPDATE" QueryHash="SOMEHASH" QueryPlanHash="SOMEHASH" RetrievedFromCache="true" StatementSqlHandle="SOMEHASH" DatabaseContextSettingsId="13" ParentObjectId="0" StatementParameterizationType="1" SecurityPolicyApplied="false">
<StatementSetOptions ANSI_NULLS="true" ANSI_PADDING="true" ANSI_WARNINGS="true" ARITHABORT="true" CONCAT_NULL_YIELDS_NULL="true" NUMERIC_ROUNDABORT="false" QUOTED_IDENTIFIER="true" />
<QueryPlan DegreeOfParallelism="1" CachedPlanSize="40" CompileTime="3" CompileCPU="3" CompileMemory="320">
<MemoryGrantInfo SerialRequiredMemory="0" SerialDesiredMemory="0" GrantedMemory="0" MaxUsedMemory="0" />
<OptimizerHardwareDependentProperties EstimatedAvailableMemoryGrant="22649218" EstimatedPagesCached="2831152" EstimatedAvailableDegreeOfParallelism="2" MaxCompileMemory="8244936" />
<QueryTimeStats CpuTime="0" ElapsedTime="0" />
<RelOp AvgRowSize="9" EstimateCPU="2E-06" EstimateIO="0.02" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" LogicalOp="Update" NodeId="1" Parallel="false" PhysicalOp="Clustered Index Update" EstimatedTotalSubtreeCost="0.0232854">
<OutputList />
<RunTimeInformation>
<RunTimeCountersPerThread Thread="0" ActualRows="0" Batches="0" ActualEndOfScans="1" ActualExecutions="1" ActualExecutionMode="Row" ActualElapsedms="0" ActualCPUms="0" ActualScans="0" ActualLogicalReads="0" ActualPhysicalReads="0" ActualReadAheads="0" ActualLobLogicalReads="0" ActualLobPhysicalReads="0" ActualLobReadAheads="0" />
</RunTimeInformation>
<Update DMLRequestSort="false">
<Object Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Index="[PK__SomeTa__3214EC0794FBBF60]" IndexKind="Clustered" Storage="RowStore" />
<Object Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Index="[nci_wi_SomeTable_D30B07CA6D9DA2DF1D34A2E0631935A7]" IndexKind="NonClustered" Storage="RowStore" />
<SetPredicate>
<ScalarOperator ScalarString="[someDataBase].[dbo].[SomeTable].[Status] = RaiseIfNullUpdate([Expr1002]),[someDataBase].[dbo].[SomeTable].[StatusLastChanged] = [@p1]">
<ScalarExpressionList>
<ScalarOperator>
<MultipleAssign>
<Assign>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="Status" />
<ScalarOperator>
<Intrinsic FunctionName="RaiseIfNullUpdate">
<ScalarOperator>
<Identifier>
<ColumnReference Column="Expr1002" />
</Identifier>
</ScalarOperator>
</Intrinsic>
</ScalarOperator>
</Assign>
<Assign>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="StatusLastChanged" />
<ScalarOperator>
<Identifier>
<ColumnReference Column="@p1" />
</Identifier>
</ScalarOperator>
</Assign>
</MultipleAssign>
</ScalarOperator>
</ScalarExpressionList>
</ScalarOperator>
</SetPredicate>
<RelOp AvgRowSize="274" EstimateCPU="1E-07" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" LogicalOp="Compute Scalar" NodeId="2" Parallel="false" PhysicalOp="Compute Scalar" EstimatedTotalSubtreeCost="0.00328338">
<OutputList>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="Id" />
<ColumnReference Column="Expr1002" />
<ColumnReference Column="Expr1007" />
</OutputList>
<RunTimeInformation>
<RunTimeCountersPerThread Thread="0" ActualRows="0" Batches="0" ActualEndOfScans="1" ActualExecutions="1" ActualExecutionMode="Row" ActualElapsedms="0" ActualCPUms="0" />
</RunTimeInformation>
<ComputeScalar>
<DefinedValues>
<DefinedValue>
<ColumnReference Column="Expr1007" />
<ScalarOperator ScalarString="[Expr1007]">
<Identifier>
<ColumnReference Column="Expr1007" />
</Identifier>
</ScalarOperator>
</DefinedValue>
</DefinedValues>
<RelOp AvgRowSize="274" EstimateCPU="1E-07" EstimateIO="0" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" LogicalOp="Compute Scalar" NodeId="3" Parallel="false" PhysicalOp="Compute Scalar" EstimatedTotalSubtreeCost="0.00328338">
<OutputList>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="Id" />
<ColumnReference Column="Expr1002" />
<ColumnReference Column="Expr1007" />
</OutputList>
<ComputeScalar>
<DefinedValues>
<DefinedValue>
<ColumnReference Column="Expr1002" />
<ScalarOperator ScalarString="CONVERT_IMPLICIT(nvarchar(255),[@p0],0)">
<Convert DataType="nvarchar" Length="510" Style="0" Implicit="true">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@p0" />
</Identifier>
</ScalarOperator>
</Convert>
</ScalarOperator>
</DefinedValue>
<DefinedValue>
<ColumnReference Column="Expr1007" />
<ScalarOperator ScalarString="CASE WHEN CASE WHEN [someDataBase].[dbo].[SomeTable].[Status] = CONVERT_IMPLICIT(nvarchar(255),[@p0],0) THEN (1) ELSE (0) END AND CASE WHEN [someDataBase].[dbo].[SomeTable].[StatusLastChanged] = [@p1] THEN (1) ELSE (0) END THEN (0) ELSE (1) END">
<IF>
<Condition>
<ScalarOperator>
<Logical Operation="AND">
<ScalarOperator>
<IF>
<Condition>
<ScalarOperator>
<Compare CompareOp="BINARY IS">
<ScalarOperator>
<Identifier>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="Status" />
</Identifier>
</ScalarOperator>
<ScalarOperator>
<Convert DataType="nvarchar" Length="510" Style="0" Implicit="true">
<ScalarOperator>
<Identifier>
<ColumnReference Column="@p0" />
</Identifier>
</ScalarOperator>
</Convert>
</ScalarOperator>
</Compare>
</ScalarOperator>
</Condition>
<Then>
<ScalarOperator>
<Const ConstValue="(1)" />
</ScalarOperator>
</Then>
<Else>
<ScalarOperator>
<Const ConstValue="(0)" />
</ScalarOperator>
</Else>
</IF>
</ScalarOperator>
<ScalarOperator>
<IF>
<Condition>
<ScalarOperator>
<Compare CompareOp="BINARY IS">
<ScalarOperator>
<Identifier>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="StatusLastChanged" />
</Identifier>
</ScalarOperator>
<ScalarOperator>
<Identifier>
<ColumnReference Column="@p1" />
</Identifier>
</ScalarOperator>
</Compare>
</ScalarOperator>
</Condition>
<Then>
<ScalarOperator>
<Const ConstValue="(1)" />
</ScalarOperator>
</Then>
<Else>
<ScalarOperator>
<Const ConstValue="(0)" />
</ScalarOperator>
</Else>
</IF>
</ScalarOperator>
</Logical>
</ScalarOperator>
</Condition>
<Then>
<ScalarOperator>
<Const ConstValue="(0)" />
</ScalarOperator>
</Then>
<Else>
<ScalarOperator>
<Const ConstValue="(1)" />
</ScalarOperator>
</Else>
</IF>
</ScalarOperator>
</DefinedValue>
</DefinedValues>
<RelOp AvgRowSize="280" EstimateCPU="0.0001581" EstimateIO="0.003125" EstimateRebinds="0" EstimateRewinds="0" EstimatedExecutionMode="Row" EstimateRows="1" EstimatedRowsRead="1" LogicalOp="Clustered Index Seek" NodeId="4" Parallel="false" PhysicalOp="Clustered Index Seek" EstimatedTotalSubtreeCost="0.0032831" TableCardinality="5198">
<OutputList>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="Id" />
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="Status" />
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="StatusLastChanged" />
</OutputList>
<RunTimeInformation>
<RunTimeCountersPerThread Thread="0" ActualRows="0" ActualRowsRead="1" Batches="0" ActualEndOfScans="1" ActualExecutions="1" ActualExecutionMode="Row" ActualElapsedms="0" ActualCPUms="0" ActualScans="0" ActualLogicalReads="2" ActualPhysicalReads="0" ActualReadAheads="0" ActualLobLogicalReads="0" ActualLobPhysicalReads="0" ActualLobReadAheads="0" />
</RunTimeInformation>
<IndexScan Ordered="true" ScanDirection="FORWARD" ForcedIndex="false" ForceSeek="false" ForceScan="false" NoExpandHint="false" Storage="RowStore">
<DefinedValues>
<DefinedValue>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="Id" />
</DefinedValue>
<DefinedValue>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="Status" />
</DefinedValue>
<DefinedValue>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="StatusLastChanged" />
</DefinedValue>
</DefinedValues>
<Object Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Index="[PK__SomeTa__3214EC0794FBBF60]" IndexKind="Clustered" Storage="RowStore" />
<SeekPredicates>
<SeekPredicateNew>
<SeekKeys>
<Prefix ScanType="EQ">
<RangeColumns>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="Id" />
</RangeColumns>
<RangeExpressions>
<ScalarOperator ScalarString="[@p2]">
<Identifier>
<ColumnReference Column="@p2" />
</Identifier>
</ScalarOperator>
</RangeExpressions>
</Prefix>
</SeekKeys>
</SeekPredicateNew>
</SeekPredicates>
<Predicate>
<ScalarOperator ScalarString="[someDataBase].[dbo].[SomeTable].[StatusLastChanged]=[@p4] AND [someDataBase].[dbo].[SomeTable].[Status]=[@p3]">
<Logical Operation="AND">
<ScalarOperator>
<Compare CompareOp="EQ">
<ScalarOperator>
<Identifier>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="StatusLastChanged" />
</Identifier>
</ScalarOperator>
<ScalarOperator>
<Identifier>
<ColumnReference Column="@p4" />
</Identifier>
</ScalarOperator>
</Compare>
</ScalarOperator>
<ScalarOperator>
<Compare CompareOp="EQ">
<ScalarOperator>
<Identifier>
<ColumnReference Database="[someDataBase]" Schema="[dbo]" Table="[SomeTable]" Column="Status" />
</Identifier>
</ScalarOperator>
<ScalarOperator>
<Identifier>
<ColumnReference Column="@p3" />
</Identifier>
</ScalarOperator>
</Compare>
</ScalarOperator>
</Logical>
</ScalarOperator>
</Predicate>
</IndexScan>
</RelOp>
</ComputeScalar>
</RelOp>
</ComputeScalar>
</RelOp>
</Update>
</RelOp>
<ParameterList>
<ColumnReference Column="@p1" ParameterDataType="datetimeoffset(7)" ParameterCompiledValue="'2019-11-05 10:28:13.0568842 +10:00'" ParameterRuntimeValue="'2019-11-05 10:28:13.0568842 +10:00'" />
<ColumnReference Column="@p0" ParameterDataType="nvarchar(4000)" ParameterCompiledValue="N'DocumentMapProcessed'" ParameterRuntimeValue="N'DocumentMapProcessed'" />
<ColumnReference Column="@p4" ParameterDataType="datetimeoffset(7)" ParameterCompiledValue="'2019-11-05 00:25:07.1376149 +00:00'" ParameterRuntimeValue="'2019-11-05 00:25:07.1376149 +00:00'" />
<ColumnReference Column="@p3" ParameterDataType="nvarchar(4000)" ParameterCompiledValue="N'MultiOrderParsed'" ParameterRuntimeValue="N'MultiOrderParsed'" />
<ColumnReference Column="@p2" ParameterDataType="int" ParameterCompiledValue="(2)" ParameterRuntimeValue="(2)" />
</ParameterList>
</QueryPlan>
</StmtSimple>
</Statements>
</Batch>
</BatchSequence>
</ShowPlanXML>