阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

MySQL+InnoDB semi-consitent read原理及实现分析

198次阅读
没有评论

共计 5902 个字符,预计需要花费 15 分钟才能阅读完成。

semi-consistent 简介

对于熟悉 MySQL,或者是看过 InnoDB 源码的朋友们来说,可能会听说过一个新鲜的名词:semi-consistent read。何谓 semi-consistent read?以下一段文字,摘于 semi-consistent read 一文:

A type of read operation used for UPDATE statements, that is a combination of read committed and consistent read. When an UPDATE statement examines a row that is already locked, InnoDB returns the latest committed version to MySQL so that MySQL can determine whether the row matches the WHERE condition of the UPDATE. If the row matches (must be updated), MySQL reads the row again, and this time InnoDB either locks it or waits for a lock on it. This type of read operation can only happen when the transaction has the read committed isolation level, or when the innodb_locks_unsafe_for_binlog option is enabled.

简单来说,semi-consistent read 是 read committed 与 consistent read 两者的结合。一个 update 语句,如果读到一行已经加锁的记录,此时 InnoDB 返回记录最近提交的版本,由 MySQL 上层判断此版本是否满足 update 的 where 条件。若满足(需要更新),则 MySQL 会重新发起一次读操作,此时会读取行的最新版本(并加锁)。

semi-consistent read 只会发生在 read committed 隔离级别下,或者是参数 innodb_locks_unsafe_for_binlog 被设置为 true。

MySQL server 与 InnoDB 引擎是如何进行交互?InnoDB 引擎如何实现 semi-consistent read?请见下面的详细分析。

semi-consistent 实现

MySQL Server 层

从上面的描述中可以看出,semi-consistent read 仅仅针对于 update 操作,因此在 sql_update.cc 的 mysql_update 方法中,有如下调用:

sql_update.cc::mysql_update()

// 通知底层引擎,尝试进行 semi consistent read

    // 是否真正进行 semi consistent read,由底层引擎决定

table->file->try_semi_consistent_read(1);

// InnoDB 引擎决定当前 update 是否可以进行 semi-consistent read

        // 具体的处理方法,在下节中分析

ha_innodb.cc::try_semi_consistent_read(bool yes);

// 进行 update 的读与更新操作

// update 操作完成之后,关闭 semi-consistent read

table->file->try_semi_consistent_read(0);

MySQL Server 层处理 semi-consistent 较为简单,接下来看看 InnoDB Engine 的处理方式。

InnoDB Engine 层

InnoDB Engine 层面,对于 semi-consistent read 的处理,包括两方面的逻辑:

  • 判断当前语句是否可以支持 semi-consistent read
  • fetch next 时,对于 semi-consistent read 的特殊处理

是否采用 semi-consistent read

前面提到,MySQL Server 在 update 时,会调用引擎的 try_semi_consistent_read 方法,来尝试进行 semi-consistent read,而是否进行 semi-consistent read,则交由底层处理。

ha_innodb.cc::try_semi_consistent_read()

if (yes &&

(srv_locks_unsafe_for_binlog

|| prebuilt->trx->isolation_level <= TRX_ISO_READ_COMMITTED))

prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT;

简单分析下,当用户设置系统参数 innodb_locks_unsafe_for_binlog 为 true,或者是采用的事务隔离级别为 read committed(或以下)时,设置 prebuilt->row_read_type 参数,标识当前 update 语句使用 semi-consistent read,fetch next 时需要有针对性的做特殊处理。

Fetch Next 特殊处理逻辑

InnoDB fetch next 的主函数入口是 row_search_for_mysql,此函数如何针对性的处理 semi-consistent read 呢?

row0sel.c::row_search_for_mysql()

// 尝试对于定位到的记录加锁

err = sel_set_rec_lock();

case DB_LOCK_WAIT:

// 如果加锁需要等待,则判断是否可以进行 semi-consistent read

        // 判断条件为:

        // 1. prebuilt->row_read_type 必须设置为 ROW_READ_TRY_SEMI_CONSISTEN

        // 2. 当前 scan 必须是 range scan 或者是全表扫描,而非 unique scan

        // 3. 当前索引必须是聚簇索引

        // 4. 不满足以上三个条件,就不能进行 semi-consistent read,进行加锁等待

        // 注意:若不需要加锁等待,那么也不需要进行 semi-consistent read,直接

        // 读取记录的最新版本即可,没有加锁等待的开销。

if ((prebuilt->row_read_type != ROW_READ_TRY_SEMI_CONSISTENT)

            || unique_search

|| index != clust_index)

goto lock_wait_or_error;

// 可以进行 semi-consistent read,根据记录的当前版本,构造最新的 commit 版本

        // 若没有 commit 版本,当前版本为最新版本,则直接读取下一条记录

        // 若存在 commit 版本,则设置 did_semi_consistent_read 为 TRUE

row_sel_build_committed_vers_for_mysql();

if (old_vers == NULL)

goto next_rec;

did_semi_consistent_read = TRUE;

// 若本次 update scan,由于加锁等待,使用了 semi-consistent,则设置相应的参数

        // 该参数,在下一小节提到的 MySQL 针对 semi-consistent 优化中有用

if (did_semi_consistent_read)

prebuilt->row_read_type = ROW_READ_DID_SEMI_CONSISTENT;

else

prebuilt->row_read_type = ROW_READ_TRY_SEMI_CONSISTENT;

// 至此,InnoDB 的 fetch next 针对 semi-consistent read 的处理完毕

优化:Unlock unmatched row

上面提到的是 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 参数的情况下才能够使用。

测试用例

构造 semi-consistent read

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 row

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;

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;

session 1 在 session 2 开始前已经提交,session 2 可以进行 semi-consistent read。并且读到的都是 session 1 的更新后项,完成加锁。但是由于更新后项均不满足 session 2 的 where 条件,session 2 会释放所有行上的锁(由 MySQL Server 层判断并调用 unlock_row 方法释放行锁)。

此时,session 1 再次执行 select * from t1 lock in share mode 语句,直接成功。因为 session 2 已经将所有的行锁提前释放。

朋友们可以试试将 session 2 的隔离级别改为 repeatable read,那么此时 session 1 就会等待 session 2 提交。

本文永久更新链接地址:http://www.linuxidc.com/Linux/2017-02/140844.htm

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-22发表,共计5902字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中