如何在efcore中使用行锁定以避免表阻塞?

问题描述 投票:1回答:1

当我在同一张表上进行大量更新时,我遇到了EFCore上的一些僵局问题。

数据库托管在蔚蓝上。

[我们有多个不同的服务,它们的多个线程都具有唯一的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);
}

[当事务卡住时,线程停滞,并且什么也没有发生,cancelToken不会抛出。

永远不会同时针对同一行执行写事务,但是针对同一表可能会有许多并发事务。

所有写入均锁定到行ID。

是否有办法在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&#xD;&#xA;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>
sql sql-server entity-framework entity-framework-core ef-core-3.0
1个回答
1
投票

您的问题提到了僵局,但症状表明只有阻塞。尽管两者都涉及锁,但从SQL角度来看,它们是不同的东西。如果由于死锁而没有收到SqlException,请考虑配置blocked process threshold (s) SQL Server配置选项并创建扩展事件跟踪以捕获blocked_process_report事件。 T-SQL示例:

--configure blocked process threshold to report blocking longer than 10 seconds
EXEC sp_configure 'blocked process threshold (s)', 10;

--create trace to capture blocked_process_report events
CREATE EVENT SESSION [blocking] ON SERVER 
ADD EVENT sqlserver.blocked_process_report
WITH (STARTUP_STATE=ON)
GO

这将提供被阻止的进程和锁的详细信息,可以从SSMS对象资源管理器实时查看(管理->扩展事件->会话->阻止,右键单击监视实时数据)。

如果无法使用此信息确定根本原因,请在跟踪中向您的问题添加示例block_process字段。

© www.soinside.com 2019 - 2024. All rights reserved.