Php InnoDB仅在引用id存在时插入记录(无外键)
外键可能是解决此问题的最佳方法。但是,我正在尝试了解表锁定/事务,因此我希望我们暂时可以忽略它们。 让我们假设我在InnoDB数据库中有两个表:Php InnoDB仅在引用id存在时插入记录(无外键),php,mysql,transactions,innodb,table-locking,Php,Mysql,Transactions,Innodb,Table Locking,外键可能是解决此问题的最佳方法。但是,我正在尝试了解表锁定/事务,因此我希望我们暂时可以忽略它们。 让我们假设我在InnoDB数据库中有两个表:类别和笑话;我正在使用PHP/MySQLi来完成这项工作。这些表看起来是这样的: CATEGORIES id (int, primary, auto_inc) | category_name (varchar[64]) ============================================================ 1
类别
和笑话
;我正在使用PHP/MySQLi来完成这项工作。这些表看起来是这样的:
CATEGORIES
id (int, primary, auto_inc) | category_name (varchar[64])
============================================================
1 knock, knock
JOKES
id (int, primary, auto_inc) | category_id (int) | joke_text (varchar[255])
=============================================================================
empty
这里有两个函数,每个函数都由不同的连接同时调用。这些电话是:delete\u category(1)
和add\u笑话(1,“interrupt cow.Interrup mooooooo!”)
现在,如果两个函数中的SELECT
语句同时执行,然后继续执行,delete\u category
将认为可以删除该类别,add\u joke
将认为可以将该笑话添加到现有类别中,因此,我将得到一个空的categories
表和joke
表中的一个条目,该条目引用了一个不存在的category\u id
如果不使用外键,您将如何解决此问题?
到目前为止,我最好的想法是做以下几点:
1) “锁定表格类别写入,笑话写入”
在删除类别的开头。然而,由于我使用的是InnoDB,我非常希望避免锁定整个表(尤其是经常使用的主表)
2) 在插入记录后,进行add_joke
事务处理,然后执行“从类别中选择id,其中id=“$category_id”“”
。如果此时不存在,则回滚事务。但是,由于add\u
中的两个SELECT
语句可能会返回不同的结果,因此我认为我需要研究事务隔离级别,这一点我并不熟悉
在我看来,如果我同时做了这两件事,它应该会像预期的那样工作。尽管如此,我还是渴望听到更多有见地的意见。谢谢。只有在没有匹配笑话的情况下,才能删除类别:
DELETE c FROM categories AS c
LEFT OUTER JOIN jokes AS j ON c.id=j.category_id
WHERE c.id = $category_id AND j.category_id IS NULL;
如果类别有任何笑话,联接将找到它们,因此外部联接将返回非空结果。WHERE子句中的条件消除了非空结果,因此整个删除将匹配零行
同样,仅当类别存在时,才能将笑话插入类别:
INSERT INTO jokes (category_id, joke_text)
SELECT c.id, '$joke_text'
FROM categories AS c WHERE c.id = $category_id;
如果没有此类类别,则SELECT返回零行,INSERT为no-op
这两种情况都会在categories表上创建一个共享锁(S锁)
S锁的演示:
在一个会话中,我运行:
mysql> INSERT INTO bar (i) SELECT SLEEP(600) FROM foo;
mysql> SHOW ENGINE INNODB STATUS\G
. . .
---TRANSACTION 3849, ACTIVE 1 sec
mysql tables in use 2, locked 2
2 lock struct(s), heap size 376, 1 row lock(s)
MySQL thread id 18, OS thread handle 0x7faefe7d1700, query id 203 192.168.56.1 root User sleep
insert into bar (i) select sleep(600) from foo
TABLE LOCK table `test`.`foo` trx id 3849 lock mode IS
RECORD LOCKS space id 22 page no 3 n bits 72 index `GEN_CLUST_INDEX` of table `test`.`foo` trx id 3849 lock mode S
在第二节课中,我运行:
mysql> INSERT INTO bar (i) SELECT SLEEP(600) FROM foo;
mysql> SHOW ENGINE INNODB STATUS\G
. . .
---TRANSACTION 3849, ACTIVE 1 sec
mysql tables in use 2, locked 2
2 lock struct(s), heap size 376, 1 row lock(s)
MySQL thread id 18, OS thread handle 0x7faefe7d1700, query id 203 192.168.56.1 root User sleep
insert into bar (i) select sleep(600) from foo
TABLE LOCK table `test`.`foo` trx id 3849 lock mode IS
RECORD LOCKS space id 22 page no 3 n bits 72 index `GEN_CLUST_INDEX` of table `test`.`foo` trx id 3849 lock mode S
您可以看到,这在表foo上创建了一个IS锁,在我正在读取的表foo的一行上创建了一个S锁
对于任何混合读/写操作,如SELECT…for UPDATE
,INSERT…SELECT
,CREATE TABLE…SELECT
,都会发生同样的情况,以阻止正在读取的行在作为写操作的源时被修改
IS锁是一个表级锁,用于防止对表进行DDL操作,因此,当此事务取决于表中的某些内容时,没有人发出DROP table
或ALTER table
。那么,您是在尝试向当前为空的类别添加笑话吗?既然你也在删除空类别,那你先删除它吗?这完全是为了学习锁定,还是真的发生了?没错。如果代码按特定顺序执行,则可以删除类别(因为没有笑话),但同时可以添加笑话(因为类别仍然存在)。这与我编写的一些旧代码有关,所以它可能会发生,我正试图用它来学习锁定/事务。好吧,这就是我感到困惑的原因。只要确保在删除类别()之前始终执行add_joke(),所有这些都可以避免。每个函数都是从不同的连接调用的。如果你有一个如何避免这个问题的想法,请发布一个答案。绝对精彩的答案,非常感谢-正是我想要的。