#### 15.7.5.3 如何最小化和处理死锁

本节建立在关于死锁的概念信息之上[第 15.7.5.2 节，“死锁检测”](innodb-deadlock-detection.html).它解释了如何组织数据库操作以最小化死锁和应用程序中所需的后续错误处理。

[死锁](glossary.html#glos_deadlock)是事务数据库中的一个经典问题，但它们并不危险，除非它们太频繁以至于您根本无法运行某些事务。通常，您必须编写您的应用程序，以便它们始终准备好在事务由于死锁而回滚时重新发出事务。

`InnoDB`使用自动行级锁定。即使在仅插入或删除单行的事务的情况下，您也可能会出现死锁。那是因为这些操作并不是真正的“原子”；它们会自动对插入或删除的行的（可能是多个）索引记录设置锁定。

您可以使用以下技术应对死锁并降低其发生的可能性：

-   随时发出[`显示引擎 INNODB 状态`](show-engine.html)以确定最近死锁的原因。这可以帮助您调整应用程序以避免死锁。

-   如果频繁的死锁警告引起关注，请通过启用[`innodb_print_all_deadlocks`](innodb-parameters.html#sysvar_innodb_print_all_deadlocks)多变的。每个死锁的信息，不仅仅是最新的，都记录在 MySQL 中[错误日志](glossary.html#glos_error_log).完成调试后禁用此选项。

-   如果由于死锁而失败，请始终准备好重新发出事务。死锁并不危险。再试一次。

-   保持交易小且持续时间短，以减少它们发生冲突的可能性。

-   在进行一组相关更改后立即提交事务，以降低它们发生冲突的可能性。特别是，不要留下互动[**mysql**](mysql.html)会话以未提交的事务打开很长时间。

-   如果你使用[锁定读取](glossary.html#glos_locking_read)([`选择 ... 更新`](select.html)要么[`选择...分享`](select.html))，尝试使用较低的隔离级别，例如[`阅读已提交`](innodb-transaction-isolation-levels.html#isolevel_read-committed).

-   当修改一个事务中的多个表，或同一个表中的不同行集时，每次都以一致的顺序执行这些操作。然后事务形成定义明确的队列并且不会死锁。例如，将数据库操作组织成应用程序中的函数，或调用存储的例程，而不是编写多个相似的序列`插入`,`更新`， 和`删除`不同地方的说法。

-   为您的表添加精心挑选的索引，以便您的查询扫描更少的索引记录并设置更少的锁。采用[`解释选择`](explain.html)确定 MySQL 服务器认为哪些索引最适合您的查询。

-   使用较少的锁定。如果你有能力允许一个[`选择`](select.html)要从旧快照返回数据，请不要添加`更新`要么`分享`条款。使用[`阅读已提交`](innodb-transaction-isolation-levels.html#isolevel_read-committed)隔离级别在这里很好，因为同一事务中的每个一致读取都从自己的新快照中读取。

-   如果没有其他帮助，请使用表级锁序列化您的事务。正确的使用方法[`锁定表`](lock-tables.html)带有事务表，例如`InnoDB`表，是开始一个事务`设置自动提交 = 0`（不是[`开始交易`](commit.html)） 其次是[`锁定表`](lock-tables.html)，并且不调用[`解锁桌子`](lock-tables.html)直到您明确提交事务。例如，如果您需要写入表`t1`并从表中读取`t2`， 你可以这样做：

    ```
    SET autocommit=0;
    LOCK TABLES t1 WRITE, t2 READ, ...;
    ... do something with tables t1 and t2 here ...
    COMMIT;
    UNLOCK TABLES;
    ```

    表级锁可防止对表的并发更新，避免死锁，但代价是对繁忙系统的响应速度较慢。

-   序列化事务的另一种方法是创建一个仅包含一行的辅助“信号量”表。让每个事务在访问其他表之前更新该行。这样，所有交易都以串行方式发生。请注意，`InnoDB`即时死锁检测算法也适用于这种情况，因为序列化锁是行级锁。使用 MySQL 表级锁，必须使用 timeout 方法来解决死锁。
