盡管Mariadb以及Facebook在long long time ago就fix掉了這個臭名昭著的問題,但官方直到 MySQL5.6 版本才Fix掉,本文主要關(guān)注三點: 1.MySQL 5.6的性能如何 2.在5.6中Group commit的三階段實現(xiàn)流程
新參數(shù)MySQL 5.6提供了兩個參數(shù)來控制binlog group commit: 單位為微妙,用于從flush隊列中取事務(wù)的超時時間,這主要是防止并發(fā)事務(wù)過高,導致某些事務(wù)的RT上升。 可以閱讀函數(shù)MYSQL_BIN_LOG::process_flush_stage_queue 來理解其功能
當設(shè)置為0時,事務(wù)可能以和binlog不相同的順序被提交,從下面的測試也可以看出,這會稍微提升點性能,但并不是特別明顯.
性能測試老規(guī)矩,先測試看看性能 sysbench, 全內(nèi)存操作,5個sbtest表,每個表1000000行數(shù)據(jù)
基本配置: innodb_flush_log_at_trx_commit=1 table_open_cache_instances=5 metadata_locks_hash_instances = 32 metadata_locks_cache_size=2048 performance_schema_instrument = ‘%=on’ performance_schema=ON innodb_lru_scan_depth=8192 innodb_purge_threads = 4
關(guān)閉Performance Schema consumer: mysql> update setup_consumers set ENABLED = ‘NO'; Query OK, 4 rows affected (0.02 sec) Rows matched: 12 Changed: 4 Warnings: 0
sysbench/sysbench –debug=off –test=sysbench/tests/db/update_index.lua –oltp-tables-count=5 –oltp-point-selects=0 –oltp-table-size=1000000 –num-threads=1000 –max-requests=10000000000 –max-time=7200 –oltp-auto-inc=off –mysql-engine-trx=yes –mysql-table-engine=innodb –oltp-test-mod=complex –mysql-db=test –mysql-host=$HOST –mysql-port=3306 –mysql-user=xx run
update_index.lua
我的機器在壓到1000個并發(fā)時,CPU已經(jīng)幾乎全部耗完。 可以看到,并發(fā)度越高,group commit的效果越好,在達到600以上并發(fā)時,設(shè)置sync_binlog=1或者0已經(jīng)沒有TPS的區(qū)別。 但問題是。我們的業(yè)務(wù)壓力很少會達到這么高的壓力,低負載下,設(shè)置sync_binlog=1依舊增加了單個線程的開銷。
另外也觀察到,設(shè)置binlog_max_flush_queue_time對TPS的影響并不明顯。
實現(xiàn)原理我們知道,binlog和innodb在5.1及以后的版本中采用類似兩階段提交的方式,關(guān)于group commit問題的前世今生,可以閱讀MATS的博客,講述的非常詳細。嗯,評論也比較有意思。。。。。
以下集中在5.6中binlog如何做group commit。在5.6中,將binlog的commit階段分為三個階段:flush stage、sync stage以及commit stage。5.6的實現(xiàn)思路和Mariadb的思路類似,都是維護一個隊列,第一個進入該隊列的作為leader線程,否則作為follower線程。leader線程收集follower的事務(wù),并負責做sync,follower線程等待leader通知操作完成。
這三個階段中,每個階段都會去維護一個隊列: Mutex_queue m_queue[STAGE_COUNTER]; 不同session的THD使用the->next_to_commit來鏈接,實際上,在如下三個階段,盡管維護了三個隊列,但隊列中所有的THD實際上都是通過next_to_commit連接起來了。
在binlog的XA_COMMIT階段(MYSQL_BIN_LOG::commit),完成事務(wù)的最后一個xid事件后,,這時候會進入MYSQL_BIN_LOG::ordered_commit,開始3個階段的流程:
###flush stage
change_stage(thd, Stage_manager::FLUSH_STAGE, thd, NULL, &LOCK_log) |–>stage_manager.enroll_for(stage, queue, leave_mutex) //將當前線程加入到m_queue[FLUSH_STAGE]中,如果是隊列的第一個線程,就被設(shè)置為leader,否則就是follower線程,線程會這其中睡眠,直到被leader喚醒(m_cond_done) |–>leader線程持有LOCK_log鎖,從change_state線程返回false.
flush_error= process_flush_stage_queue(&total_bytes, &do_rotate, &wait_queue); //只有l(wèi)eader線程才會進入這個邏輯 |–>首先讀取隊列,直到隊列為空,或者超時(超時時間是通過參數(shù)binlog_max_flush_queue_time來控制)為止,對讀到的每個線程做flush_thread_caches,將binlog刷到cache中。注意在出隊列的時候,可能還有新的session被append到隊列中,設(shè)置超時的目的也正在于此 |–>如果是超時,這時候隊列中還有session的話,就取出整個隊列的頭部線程,并將原隊列置空(fetch_queue_for),然后對取出的session進行flush_thread_caches |–>判斷總的寫入binlog的byte數(shù)是否超過max bin log size,如果超過了,就設(shè)置rotate標記
flush_error= flush_cache_to_file(&flush_end_pos); |–>將I/O Cache中的內(nèi)容寫到文件中
signal_update() //通知dump線程有新的Binlog
###sync stage
change_stage(thd, Stage_manager::SYNC_STAGE, wait_queue, &LOCK_log, &LOCK_sync) |–>stage_manager.enroll_for(stage, queue, leave_mutex) //當前線程加入到m_queue[SYNC_STAGE]隊列中,釋放lock_log鎖;同樣的如果是SYNC_STAGE隊列的leader,則立刻返回,否則進行condition wait. |–>leader線程加上Lock_sync鎖
final_queue= stage_manager.fetch_queue_for(Stage_manager::SYNC_STAGE); //從SYNC_STAGE隊列中取出來,并清空隊列,主要用于commit階段
std::pair<bool, bool> result= sync_binlog_file(false); //刷binlog 文件(如果設(shè)置了sync_binlog的話)
簡單的理解就是,在flush stage階段形成N批的組session,在SYNC階段又會由這N批組產(chǎn)生出新的leader來負責做最耗時的sync操作
###commit stage
commit階段受到參數(shù)binlog_order_commits限制 當binlog_order_commits關(guān)閉時,直接unlock LOCK_sync,由各個session自行進入Innodb commit階段(隨后調(diào)用的finish_commit(thd)),這樣不會保證binlog和事務(wù)commit的順序一致,如果你不關(guān)注innodb的ibdata中記錄的binlog信息,那么可以關(guān)閉這個選項來稍微提高點性能
當打開binlog_order_commits時,才會進入commit stage,如下描述的
change_stage(thd, Stage_manager::COMMIT_STAGE,final_queue, &LOCK_sync, &LOCK_commit) |–>進入新的COMMIT_STAGE隊列,釋放LOCK_sync鎖,新的leader獲取LOCK_commit鎖,其他的session等待
THD *commit_queue= stage_manager.fetch_queue_for(Stage_manager::COMMIT_STAGE); //取出并清空COMMIT_STAGE隊列
process_commit_stage_queue(thd, commit_queue, flush_error) |–>這里會遍歷所有的線程,然后調(diào)用ha_commit_low->innobase_commit進入innodb層依次提交
完成上述步驟后,解除LOCK_commit鎖
stage_manager.signal_done(final_queue); |–>將所有Pending的線程的標記置為false(thd->transaction.flags.pending= false)并做m_cond_done廣播,喚醒pending的線程
(void) finish_commit(the); //如果binlog_order_commits設(shè)置為FALSE,就會進入這一步來提交存儲引擎層事務(wù); 另外還會更新grid信息 Innodb的group commit和mariadb的類似,都只有兩次sync,即在prepare階段sync,以及sync Binlog文件(雙一配置),為了保證rotate時,所有前一個binlog的事件的redo log都被刷到磁盤,會在函數(shù)new_file_impl中調(diào)用如下代碼段: ha_flush_logs 會調(diào)用存儲引擎接口刷日志文件
參考文檔http://dimitrik./blog/archives/2012/06/mysql-performance-binlog-group-commit-in-56.html http://mysqlmusings./2012/06/binary-log-group-commit-in-mysql-56.html MySQL 5.6.10 source code
原創(chuàng)文章,轉(zhuǎn)載請注明: 轉(zhuǎn)載自Simple Life 本文鏈接地址: [MySQL 5.6] MySQL 5.6 group commit 性能測試及內(nèi)部實現(xiàn)流程 文章的腳注信息由WordPress的wp-posturl插件自動生成 |
|