MySQL 事务是否未得到充分利用?

dee*_*392 1 mysql innodb transaction

我对事务有一个模糊的理解,这意味着你可以帮助服务器理解一系列查询是相关的。

场景:假设我有一系列创建用户的查询 - 插入用户信息记录,创建一个空的配置文件记录并初始化一些其他记录等。

如果出于某种原因脚本在中途失败,我需要做什么来确保与“创建用户查询”相关的所有事情都被撤消?

根据我阅读MySQL文档的理解,它就像使用START TRANSACTIONCOMMIT语句一样简单。

如果是这样,为什么这么多 CMS 忽视这样做呢?我想反对它的一个常见论点可能是,对于只处理几个记录更新的短脚本来说,这是没有必要的,但是我认为当您也处理复制环境时它也非常有益,这是不是正确的?

Ulr*_*bor 5

事务和保存点简介

在您的场景“创建新用户”中,基本过程可能如下:

BEGIN;
INSERT INTO `users` (`username`, `password`, ...) VALUES ("me", "XXX", ...);
SET @userid := LAST_INSERT_ID();
INSERT INTO `profile_pictures` (`user_id`, `profile_picture_path`)
  VALUES (@userid, '/somewhere/in/the/filesystem.jpg');
COMMIT;
Run Code Online (Sandbox Code Playgroud)

如果出于某种原因该过程应该在中间结束,那么在 之前的某处发出查询就足够了,ROLLBACK;而不是COMMIT. 所有更改都将被撤消。这是最基本的任务之一,可以通过事务来完成。

Rolando 提到的保存点可能会派上用场,例如,如果将行插入profile_pictures第一个,然后尝试将图像复制到所需位置,如果失败,则只想撤消第二个插入,而不是第一个。(如果复制图片失败,则没有理由取消整个用户注册。)为此,可以在 insert into 之后users、insert into 之前定义一个保存点profile_pictures

BEGIN;
INSERT INTO `users` (`username`, `password`, ...) VALUES ("me", "XXX", ...);
SET @userid := LAST_INSERT_ID();
SAVEPOINT before_profile_picture;
INSERT INTO `profile_pictures` (`user_id`, `profile_picture_path`)
  VALUES (@userid, '/somewhere/in/the/filesystem.jpg');
COMMIT;
Run Code Online (Sandbox Code Playgroud)

在此过程中,可以发出ROLLBACK TO before_profile_picture;撤消最后一个插入的命令,但保留插入的用户。COMMIT如果在回滚到特定保存点后发出,则 中的行将users被保存,而 中将不会留下任何内容profile_pictures

...以及为什么它们没有被广泛使用...

事务只有与存储引擎 InnoDB 结合才有意义,因为 MyISAM 根本不支持事务。InnoDB 及其锁定策略(行级锁定而不是像 MyISAM 那样的表锁定)与事务的结合使用显示了一些明显的优点,但也有一些不清楚的缺点。

问题是,为什么它们不那么常见,所以我们专注于缺点。最常见的缺点是死锁

死锁的解释

例如,当两个客户端同时尝试以下查询时,就会发生死锁:

客户1

BEGIN;
SELECT * FROM table1 FOR UPDATE; /* Query 1.1 */
SELECT * FROM table2 FOR UPDATE; /* Query 1.2 */
Run Code Online (Sandbox Code Playgroud)

客户2

BEGIN;
SELECT * FROM table2 FOR UPDATE; /* Query 2.1 */
SELECT * FROM table1 FOR UPDATE; /* Query 2.2 */
Run Code Online (Sandbox Code Playgroud)

我对四个查询进行了编号,以便在下面的解释中清楚地参考。

由于数据库需要以任何线性顺序执行查询,因此顺序可能是:

Query 1.1
Query 2.1
Query 1.2
Query 2.2
Run Code Online (Sandbox Code Playgroud)

这会很糟糕。查询1.1table1完全锁定。然后查询 2.1table2完全锁定。现在查询 1.2 想要锁定table2,但table2已被客户端 2 锁定。该查询将阻塞并等待另一个客户端完成其操作。因此,查询 2.2 被执行,但该查询想要获取table1被客户端 1 锁定的锁,因此将等待它完成。因此两个客户端都等待对方完成。这当然永远不会发生。InnoDB 检测到这种情况并自动回滚其中一个事务。

应用程序(!)收到错误,理论上有责任重新启动事务。尽管这在“错误”下运行,但死锁是事务数据库中的正常情况,并且重新启动事务对于应用程序来说应该或多或少是正常的工作流程:“如果事务因死锁而失败,请始终准备好重新发出事务。死锁不是危险。再试一次。”

这如何影响行为和应用程序开发

我们在上一节中发现,随着 InnoDB 和事务的引入,可能会发生死锁,应用程序必须能够应对这种死锁。

如果我说应用程序必须能够应对它,我基本上是指应用程序的开发人员必须有能力(和动机......)来应对它。这是交易中最大的问题之一!许多普通开发人员将死锁视为 DBMS 的缺陷,这种情况不应该发生,并且他们不知道如何处理它们。

这种情况直到最近 2 到 5 年左右才发生变化。

即使开源项目的开发人员了解使用 InnoDB 和事务的原因,但有些死锁还是有点乏味,需要花费大量的精力来解决。必须对数据库事务、隔离级别、锁定应用程序本身有透彻的了解,才能减少死锁的发生。查看MySQL 手册中的如何应对死锁,了解在这些情况下可能需要做什么。当优势不明显并且应该首先学习技能时,这通常被认为是太多的努力。

这也导致客户填写的支持票包含诸如“等待时间长”(因为事务需要等待其他事务来释放锁)、“奇怪的错误”(死锁)和类似问题等问题。客户不会也不想了解数据库理论、死锁或其他什么。他们对功能齐全的软件感兴趣。

如果一个人可以选择使用事务、应对死锁、学习技能、彻底分析应用程序以及以非技术人员可以理解正在发生的情况的任何方式回答支持请求,而另一方面只使用可以运行的 MyISAM 表在大多数情况下都很好,我确实理解为什么大多数项目花了这么长时间并且仍然需要时间来采用带有事务的 InnoDB。

我确实认为社交方面采用缓慢的主要原因仅仅是技术方面。希望这篇文章能够澄清有关交易使用的一些方面以及为什么它们的使用不那么广泛。