MySQL检查记录的可删除性

2uluyalo  于 12个月前  发布在  Mysql
关注(0)|答案(1)|浏览(87)

考虑下面的表结构(我无耻地从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查询,如果由于约束而无法删除记录,则会抛出异常。我觉得这种通过异常进行流控制的做法并不是一个好的实践,而且似乎也没有明显的方法可以绕过它。”””或者有吗?**
我可以看到,有机会查询数据库,如果一个记录可以删除,可能会引入很多并发问题(假设记录不能删除,就在我得到我的(否定的)结果后,另一个用户删除了导致测试失败的最后一条记录。然后我手上就有了关于数据库状态的无效信息。
我咨询了thisthat,两者都没有给我满意的答案。

mefy6pfw

mefy6pfw1#

下面是一个生成查询的原型,用于检查子表中是否存在行:
假设你有一个表parent,它有两个子表child1child2,每个子表都有一个指向父表的外键。
如果需要检查子表中是否存在引用父表主键值1的行:

select concat('select exists(select * from `', table_schema, '`.`', table_name,
  '` where `', column_name, '`=1 for update)') AS _sql
from information_schema.key_column_usage
where referenced_table_name='parent';

输出:

+-----------------------------------------------------------------------------+
| _sql                                                                        |
+-----------------------------------------------------------------------------+
| select exists(select * from `test`.`child1` where `parent_id`=1 for update) |
| select exists(select * from `test`.`child2` where `parent_id`=1 for update) |
+-----------------------------------------------------------------------------+

演示:https://dbfiddle.uk/34gVPVr9
通过使用for update,这将在它找到的行上创建一个锁定读取。这将防止其他会话删除这些行,直到事务结束。
但是,即使for update也不能防止另一个竞争条件:某些并发会话可能会在您运行select查询之后立即插入新行,因此您可能会错误地假设parent中的某行已准备好被删除,即使在您删除它时它将具有相关行。
解决第二个争用条件的唯一方法是在所有子表上保留表锁,以防止插入。您可以使用LOCK TABLES来实现这一点,但这是一个非常侵入性的解决方案,因为它会在应用程序中创建并发瓶颈。
我建议您使用更简单、更可靠的方法来完成最初的操作,只需尝试删除parent中的一行,然后依靠异常来告诉您是否成功。
我知道有common advice against using exception handling as flow control,但我认为在这种情况下,使用异常来防止在没有竞争条件的情况下破坏引用完整性,这是一种较小的邪恶。

相关问题