为啥不支持它的数据库可以简单地忽略 select_for_update?

Posted

技术标签:

【中文标题】为啥不支持它的数据库可以简单地忽略 select_for_update?【英文标题】:Why could select_for_update simply be ignored with databases that don't support it?为什么不支持它的数据库可以简单地忽略 select_for_update? 【发布时间】:2018-04-07 07:58:57 【问题描述】:

select_for_update 的 Django 文档说

在不支持 SELECT 的后端使用 select_for_update() ... FOR UPDATE(如 SQLite)将不起作用。选择 ... 更新 不会被添加到查询中,并且如果 select_for_update() 用于自动提交模式。

这让我觉得这是一个奇怪且具有潜在危险的决定,尤其是因为 select_for_update 用于锁定行。如果我编写使用select_for_update 的代码,我会相信它实际上会受到尊重!如果数据库后端不支持它,我希望 Django 要么退回到安全但效率较低的替代方案,或者,如果不存在,则抛出某种异常。

在这种情况下,似乎 Django 可以通过忽略不支持的 DB(例如 SQLite)上的 select_for_update 来突然默默地重新引入竞争条件。我的直觉说 Django 不会那样做,如果不支持它肯定有一些原因不需要它(也许不支持它的引擎使用完整的数据库锁定?)但我似乎无法在文档中找到任何具体的支持提出这个理论。这个问题似乎也不一定是 Django 特有的。

这让我对使用select_for_update 非常谨慎,尽管它可以很好地解决当前的一些问题。

【问题讨论】:

No on here很可能能够回答Django团队做出的设计决策。您最好提出它是一个错误(如果您认为它是一个错误)或尝试直接与他们交谈。 @Shadow 我对这个问题的希望是,如果没有人可以回答 Django 部分,也许他们至少可以回答 SQL DB 部分。基于此,我可以跟进一个错误或对 Django 的文档请求。 【参考方案1】:

对于允许减少事务隔离以提高并发访问速度的数据库引擎(例如 PostgreSQL、Oracle 和 mysql),SELECT FOR UPDATE 用于告诉数据库现在读取的行可能会被以后写。这可以避免并发事务中出现不一致的数据,甚至可以防止在某些情况下出现死锁。

在 SQLite 中,所有事务are serializable,也就是说,它的行为就像整个数据库都被锁定在每个事务周围。 (并且在自动提交模式下,每条语句都包含在一个隐式事务中。)

所以 SELECT FOR UPDATE,即使它被实现了,实际上也不会添加比已经存在的更多的锁定。忽略它对 SQLite 来说是正确的。

【讨论】:

这就是我认为可能发生的事情。从更一般的意义上说,如果所有不支持 SELECT FOR UPDATE 的数据库都自动应用更粗略的锁,那么如果不支持,Django 忽略它是有意义的,因为事务被锁定在更高级别。不过他们的文档这么说就好了…… 追求这种思路使我找到了this answer,它详细介绍了不同的数据库如何处理锁。值得注意的是,张贴者明确提到 postgres、oracle 和 mySQL 是采用 SELECT FOR UPDATE 适用的锁定策略的引擎。这个列表与 Django 文档的支持select_for_update 的后端列表完全匹配,所以这可能是原因。如果您同意我的解释并将您的答案更新为更笼统一点,我会接受。 此页面指定在 SQLite 中,数据库不会围绕每个事务锁定:sqlite.org/lockingv3.html#transaction_control。只有在事务中写入后才能获得排他锁。 @MaartenVisscher 对于可序列化的事务,只有写操作才需要独占锁。但是读取仍然需要共享锁。 @mhvis 排他锁是排他的;存在共享锁时不能取。【参考方案2】:

从这个角度考虑。

您刚刚从 github 安装了一个库,并希望使用默认的 mysqlite 设置快速试用它 - 但您不能使用它,因为它们碰巧在一个地方使用了 select_for_update。你宁愿这个包工作(但有可能出现竞争条件)还是在你的脸上爆炸?

为了让他们的产品在它支持的所有不同配置下工作,Django 团队做出了许多妥协。理想情况下,Django 应用程序应该能够放入其中的任何一个中。如果竞争条件成为开发人员的一个问题,那么在他们意识到他们正在使用不合适的后端之前不需要进行大量研究,并且必须切换到更适合手头任务的后端。

换句话说,在这种情况下,Django 更倾向于兼容性而不是功能。

Django 可以通过忽略 select_for_update 突然安静地重新引入

它不会突然引入它们 - 更改数据库后端并非偶然 - 在生产环境中,这通常是一项不平凡的任务。我们不是在这里掷骰子。如果开发人员选择更改数据库,那么他们最好研究一下该数据库的优缺点 - 并对其进行测试。

【讨论】:

尽管如此。是的,该库可以在您有限的概念证明中运行,但是当您在生产环境中使用它并且有许多用户访问您的应用程序时,偶尔会发生竞争,并且您的数据会在没有警告的情况下损坏。您不知道要确保您的数据库支持select_for_update,因为库开发人员忘记了并且没有将其放入自述文件中。现在您的 production 数据库已经损坏...而您甚至都不知道!一个 Django 应用没有数据就什么都不是,所以做安全但不方便的事情总是爆炸比偶尔悄悄地破坏数据更可取。 如果有人在生产中使用 mysqlite - 他们应该得到它。正如我所说——这是一种权衡,他们必须在可靠性或兼容性之间做出选择。安全但不方便的事情可能是您的偏好 - 但不是每个人的偏好。我不会和你争论哪个是“正确的”,但我提出这个答案是因为你似乎没有考虑到这两种观点。

以上是关于为啥不支持它的数据库可以简单地忽略 select_for_update?的主要内容,如果未能解决你的问题,请参考以下文章

面试官问:为啥不建议使用 Select *?请你大声地回答他!!

为啥 select 语句没有从数据库表中选择所有行?它每次都忽略第一个条目

svn设置忽略文件为啥不管用

为啥 SELECT 查询不启动事务? [甲骨文]

为啥clang忽略__restrict__?

为啥我不能简单地添加一个包含所有列的索引?