考虑下面的表结构(我无耻地从this excellent answer中截取):
categories: products:
+----+------+ +----+---------+
| id | name | | id | name |
+----+------+ +----+---------+
| 1 | red | | 1 | mittens |
| 2 | blue | | 2 | boots |
+---++------+ +----+---------+
products_categories:
+------------+-------------+
| product_id | category_id |
+------------+-------------+
| 1 | 1 | // red mittens
| 1 | 2 | // blue mittens
| 2 | 1 | // red boots
| 2 | 2 | // blue boots
+------------+-------------+
在这个场景中,products_categories
的外键被设置为ON DELETE CASCADE
,所以当I DELETE FROM categories WHERE id = 2;
时,所有以2
作为category_id
的记录都会被删除。
假设我将类别的外键更改为ON DELETE RESTRICT
(无论如何,这似乎是默认值)。从categories
中删除任何记录现在都将失败,只要products_categories
中有一条记录引用了该记录。
**是否有任何方法可以确定特定记录上的DELETE
是否会失败?**DB显然能够将此作为DELETE
查询的一部分进行评估。
我有一个程序(C#),它可能想从一个由RESTRICT
约束限制的表中删除一条记录。目前我必须try { } catch(Exception ex) { }
DELETE
查询,如果由于约束而无法删除记录,则会抛出异常。我觉得这种通过异常进行流控制的做法并不是一个好的实践,而且似乎也没有明显的方法可以绕过它。”””或者有吗?**
我可以看到,有机会查询数据库,如果一个记录可以删除,可能会引入很多并发问题(假设记录不能删除,就在我得到我的(否定的)结果后,另一个用户删除了导致测试失败的最后一条记录。然后我手上就有了关于数据库状态的无效信息。
我咨询了this和that,两者都没有给我满意的答案。
1条答案
按热度按时间mefy6pfw1#
下面是一个生成查询的原型,用于检查子表中是否存在行:
假设你有一个表
parent
,它有两个子表child1
和child2
,每个子表都有一个指向父表的外键。如果需要检查子表中是否存在引用父表主键值
1
的行:输出:
演示:https://dbfiddle.uk/34gVPVr9
通过使用
for update
,这将在它找到的行上创建一个锁定读取。这将防止其他会话删除这些行,直到事务结束。但是,即使
for update
也不能防止另一个竞争条件:某些并发会话可能会在您运行select查询之后立即插入新行,因此您可能会错误地假设parent
中的某行已准备好被删除,即使在您删除它时它将具有相关行。解决第二个争用条件的唯一方法是在所有子表上保留表锁,以防止插入。您可以使用
LOCK TABLES
来实现这一点,但这是一个非常侵入性的解决方案,因为它会在应用程序中创建并发瓶颈。我建议您使用更简单、更可靠的方法来完成最初的操作,只需尝试删除
parent
中的一行,然后依靠异常来告诉您是否成功。我知道有common advice against using exception handling as flow control,但我认为在这种情况下,使用异常来防止在没有竞争条件的情况下破坏引用完整性,这是一种较小的邪恶。