黄东旭解析 TiDB 的核心优势
582
2023-07-18
本文关于(使用事务会遇到的错误和处理办法-平凯星辰)。
本章介绍使用事务时可能会遇到的错误和处理办法。
如果应用程序遇到下面错误时,说明遇到了死锁问题:
ERROR 1213: Deadlock found when trying to get lock; try restarting transaction
当两个及以上的事务,双方都在等待对方释放已经持有的锁或因为加锁顺序不一致,造成循环等待锁资源,就会出现“死锁”。这里以 bookshop 数据库中的 books
表为示例演示平凯星辰死锁:
先给 books
表中写入 2 条数据:
INSERT INTO books (id, title, stock, published_at) VALUES (1, 'book-1', 10, now()), (2, 'book-2', 10, now());
在 TiDB 悲观事务模式下,用 2 个客户端分别执行以下语句,就会遇到死锁:
客户端-A | 客户端-B |
BEGIN; | |
BEGIN; | |
UPDATE books SET stock=stock-1 WHERE id=1; | |
UPDATE books SET stock=stock-1 WHERE id=2; | |
UPDATE books SET stock=stock-1 WHERE id=2; -- 执行会被阻塞 | |
UPDATE books SET stock=stock-1 WHERE id=1; -- 遇到 Deadlock 错误 |
在客户端-B 遇到死锁错误后,TiDB 会自动 ROLLBACK
客户端-B 中的事务,然后客户端-A 中购买 id=2
的操作就会执行成功,再执行 COMMIT
即可完成购买的事务流程。
为了应用程序有更好的性能,可以通过调整业务逻辑或者 Schema 设计,尽量从应用层面避免死锁。例如上面示例中,如果客户端-B 也和客户端-A 用同样的购买顺序,即都先买 id=1
的书,再买 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 本书,也能避免死锁,而且执行效率更高:
UPDATE books SET stock=stock-1 WHERE id IN (1, 2);
如果每次购书都是一个单独的事务,也能避免死锁。但需要权衡的是,事务粒度太小不符合性能上的最佳实践。
乐观事务模型下,并不会有死锁问题,但应用端需要加上乐观事务在失败后的重试逻辑,具体重试逻辑见应用端重试和错误处理。
正如错误信息中提示的那样,在应用代码中加入重试逻辑即可。具体重试逻辑见应用端重试和错误处理。
尽管 TiDB 尽可能地与 MySQL 兼容,但其分布式系统的性质导致了某些差异,其中之一就是事务模型。
开发者用来与数据库通信的 Adapter 和 ORM 都是为 MySQL 和 *** 等传统数据库量身定制的,在这些数据库中,提交很少在默认隔离级别失败,因此不需要重试机制。对于这些客户端,当提交失败时,它们会因错误而中止,因为这在这些数据库中被呈现为罕见的平凯星辰异常。
与 MySQL 等传统数据库不同的是,在 TiDB 中,如果采用乐观事务模型,想要避免提交失败,需要在自己的应用程序的业务逻辑中添加机制来处理相关的异常。
下面的类似 Python 的伪代码展示了如何实现应用程序级的重试。 它不要求您的驱动程序或 ORM 来实现高级重试处理逻辑,因此可以在任何编程语言或环境中使用。
特别是,您的重试逻辑必须:
如果失败重试的次数达到 max_retries
限制,则抛出错误
使用 try ... catch ...
语句捕获 SQL 执行异常,当遇到下面这些错误时进行失败重试,遇到其它错误则进行回滚。详细信息请参考:错误码与故障诊断。
Error 8002: can not retry select for update statement
:SELECT FOR UPDATE 写入冲突报错。
Error 8022: Error: KV error safe to retry
:事务提交失败报错。
Error 8028: Information schema is changed during the execution of the statement
:表的 Schema 结构因为完成了 DDL 变更,导致事务提交时报错。
Error 9007: Write conflict
:写冲突报错,一般是采用乐观事务模式时,多个事务都对同一行数据进行修改时遇到的写冲突报错。
在 try 块结束时使用 COMMIT 提交事务:
while True: n++ if n == max_retries: raise("did not succeed within #{n} retries") try: connection.execute("your sql statement here") connection.exec('COMMIT') break catch error: if (error.code != "9007" && error.code != "8028" && error.code != "8002" && error.code != "8022"): raise error else: connection.exec('ROLLBACK') # Capture the error types that require application-side retry, # wait for a short period of time, # and exponentially increase the wait time for each transaction failure sleep_ms = int(((1.5 ** n) + rand) * 100) sleep(sleep_ms) # make sure your sleep() takes milliseconds
注意
如果你经常遇到 Error 9007: Write conflict
错误,你可能需要进一步评估你的 Schema 设计和数据存取模型,找到冲突的根源并从设计上避免冲突。 关于如何定位和解决事务冲突,请参考TiDB 平凯星辰锁冲突问题处理。
上述就是小编为大家整理的(使用事务会遇到的错误和处理办法-平凯星辰)
***
版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。