显然,满足以下三个条件的任意一个是,都会被阻塞住:
1、prebuilt->row_read_type != ROW_READ_TRY_SEMI_CONSISTENT //以上三例都是1,都不满足
2、unique_search //检索元组具有唯一性
3、index != clust_index //当前检索记录使用的索引不是聚集索引
当使用非索引列检索时,三者皆不满足;
当使用二级索引列检索时,满足index != clust_index
当使用聚集索引列检索时,满足unique_search
如果无需goto lock_wait_or_error, 就会去构建对应记录的最老版本(row_sel_build_committed_vers_for_mysql),对于插入而言,显然最老版本就是NULL空指针了,因此如果根据非索引列检索,Session 2就好像看不到那条记录一样,直接返回了。
如果表上没有索引的话,那么对于任意插入的记录,更新操作都见不到插入的记录(但是会为插入操作创建记录锁)。
我们再来看另外一种情况:
SESSION 1:
CREATE TABLE t1 (c1 int primary key , c2 int, c3 int, key(c2));
INSERT INTO t1 VALUES (1,2,3);
BEGIN;
UPDATE t1 SET c3 = c3 +1 WHERE c1 = 1; // c3 from 3=>4
UPDATE t1 SET c3 = c3 +1 WHERE c1 = 1; // c3 from 4=>5
SESSION 2:
UPDATE t1 SET c3=c3+1 WHERE c3 = 4; // No block
UPDATE t1 SET c3=c3+1 WHERE c3 = 5; // No block
UPDATE t1 SET c3=c3+1 WHERE c3 = 3; //阻塞住
实际上我们通过semi consistent read 能读到最老版本的记录时会将prebuilt->row_read_type从ROW_READ_TRY_SEMI_CONSISTENT修改成ROW_READ_DID_SEMI_CONSISTENT。
当读完记录后,返回Server层,会判断是否进行了semi consistent read。如果该记录符合查询,并且进行了semi consistent read,那么就再读该记录,第二次再读时,如果SESSION1还没提交,就会进入锁等待,被阻塞住。如果记录不符合查询,那么就直接忽略掉。
在上述的3条SQL中,第一条和第二条构建的最老版本记录,都不满足c3=4 和c3=5,因此忽略掉,不阻塞。但是最老记录满足c3 =3 ,因此在第二次进入innodb层时被阻塞住。
相关代码(sql_update.cc, mysql_update函数)
684 while (!(error=info.read_record(&info)) && !thd->killed)
685 {
686 thd->inc_examined_row_count(1);
687 bool skip_record= FALSE;
688 if (qep_tab.skip_record(thd, &skip_record))
689 {
690 error= 1;
691 /*
692 Don’t try unlocking the row if skip_record reported an error since
693 in this case the transaction might have been rolled back already.
694 */
695 break;
696 }
697 if (!skip_record)
698 {
699 if (table->file->was_semi_consistent_read())
700 continue; /* repeat the read of the same row if it still exists */