mysql一个语句并发问题的思考
我们数据库的引擎是innodb
这是我们再做并发限制的时候,使用了一个语句insert into xx select .. from xx where yyyy
这种形式的语句, 如果表里没有这条记录就添加。
在初步考虑的时候,这种情况下如果第一个事务还没提交,那么除非数据库的隔离级别是脏读,否则是读不到的。
但是在测试过程中,却发现只有第一个insert会返回1并且会block后续的insert。
不太理解。找些相关的官方文档看下
-
INSERT sets an exclusive lock on the inserted row. This lock is an index-record lock, not a next-key lock (that is, there is no gap lock) and does not prevent other sessions from inserting into the gap before the inserted row.
insert会设置一个排他锁在insert row。这是一个基于index(即便表上没有index,mysql也会默认创建一个唯一索引,see https://dev.mysql.com/doc/refman/5.7/en/innodb-index-types.html), 不是一个next-key锁,不会阻止其他会话插入
-
INSERT INTO T SELECT … FROM S WHERE … sets an exclusive index record lock (without a gap lock) on each row inserted into T. If the transaction isolation level is READ COMMITTED, or innodb_locks_unsafe_for_binlog is enabled and the transaction isolation level is not SERIALIZABLE, InnoDB does the search on S as a consistent read (no locks). Otherwise, InnoDB sets shared next-key locks on rows from S. InnoDB has to set locks in the latter case: In roll-forward recovery from a backup, every SQL statement must be executed in exactly the same way it was done originally.
这个语句会设置一个index record的排他锁。 如果事务是read committed或者启用了innodb_locks_unsafe_for_binlog并且事务不是SERIALIZABLE, innodb不会在S上设置锁。否则, InnoDB会在S上设置一个next-key锁。 InnoDB必须对后一种情况加锁,因为在恢复的时候,每个sql语句都必须和原来的顺序一样执行。
看了官方文档,我们总结了两点:
- 如果有primary 或者其他的unique的锁,那么insert会基于加上index record的排他锁
- 如果是insert..select… 这种形式下,insert会加一个排他锁,select 会加上一个共享的锁。
对于我们这里的语句,insert表和select表是一致的,而且通过不同的insert发现也是需要顺序的。
第一个语句执行的共享锁执行并且释放过后,才能进行由第二个语句获取排它锁和共享锁。
这是个人的理解,如果有任何不对的地方敬请指正。