Sql 在一个事务中插入和更新时服务器死锁

Sql server deadlock when insert and update in one transaction

我正在使用 Azure SQL + Java + Spring Boot 2。目前我正在尝试了解发生死锁的原因。在一个事务中,我正在对相同的 table 但不同的行进行插入和稍后更新。据我了解,SQL 服务器默认使用 rowlockread_committed 隔离。

这里是死锁的详细信息:

<deadlock>
    <victim-list>
        <victimProcess id="process2b8618644e8"/>
    </victim-list>
    <process-list>
        <process id="process2b8618644e8" taskpriority="0" logused="352"
                 waitresource="KEY: 16:72057594043760640 (5f601e0257f1)" waittime="1321" ownerId="9246067"
                 transactionname="implicit_transaction" lasttranstarted="2019-02-17T13:03:49.683" XDES="0x2b868778458"
                 lockMode="U" schedulerid="2" kpid="288932" status="suspended" spid="118" sbid="0" ecid="0" priority="0"
                 trancount="2" lastbatchstarted="2019-02-17T13:03:49.793" lastbatchcompleted="2019-02-17T13:03:49.760"
                 lastattention="1900-01-01T00:00:00.760" clientapp="Microsoft JDBC Driver for SQL Server"
                 hostname="TZ-MacBook-Pro.local" hostpid="0" loginname="master" isolationlevel="read committed (2)"
                 xactid="9246067" currentdb="16" currentdbname="test" lockTimeout="4294967295" clientoption1="671088672"
                 clientoption2="128058">
            <executionStack>
                <frame procname="unknown" queryhash="0x431f2517c2d3feb8" queryplanhash="0x3a793ad664472011" line="1"
                       stmtstart="110" stmtend="238"
                       sqlhandle="0x02000000060c530e608cef3b3e4e4712f48c40d2efd9b04d0000000000000000000000000000000000000000">
                    unknown
                </frame>
                <frame procname="unknown" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="1"
                       sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
                    unknown
                </frame>
            </executionStack>
            <inputbuf>
                (@P0 int,@P1 nvarchar(4000),@P2 nvarchar(4000),@P3 int)update bug set lock=@P0, status=@P1 where
                bug_id=@P2 and lock=@P3
            </inputbuf>
        </process>
        <process id="process2b84f702108" taskpriority="0" logused="700"
                 waitresource="KEY: 16:72057594043760640 (185e9bfc7320)" waittime="1287" ownerId="9246090"
                 transactionname="implicit_transaction" lasttranstarted="2019-02-17T13:03:49.727" XDES="0x2b868764458"
                 lockMode="U" schedulerid="1" kpid="334812" status="suspended" spid="115" sbid="0" ecid="0" priority="0"
                 trancount="2" lastbatchstarted="2019-02-17T13:03:49.830" lastbatchcompleted="2019-02-17T13:03:49.790"
                 lastattention="1900-01-01T00:00:00.790" clientapp="Microsoft JDBC Driver for SQL Server"
                 hostname="TZ-MacBook-Pro.local" hostpid="0" loginname="master" isolationlevel="read committed (2)"
                 xactid="9246090" currentdb="16" currentdbname="test" lockTimeout="4294967295" clientoption1="671088672"
                 clientoption2="128058">
            <executionStack>
                <frame procname="unknown" queryhash="0x431f2517c2d3feb8" queryplanhash="0x3a793ad664472011" line="1"
                       stmtstart="110" stmtend="238"
                       sqlhandle="0x02000000060c530e608cef3b3e4e4712f48c40d2efd9b04d0000000000000000000000000000000000000000">
                    unknown
                </frame>
                <frame procname="unknown" queryhash="0x0000000000000000" queryplanhash="0x0000000000000000" line="1"
                       sqlhandle="0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000">
                    unknown
                </frame>
            </executionStack>
            <inputbuf>
                (@P0 int,@P1 nvarchar(4000),@P2 nvarchar(4000),@P3 int)update bug set lock=@P0, status=@P1 where
                bug_id=@P2 and lock=@P3
            </inputbuf>
        </process>
    </process-list>
    <resource-list>
        <keylock hobtid="72057594043760640" dbid="16" objectname="b51ce3c4-02f0-4dfa-89dd-3ab2f45beac7.dbo.bug"
                 indexname="bug_pk" id="lock2b8658c6f00" mode="X" associatedObjectId="72057594043760640">
            <owner-list>
                <owner id="process2b84f702108" mode="X"/>
            </owner-list>
            <waiter-list>
                <waiter id="process2b8618644e8" mode="U" requestType="wait"/>
            </waiter-list>
        </keylock>
        <keylock hobtid="72057594043760640" dbid="16" objectname="b51ce3c4-02f0-4dfa-89dd-3ab2f45beac7.dbo.bug"
                 indexname="bug_pk" id="lock2b856aa7f80" mode="X" associatedObjectId="72057594043760640">
            <owner-list>
                <owner id="process2b8618644e8" mode="X"/>
            </owner-list>
            <waiter-list>
                <waiter id="process2b84f702108" mode="U" requestType="wait"/>
            </waiter-list>
        </keylock>
    </resource-list>
</deadlock>

此外,我在我的 id 列上使用了聚簇索引,并且随机操作顺序没有问题。此外,当我只在事务内部插入时,一切正常,没有死锁。 如果我切换到 PostgreSql,则不会出现死锁问题。

那么为什么我在 Azure SQL 中遇到了死锁?

这是Java源代码:

    @Transactional
    public void bug() {
        BugEntity bugEntity = bugRepo.findById("1234")
                .orElseGet(() -> createBugEntity());

        if (bugEntity.getStatus().equals("SomeStatus")) {
            bugEntity.setStatus("NewStatus");
            bugRepo.save(bugEntity);
        }

    }

    private BugEntity createBugEntity() {
        BugEntity bugEntity = BugEntity.builder()
                .bugId(UUID.randomUUID().toString())
                .status("SomeStatus")
                .data("SomeData")
                .build();
        return bugRepo.save(bugEntity);
    }

实体class:

@AllArgsConstructor
@NoArgsConstructor
@Data
@Builder
@Entity(name = "bug")
@DynamicUpdate
public class BugEntity {

    @Id
    String bugId;
    String status;
    String data;

    LocalDateTime dateTime;

    @Version
    int lock;


}

为了重现死锁,我使用了带有几个并行请求的 gatling 模拟。

死锁的一个常见原因是需要查询和索引调优的查询接触到的数据超出了需要。

死锁跟踪显示参数作为 nvarchar(4000) 数据类型传递。这可能会阻止 varchar 列上的索引得到有效使用,因为 nvarchar 的数据类型优先级高于 varchar。当从应用程序数据类型推断出参数数据类型时,此问题尤为常见,因为字符串通常是 Unicode,例如在 Java 和 .NET 语言中,导致 nvarchar 参数与基础列数据类型无关.

解决方案是对字符串使用 varchar 参数而不是 nvarchar,除非基础列是 nvarchar。如果您不在数据库中使用 nvarchar,请指定 JDBC 连接字符串参数 sendStringParametersAsUnicode,如 中所述。或者,更改应用程序代码或框架配置以对 varchar 列使用 varchar 参数类型。

最佳做法是确保参数与基础数据库列的类型相匹配。这将提高性能和并发性,并且还有其他好处。