Tại sao một khóa ngoại từ một bảng để chính nó gây ra bế tắc khi chạy hai lần xóa?


7

Tôi có một cơ sở dữ liệu với một bảng "SelfRef". SelfRef có hai trường:

Id (guid, PK, not null)
SelfRefId (guid, nullable)

Có một ràng buộc khóa ngoài ánh xạ trường SelfRefId trở lại trường Id.

Tôi có một dự án EntityFrameworkCore tham chiếu cơ sở dữ liệu. Tôi đang chạy thử nghiệm sau:

  1. Tạo hai mục trong bảng SelfRef. Trong mỗi trường hợp, SelfRefId là null. Lưu thay đổi.
  2. Xóa cả hai mục trong các tác vụ riêng biệt, nhiều hơn hoặc ít hơn.

Tôi thấy rằng bước 2 thường gây ra bế tắc. Tôi không hiểu tại sao nó nên.

Tôi đang hiển thị mã của mình bên dưới, mặc dù tôi nghi ngờ vấn đề này là dành riêng cho mã này:

public class TestSelfRefDeadlock
{
    private async Task CreateSelfRef_ThenDelete_Deletes() {
        var sr = new SelfRef
        {
            Id = Guid.NewGuid(),
            Name = "SR"
        };
        var factory = new SelfRefDbFactory();
        using (var db = factory.Create()) {
            db.Add(sr);
            await db.SaveChangesAsync();  // EDIT: Changing this to db.SaveChanges() appears to fix the problem, at least in this test scenario.
        }
        using (var db = factory.Create()) {
            db.SelfRef.Remove(sr);
            await db.SaveChangesAsync();
        }
    }

    private IEnumerable<Task> DeadlockTasks() {
        for (int i=0; i<2; i++) {
            yield return CreateSelfRef_ThenDelete_Deletes();
        }
    }

    [Fact]
    public async Task LotsaDeletes_DoNotDeadlock()
        => await Task.WhenAll(DeadlockTasks());
}

EDIT: Tôi đã xác nhận rằng sự bế tắc tương tự xảy ra trong EF6.

Để tạo bảng trong cơ sở dữ liệu của tôi:

USE [SelfReferential]
GO

/****** Object:  Table [dbo].[SelfRef]    Script Date: 3/20/2018 3:43:50 PM ******/
SET ANSI_NULLS ON
GO

SET QUOTED_IDENTIFIER ON
GO

CREATE TABLE [dbo].[SelfRef](
    [Id] [uniqueidentifier] NOT NULL,
    [SelfReferentialId] [uniqueidentifier] NULL,
    [Name] [nchar](10) NULL,
 CONSTRAINT [PK_SelfRef] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

GO

ALTER TABLE [dbo].[SelfRef]  WITH CHECK ADD  CONSTRAINT [FK_SelfRef_SelfRef] FOREIGN KEY([SelfReferentialId])
REFERENCES [dbo].[SelfRef] ([Id])
GO

ALTER TABLE [dbo].[SelfRef] CHECK CONSTRAINT [FK_SelfRef_SelfRef]
GO

Để tạo các thực thể:

Scaffold-DbContext "Server=localhost;Database=SelfReferential;Trusted_Connection=True;" Microsoft.EntityFrameworkCore.SqlServer -Context SelfRefDb -OutputDir Entities -Force 

Lớp DbFactory:

public class SelfRefDbFactory : IFactory<SelfRefDb>
{
    private const string str1 = @"Data Source=MyPcName;Initial Catalog=SelfReferential;Integrated Security=True;ApplicationIntent=ReadWrite;";
    private const string str2 = @"Data Source=MyPcName;Initial Catalog=SelfReferential;Integrated Security=True;ApplicationIntent=ReadWrite;MultipleActiveResultSets=True";
    public SelfRefDb Create() {
        var options = new DbContextOptionsBuilder<SelfRefDb>()
            .UseSqlServer(str1).Options;
        return new SelfRefDb(options);
    }
}

Thông báo lỗi:

Message: System.InvalidOperationException : An exception has been raised that is likely due to a transient failure. If you are connecting to a SQL Azure database consider using SqlAzureExecutionStrategy.
---- Microsoft.EntityFrameworkCore.DbUpdateException : An error occurred while updating the entries. See the inner exception for details.
-------- System.Data.SqlClient.SqlException : Transaction (Process ID 58) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.

Một số sự kiện Sql từ profiler bên dưới. Tôi đang bỏ qua nhiều sự kiện "Đăng nhập kiểm toán" và "Đăng xuất kiểm toán" và các trường được sao chép từng cái một. Phải có một cách tốt hơn để trích xuất mọi thứ, nhưng tôi không biết nó là gì.

exec sp_executesql N'SET NOCOUNT ON;
INSERT INTO [SelfRef] ([Id], [Name], [SelfReferentialId])
VALUES (@p0, @p1, @p2);
',N'@p0 uniqueidentifier,@p1 nvarchar(10),@p2 
uniqueidentifier',@p0='93671E2E-28E5-414D-A3DB-239FA433640C',@p1=N'SR',@p2=NULL

Chạy đặc biệt này là với hai chủ đề. Sau hai sự kiện như trên, tôi thấy hai trong số:

exec sp_reset_connection 

sau đó hai như thế này:

exec sp_executesql N'SET NOCOUNT ON;
DELETE FROM [SelfRef]
WHERE [Id] = @p0;
SELECT @@ROWCOUNT;

',N'@p0 uniqueidentifier',@p0='F5B53458-08C5-485E-8364-2A2842E95158'

Thêm hai kết nối đặt lại, sau đó nó đã được thực hiện.

Bế tắc xml:

<deadlock-list>
    <deadlock victim="process1fe3db6b468">
        <process-list>
            <process id="process1fe3db6b468" taskpriority="0" logused="300" waitresource="KEY: 14:72057594041401344 (427c492d0b23)" waittime="147" ownerId="218910" transactionname="user_transaction" lasttranstarted="2018-03-22T14:33:57.880" XDES="0x2021f8bc408" lockMode="S" schedulerid="2" kpid="8540" status="suspended" spid="53" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-03-22T14:33:57.883" lastbatchcompleted="2018-03-22T14:33:57.880" lastattention="1900-01-01T00:00:00.880" clientapp=".Net SqlClient Data Provider" hostname="WILLIAMASUS" hostpid="14656" loginname="MicrosoftAccount\jockusch@gmail.com" isolationlevel="read committed (2)" xactid="218910" currentdb="14" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
                <executionStack>
                    <frame procname="adhoc" line="2" stmtstart="78" stmtend="154" sqlhandle="0x0200000087849c297464e5637211740e8fde989bf9ffc37a0000000000000000000000000000000000000000">
unknown     </frame>
                    <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown     </frame>
                </executionStack>
                <inputbuf>
(@p0 uniqueidentifier)SET NOCOUNT ON;
DELETE FROM [SelfRef]
WHERE [Id] = @p0;
SELECT @@ROWCOUNT;

    </inputbuf>
            </process>
            <process id="process1fe3db684e8" taskpriority="0" logused="300" waitresource="KEY: 14:72057594041401344 (8e30f77e2707)" waittime="146" ownerId="218908" transactionname="user_transaction" lasttranstarted="2018-03-22T14:33:57.880" XDES="0x20227f6b458" lockMode="S" schedulerid="1" kpid="8300" status="suspended" spid="54" sbid="0" ecid="0" priority="0" trancount="2" lastbatchstarted="2018-03-22T14:33:57.883" lastbatchcompleted="2018-03-22T14:33:57.880" lastattention="1900-01-01T00:00:00.880" clientapp=".Net SqlClient Data Provider" hostname="WILLIAMASUS" hostpid="14656" loginname="MicrosoftAccount\jockusch@gmail.com" isolationlevel="read committed (2)" xactid="218908" currentdb="14" lockTimeout="4294967295" clientoption1="673185824" clientoption2="128056">
                <executionStack>
                    <frame procname="adhoc" line="2" stmtstart="78" stmtend="154" sqlhandle="0x0200000087849c297464e5637211740e8fde989bf9ffc37a0000000000000000000000000000000000000000">
unknown     </frame>
                    <frame procname="unknown" line="1" sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
unknown     </frame>
                </executionStack>
                <inputbuf>
(@p0 uniqueidentifier)SET NOCOUNT ON;
DELETE FROM [SelfRef]
WHERE [Id] = @p0;
SELECT @@ROWCOUNT;

    </inputbuf>
            </process>
        </process-list>
        <resource-list>
            <keylock hobtid="72057594041401344" dbid="14" objectname="SelfReferential.dbo.SelfRef" indexname="PK_SelfRef" id="lock20229074e00" mode="X" associatedObjectId="72057594041401344">
                <owner-list>
                    <owner id="process1fe3db684e8" mode="X"/>
                </owner-list>
                <waiter-list>
                    <waiter id="process1fe3db6b468" mode="S" requestType="wait"/>
                </waiter-list>
            </keylock>
            <keylock hobtid="72057594041401344" dbid="14" objectname="SelfReferential.dbo.SelfRef" indexname="PK_SelfRef" id="lock20229073c80" mode="X" associatedObjectId="72057594041401344">
                <owner-list>
                    <owner id="process1fe3db6b468" mode="X"/>
                </owner-list>
                <waiter-list>
                    <waiter id="process1fe3db684e8" mode="S" requestType="wait"/>
                </waiter-list>
            </keylock>
        </resource-list>
    </deadlock>
</deadlock-list>

Có an toàn không khi cho rằng việc triển khai SelfRefDbFactory.Createhoàn toàn an toàn theo luồng, luôn trả về các phiên bản mới của bối cảnh cơ sở dữ liệu của bạn?

1
Bạn có thể sử dụng Profiler để nắm bắt các truy vấn thực tế mà nó đang gửi không?
HLGEM

1
@ Bánh quy kiểm tra DeadlockTasks. Nó chạy rất nhiều cuộc gọi đến CreateSelfRef_ThenDelete_Deletestrong các nhiệm vụ riêng biệt. Phương pháp này tạo ra hai bối cảnh khác nhau , tức là hai kết nối để sửa đổi hai thực thể khác nhau. Đó là quá nhiều hoạt động ảnh hưởng đến cùng một đối tượng chạy đồng thời. Tại sao làm điều này? Chỉ cần tạo một bối cảnh duy nhất , sửa đổi tất cả các thực thể khi cần và gọi SaveChangesmột lần
Panagiotis Kanavos 21/03/18

2
@Panagiotis, tôi nghĩ khá rõ ràng rằng OP đã viết bài kiểm tra này để cố tình chèn và xóa khỏi cùng một bảng - một điều chắc chắn không phải là một yêu cầu bất thường đối với nhiều ứng dụng. Có gì sai với nhiều bối cảnh? Đây không phải là một đối số về tiêu chuẩn hoặc hiệu suất mã hóa.

1
Bạn có thể thêm Deadlock Graph XML không?
Mark Sinkinson

Câu trả lời:


2

XML bế tắc chỉ ra rằng hai phiên đang chiến đấu trên hai hàng khác nhau, mỗi phiên có khóa clusive e (X) trên một và yêu cầu khóa (S) ở bên kia.

Được:

  1. Xóa PK yêu cầu trước tiên phải đảm bảo rằng không có tham chiếu FK nào hiện có
  2. Mức cô lập giao dịch là "Đọc cam kết"
  3. Bạn đang sử dụng kết nối tổng hợp
  4. Hai phiên cạnh tranh đến từ cùng một ứng dụng (theo hostpidgiá trị)
  5. vẻ như thực hiện Bước 1 dưới dạng async cho phép khắc phục sự cố trong khi không lưu async thì không
  6. Không có dấu hiệu nào cho thấy các giao dịch đang được sử dụng, mặc dù EF có thể thực hiện điều đó đằng sau hậu trường, đặc biệt là khi XML bế tắc hiển thị trancount="2"cho cả hai phiên

cũng có thể là:

  1. sử dụng tùy chọn async trên lưu sẽ thay đổi thời gian và / hoặc liệu lớp ứng dụng có bắt đầu giao dịch hay không
  2. nhóm kết nối đang cho phép các luồng trao đổi kết nối / phiên mà họ đang sử dụng

Bây giờ, công cụ tổng hợp kết nối (số 2) thường không gây ra bất kỳ sự cố nào, nhưng vì có những tình huống có thể xảy ra (chẳng hạn như nếu Giao dịch phân tán đang được sử dụng), tôi không muốn loại trừ. Và, vì tôi không biết tùy chọn EF và / hoặc tùy chọn async xử lý mọi thứ như thế nào, nên rất có thể là sự kết hợp giữa async và nhóm kết nối.

Vì vậy, tại sao trước tiên bạn không thử giữ async lưu cho Bước 1 nhưng vô hiệu hóa nhóm kết nối bằng cách thêm Pooling=false;vào chuỗi kết nối của bạn.

Tất nhiên, việc vô hiệu hóa việc kết nối kết nối có giúp ích hay không, cho rằng việc không sử dụng async trên lưu sẽ giải quyết vấn đề (hoặc ít nhất là xuất hiện cho đến nay), bạn nên cân nhắc không sử dụng async khi tạo các mục. Có lẽ chỉ sử dụng để xóa và chọn? Ngay cả khi chúng tôi xác định thay đổi chính xác về hành vi giữa việc sử dụng và không sử dụng async ở Bước 1, thì đó có thể không phải là bất cứ điều gì có thể giải quyết được (hoặc ít nhất là không giải quyết mà không làm những việc có lẽ không nên làm).


Tắt pooling dường như làm giảm xác suất bế tắc, nhưng không phải bằng không.
William Jockusch
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.