mysql5.7只读事务_Innodbreadonly事务、MySQL5.7和
Percon。。。
前⾔
只读事务在MySQL5.6中引⼊,改进了创建视图快照的开销,减少了持有trx_sys->mutex的时间,这有利于提升只读性能;这⼀点已经⼴为⼈知;
本⽂的内容基本按照读代码的顺序来的,先了解了下Oracle MySQL5.6.15的只读事务部分代码,再看了Percona5.6对于事务部分的相关改进;随后⼤概过了下Oracle MySQL5.7对事务部分的优化;select按钮是什么意思
总的来说,Percona移植了其在5.5上所做的优化,⽽Oracle MySQL5.7优化的更彻底,很多代码都重构了。
本⽂不涉及到性能测试,只是代码阅读过程的笔记,记录的⽬的是⽅便以后查阅⽅便,因此同时也附带上了⼀些新版本修改的Rev号。
1.如何使⽤只读事务
a.设置变量tx_read_only,当全局设置为true时,涉及到的SQL只能是只读的。这个参数可以是session 级别,也可以是全局级别;
开启该参数后,就默认所有查询⾛只读的逻辑;
b.开启事务时指明:
START TRANSACTION READ ONLY;
c.autocommit状态下的查询操作也会被当做只读事务
如果事务中混合了DML操作,就会报如下错误:
root@test 09:57:58>delete from t1;
mysql怎么读英语ERROR 1792 (25006): Cannot execute statement in a READ ONLY transaction.
2.只读事务涉及的代码逻辑(MySQL5.6)
Innodb将所有的事务对象维护在链表上,通过trx_sys来管理,在5.6中,最明显的变化就是事务链表被拆分成了两个链表:
⼀个是只读事务链表:ro_trx_list,其他⾮标记为只读的事务对象放在链表rw_trx_list上;
这种分离,使得读写事务链表⾜够⼩,创建readview 的MVCC快照的速度更快;
a.开始⼀个事务
⼊⼝函数trx_start_low
1)判断事务是否是只读的;
trx->auto_commit = (trx->api_trx && trx->api_auto_commit)
|| thd_trx_is_auto_commit(trx->mysql_thd);
trx->read_only =
(trx->api_trx && !trx->read_write)
|| (!trx->ddl && thd_trx_is_read_only(trx->mysql_thd))
|| srv_read_only_mode;
if (!trx->auto_commit) {
++trx->will_lock;html难不难
} else if (trx->will_lock == 0) {
trx->read_only = TRUE;
}
trx->no = TRX_ID_MAX      //初始值被设置为⼀个极⼤值
trx->id = trx_sys_get_new_trx_id(); //事务id为当前最⼤的事务id(trx_sys->max_trx_id)
2)对于read_only的事务,⽆需去为其分配回滚段(trx_assign_rseg_low)
3)对于read only的事务,只有不是non-locking autocommit select时,才将trx对象加⼊到ro_trx_list上;
也就是说,autocommit的只读查询⽆需加⼊活跃事务链表。
对于⾮只读事务,加⼊到rw_trx_list上;
b.创建read view快照
每次显式start transaction with consistent snapshot(在repeatable read 隔离级别下)或者事务的第⼀条SELECT,都需要去创建⼀个rearview
创建read view的⽬的是了限定该查询的事务可见性
trx_assign_read_view->read_view_open_now->read_view_open_now_low
从函数read_view_open_now_low 可以看出,在创建read view时,只需要考虑读写事务链表,这有别于之前版本需要扫描全部事务,因为这是trx_sys->mutex的保护之下,因此可以提升性能。
具体的,⾸先根据读写事务链表的长度分配read view及⼀个事务id数组,两者分配在同⼀块内存 (view = read_view_create_low(n_trx, heap));
然后将当前活跃的(状态不是TRX_STATE_COMMITTED_IN_MEMORY)读写事务id(rw_trx_list)拷贝到view->trx_ids数组中,id顺序为降序
ut_list_map(trx_sys->rw_trx_list, &trx_t::trx_list, CreateView(view));
view->low_limit_no被设置为当前活跃事务中最⼩的trx->no(在trx_commit->trx_commit_low->trx_write_serialisation_history-
>trx_serialisation_number_get中被赋值,设为当前最⼤事务trx_sys->max_trx_id+1)。
在扫描完所有的读写事务后,设置up_limit_id为当前活跃读写事务的最⼩事务id;
low_limit_no的意思是该read view在读多版本时,⽆需去读事务号⼩于这个值的undo⽇志;
low_limit_id表⽰所有事务id⼤于等于该值的事务所做的修改都不应该被该view看到;
求职简历免费模板下载up_limit_id 表⽰所有⼩于该值的事务,都能被当前view可见;
然后根据low_limit_no顺序降序将其插⼊到rx_sys->view_list链表中(read_view_add(view))
c.判断事务可见性
通过函数read_view_sees_trx_id来进⾏判断,对于活跃的事务,通过⼆分查来判断
d.事务提交
backtrace: innobase_commit->trx_commit_for_mysql->trx_commit->trx_commit_in_memory
这时候对于不同的事务类型有所区分:
#对于autocommit no-lock的事务类型,直接设置完事务状态,从trx sys的read view链表中移除即可;
#对于正常开启的事务,先释放锁(lock_trx_release_locks()),再分别从只读事务和读写事务链表中移除;
对于read only的事务,也需要调⽤lock_trx_release_locks,举个例⼦:
START TRANSACTION READ ONLY;
select * from sbtest1 where id = 999 lock in share mode;
3.Percona对创建read view的改进
Percona在5.5.30及5.6.11之后的版本中对readview这部分逻辑做了修改,Percona的官⽅博客对此进⾏了描述;
⼤体的修改为:
a.在开启⼀个事务时,如果是读写事务,那么会为其在⼀个全局数组中保留⼀个slot(trx_start_low->trx
_reserve_descriptor(trx))
trx_sys->descriptors是维护活跃事务id的数组,新的事务 id会从数组尾部开始到位置插⼊其id值;数组以事务id升序排列
trx_sys->descr_n_used 表⽰当前读写事务的个数;
b.创建read view的内存分配(read_view_create_low)不再是从trx对象的heap中分配,⽽是使⽤malloc分配,分配好后cache下来,存储在trx->prebuilt_view中,下次重⽤该事务对象(trx_t)时就可以重复使⽤. read_view成员新增max_trx_ids,⽤于维持活跃事务id数组的长度,只有当前事务链表⼤于该值时,才需要重分配,分配的数组⼤⼩为当前活跃读写事务数的1.1倍.
c.由于已经将事务id有序的存储在数组trx_sys->descriptors中,那么这⾥只需要将这个数组(除了当前事务id)直接进⾏memcpy即可;这相⽐Oracle MySQL5.6的便利链表的⽅式效率更⾼。
有⼈可能注意到,在5.6原⽣逻辑中,遍历活跃读写事务链表时,还要到最⼩的trx->no,将其复制给view->low_limit_no,在Percona的改进⾥增加了⼀个链表trx_sys->trx_serial_list,⽤于维护那些已经分配了序列号的事务(见函数trx_serialisation_number_get),由于分配的过程是有序的,因此只需要取列表的第⼀个节点即可;
相关函数:read_view_open_now_low
d.在判断事务可见性时,直接使⽤c++的bsearch函数;
4.MySQL
5.7的事务系统及相关改进
a.MySQL5.7在这部分的代码基本上重构了,⼤量使⽤C++的类,对于我这样习惯了innodb C语⾔格式的⼈来说,还真有点觉得别扭。(Rev:6203)
从其在Rev:6203 commit的⽇志来看,包含以下改进:
1. Refactor the MVCC code
2. Reuse read views for AC-NL-RO selects
3. Use a pool of read views
4. Add MVCC class
5. Use a trx_id to trx_t* map
6. Keep the active trx_id_ts in a vector.
号查询颜
7. Pre-allocate a small cache of record and table locks
8. Avoid extra work when a transaction is tagged as read-only (during commit).
彩虹发卡网9. General code cleanup
⼤概扫了下:
#只读事务不考虑innodb的commit concurrency,提交时不调⽤trx_commit_complete_for_mysql, 不考虑auot-inc 锁, ⽆需去唤醒master线程,等等等;
#所有MVCC操作使⽤⼀个新类MVCC来进⾏重构;
#系统初始化时,会预先创建1024个read view(trx_sys_create);
所有cache的read view被放到MVCC::m_free链表中;
另外⼀个链表是m_views, ⽤于存储所有活跃或者标记为关闭的readview,当前只有auto commit no-lock read only的SQL使⽤这⼀优化,重⽤上⼀个事务的read view;如果只读期间,没有任何的分配事
务id,也就是没有写操作(trx_sys->max_trx_id未发⽣变化),那么这个read view会被直接接着使⽤;(函数MVCC::view_open)
#创建readview的代码路径和之前不同,但⼊⼝皆为trx_assign_read_view,调⽤trx_sys->mvcc->view_open(trx->read_view, trx)
直接从m_free链表中使⽤⼀个空闲的readview,⽆需分配内存(MVCC::get_view)
#拷贝活跃事务id的⾏为(ReadView::prepare)和Percona版本的类似,都是新加了⼀个list,trx_sys->serialisation_list来维护进⼊commit阶段分配了序列号的事务(trx->no),直接使⽤内存拷贝,因为在创建读写事务时,已经在trx_sys->rw_trx_ids中维护了事务id。
#检查事务可见性(view->changes_visible(trx_id))
#除了事务read view外,还为锁系统也分配了内存(rec_pool,table_pool)
b.⽆需显式的开启⼀个只读事务,⾃动识别(Rev:5209)
#默认情况下,所有的事务都认为以只读的⽅式开启(除⾮事务被显式标⽰为读写操作)
#当遇到写操作,或者需要加IX/X锁时,转换为读写模式(见函数trx_start_if_not_started_xa_low);
#只读事务不分配事务id(trx_start_low);但对于只读查询但创建了临时表的场景,将其设置为读写事务
#实际上已经没有读事务队列了(Rev:6788);
c.同样的事务对象trx_t也为其预分配了内存,(Rev:5744),默认为4M字节的连续内存;
在5.7⾥增加了⼀套标准类来处理类似的需要pool的场景

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。