上面提到的是semi-consistent read的功能实现,除此之外,MySQL针对semi-consistent read,还做了优化措施:对于update scan返回的不满足条件的记录,提前放锁。
MySQL Server层流程:
sql_update.cc::mysql_update()
// 判断当前scan返回的记录,是否满足update的where条件
// 若满足,则进行update操作
if (!(select && select->skip_record())
…
// 若不满足update的where条件,则选择将当前记录上的行锁提前释放
else
table->file->unlock_row();
InnoDB Engine层流程:
ha_innobd.cc::unlock_row();
switch (prebuilt->row_read_type)
// 若系统未设置参数innodb_locks_unsafe_for_binlog,同时隔离级别大于
// TRX_ISO_READ_COMMITTED,则不可提前释放不满足条件的行锁
// 否则可以提前释放不满足条件的行锁
case ROW_READ_WITH_LOCKS:
if (!srv_locks_unsafe_for_binlog &&
prebuilt->trx->isolation_level > TRX_ISO_READ_COMMITTED)
break;
// 若当前系统已采用SEMI_CONSISTENT read,但是没有锁等待,加锁直接成功
// 那么此时直接释放不满足条件的行锁
case ROW_READ_TRY_SEMI_CONSISTENT:
row_unlock_for_mysql();
// 若当前系统已采用SEMI_CONSISTENT read,并且有锁等待,构造了commit版本
// 没有在commit版本上加锁,因此也无锁可放,直接返回即可
case ROW_READ_DID_SEMI_CONSISTENT:
prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT;
break;
semi-consistent优缺点分析 优点
减少了更新同一行记录时的冲突,减少锁等待。
无并发冲突,读记录最新版本并加锁;有并发冲突,读事务最新的commit版本,不加锁,无需锁等待。
可以提前放锁,进一步减少并发冲突概率。
对于不满足update更新条件的记录,可以提前放锁,减少并发冲突的概率。
在理解了semi-consistent read原理及实现方案的基础上,可以酌情考虑使用semi-consistent read,提高系统的并发性能。
非冲突串行化策略,因此对于binlog来说,是不安全的
两条语句,根据执行顺序与提交顺序的不同,通过binlog复制到备库后的结果也会不同。不是完全的冲突串行化结果。
因此只能在事务的隔离级别为read committed(或以下),或者设置了innodb_locks_unsafe_for_binlog参数的情况下才能够使用。
set binlog_format=mixed;
set session transaction isolation level repeatable read;
create table t1(a int not null) engine=innodb DEFAULT CHARSET=latin1;
insert into t1 values (1),(2),(3),(4),(5),(6),(7);
session 1: session 2:
set autocommit=0;
update t1 set a = a + 10;
set binlog_format=mixed;
set session transaction isolation level read committed;
update t1 set a = a + 100 where a > 10;
此时,session 2不需要等待session 1,虽然session 1的更新后项满足session 2的条件,但是由于session 2进行了semi-consistent read,读取到的记录的前项为(1-7),不满足session 2的更新where条件,因此session 2直接返回。
session 2直接返回,0 rows affected。
构造unlock unmatched rowset binlog_format=mixed;
set session transaction isolation level repeatable read;
create table t1(a int not null) engine=innodb DEFAULT CHARSET=latin1;
insert into t1 values (1),(2),(3),(4),(5),(6),(7);
session 1: session 2:
set autocommit=0;
update t1 set a = a + 10;
commit;
set binlog_format=mixed;
set session transaction isolation level repeatable read;
set autocommit = 0;
update t1 set a = a + 100 where a < 10;
select * from t1 lock in share mode;