事务错误处理

    如果应用程序遇到下面错误时,说明遇到了死锁问题:

    当两个及以上的事务,双方都在等待对方释放已经持有的锁或因为加锁顺序不一致,造成循环等待锁资源,就会出现“死锁”。这里以 数据库中的 表为示例演示死锁:

    先给 books 表中写入 2 条数据:

    在 TiDB 悲观事务模式下,用 2 个客户端分别执行以下语句,就会遇到死锁:

    在客户端-B 遇到死锁错误后,TiDB 会自动 ROLLBACK 客户端-B 中的事务,然后客户端-A 中购买 id=2 的操作就会执行成功,再执行 即可完成购买的事务流程。

    客户端-A客户端-B
    BEGIN;
    BEGIN;
    UPDATE books SET stock=stock-1 WHERE id=1;
    UPDATE books SET stock=stock-1 WHERE id=1; — 执行会被阻塞,当事务 A 完成后再继续执行
    UPDATE books SET stock=stock-1 WHERE id=2;
    UPDATE books SET stock=stock-1 WHERE id=2;
    COMMIT;
    COMMIT;

    或者直接用 1 条 SQL 购买 2 本书,也能避免死锁,而且执行效率更高:

    如果每次购书都是一个单独的事务,也能避免死锁。但需要权衡的是,事务粒度太小不符合性能上的最佳实践。

    乐观事务模型下,并不会有死锁问题,但应用端需要加上乐观事务在失败后的重试逻辑,具体重试逻辑见。

    正如错误信息中提示的那样,在应用代码中加入重试逻辑即可。具体重试逻辑见。

    尽管 TiDB 尽可能地与 MySQL 兼容,但其分布式系统的性质导致了某些差异,其中之一就是事务模型。

    与 MySQL 等传统数据库不同的是,在 TiDB 中,如果采用乐观事务模型,想要避免提交失败,需要在自己的应用程序的业务逻辑中添加机制来处理相关的异常。

    下面的类似 Python 的伪代码展示了如何实现应用程序级的重试。 它不要求您的驱动程序或 ORM 来实现高级重试处理逻辑,因此可以在任何编程语言或环境中使用。

    特别是,您的重试逻辑必须:

    • 如果失败重试的次数达到 max_retries 限制,则抛出错误
    • 使用 语句捕获 SQL 执行异常,当遇到下面这些错误时进行失败重试,遇到其它错误则进行回滚。详细信息请参考:。
      • Error 8022: Error: KV error safe to retry:事务提交失败报错。
      • Error 8028: Information schema is changed during the execution of the statement:表的 Schema 结构因为完成了 DDL 变更,导致事务提交时报错。
      • :写冲突报错,一般是采用乐观事务模式时,多个事务都对同一行数据进行修改时遇到的写冲突报错。
    • 在 try 块结束时使用 COMMIT 提交事务:

    注意