MyRocks事務鎖分析

zysql發表於2018-03-20

概述

MyRocks中RocksDB作為基於快照的事務引擎,其在事務支援上有別於InnoDB,有其自身的特點。在早期的月報[[myrocks之事務處理]](http://mysql.taobao.org/monthly/2016/11/02/) 中,我們對鎖的實現有過簡單的分析,本文會以一些例子來介紹MyRocks是如果來加鎖解鎖的。

鎖型別

MyRocks早期只支援排他鎖,支援SELEC… IN SHARE MODE後,MyRocks才開始引入共享鎖。

 /* Type of locking to apply to rows */
 enum { RDB_LOCK_NONE, RDB_LOCK_READ, RDB_LOCK_WRITE } m_lock_rows;

#587是關於共享鎖的一個有趣BUG,有興趣的同學可以看看。
MyRocks的鎖都是記憶體鎖,因此MyRocks事務不宜持有過多的鎖,以避免佔用過多的記憶體。
MyRocks通過引數rocksdb_max_row_locks來控制單個事務所持有鎖的總數。另外,rocksdb鎖系統還支援以下引數
max_num_locks:系統鎖個數總限制
expiration_time:鎖過期時間

如果鎖個數超出限制,客戶端會返回下面的錯誤

failed: 12054: Status error 10 received from RocksDB: Operation aborted: Failed to acquire lock due to max_num_locks limit

隔離級別

MyRocks的事務隔離級只支援的READ-COMMITED和REPEATABLE-READ。隔離級別的支援和snapshot密切相關,隔離級別為READ-COMMITED時,事務中每的個stmt都會建立一個snapshot, 隔離級別為REPEATABLE-REA時,只在事務開啟後第一個stmt建立一次snapshot。MyRocks中隔離級別不同不會影響加鎖和解鎖的行為,因此,後面在分析MyRocks的加鎖解鎖時不區分隔離級別。

隱式主鍵

MyRocks支援建立無主鍵的表,但RocksDB作為KV儲存,是需要KEY的。因此,RocksDB內部會給表增加一個名為”HIDDEN_PK_ID”的隱式主鍵列,此值自增,類似與自增列。此列對於MySQL server層是透明的,讀取表資料時會自動跳過”HIDDEN_PK_ID”列。

對於無主鍵的表,MyRocks的鎖都是加在隱式主鍵上的。

對於binlog複製來說,MyRocks隱式主鍵並不會提升複製速度,因為隱式主鍵對server層是透明的,主鍵列不會記入binlog。
因此,建議MyRocks表都指定主鍵。

加鎖分析

以此表結構來分析各類語句的加鎖情況。

create table t1(id int primary key, c1 int unique, c2 int, c3 int, key idx_c2(c2)) engine=rocksdb;
insert into t1 values(1,1,1,1);
insert into t1 values(2,2,2,2);
insert into t1 values(3,3,3,3);
insert into t1 values(4,4,4,4);
  • 示例 select
select * from t1;

MVCC, 普通讀不加鎖

  • 示例 select .. in share mode
select * from t1 where id=1 in share mode;

對主鍵id=1記錄加S鎖

  • 示例 select .. for update
select * from t1 where id=1 for update;

對主鍵id=1記錄加X鎖

  • 示例 insert
begin;
insert into t1 values(1,1,1,1);
rollback;

主鍵id=1加X鎖
唯一索引c1=1加X鎖

  • 示例 delete by主鍵
begin;
delete from t1 where id=1;
rollback;

主鍵id=1加X鎖

  • 示例 delete by唯一索引
begin;
delete from t1 where c1=2;
rollback;

主鍵id=2加X鎖,其他索引不加鎖

  • 示例 delete by普通索引
begin;
delete from t1 where c2=3;
rollback;

主鍵id=3加X鎖,其他索引不加鎖

  • 示例 delete by無索引
begin;
delete from t1 where c3=4;
rollback;

對主鍵每條加X鎖,其他索引不加鎖
實際上server層過濾不符合條件的行會釋放鎖,最終只對主鍵id=4加X鎖

  • 示例 delete by 主鍵不存在的行
begin;
delete from t1 where id=100;
rollback;

主鍵id=100加X鎖

  • 示例 delete by 其他索引不存在的行
begin;
delete from t1 where c1=100;
rollback;

沒有鎖可以加

以上例子基本可以覆蓋所有加鎖的情況,再舉例幾個例子練習下

  • 示例 select for update
begin;
select * from t1 where  c2=3 for update;
rollback;

主鍵id=3加X鎖, 其他索引不加鎖

  • 示例 update更新無索引列
begin;
update t1 set c3=5 where c3=4;
rollback;

對主鍵每條加X鎖,其他索引不加鎖
實際上server層過濾不符合條件的行會釋放鎖,最終只對主鍵id=4加X鎖

  • 示例 update更新索引列
begin;
update t1 set c2=5 where c3=4;
rollback;

對主鍵每條加X鎖,其他索引不加鎖
實際上server層過濾不符合條件的行會釋放鎖,最終只對主鍵id=4加X鎖
同時會對唯一索引c2=5加X鎖

對於無主鍵表的表說,RocksDB內部會有隱式主鍵,所加鎖都在隱式主鍵上

解鎖

事務提交或回滾時都會將事務所持有的鎖都釋放掉。
另外一種情況是,對於不滿足查詢條件的記錄,MySQL會提前釋放鎖。

總結

  • MyRocks只會對主鍵和唯一索引加鎖,普通索引不會加鎖。
  • 只有插入或更新了唯一索引時,才會對唯一索引加鎖,對唯一索引加鎖的目的是為了保證唯一性。
  • 按主鍵鎖定查詢不存在的行時,會對不存在的行主鍵加X鎖。
  • 按二級索引查詢時,只會對主鍵加鎖,不會對二級鎖引加鎖。
  • S鎖只應用於SELECT … IN SHARE MODE語句。

堆疊

最後提供一些堆疊資訊,方便學習

  • 走唯一索引對主鍵加鎖
#0  rocksdb::TransactionLockMgr::TryLock
#1  rocksdb::PessimisticTransactionDB::TryLock
#2  rocksdb::PessimisticTransaction::TryLock
#3  rocksdb::TransactionBaseImpl::GetForUpdate
#4  myrocks::Rdb_transaction_impl::get_for_update
#5  myrocks::ha_rocksdb::get_for_update
#6  myrocks::ha_rocksdb::get_row_by_rowid
#7  get_row_by_rowid
#8  myrocks::ha_rocksdb::read_row_from_secondary_key
#9  myrocks::ha_rocksdb::index_read_map_impl
#10 myrocks::ha_rocksdb::read_range_first
#11 handler::multi_range_read_next
#12 QUICK_RANGE_SELECT::get_next
#13 rr_quick
#14 mysql_delete
#15 mysql_execute_command
#16 mysql_parse
#17 dispatch_command
  • 提交時解鎖
#0  rocksdb::TransactionLockMgr::UnLockKey
#1  rocksdb::TransactionLockMgr::UnLock
#2  rocksdb::PessimisticTransactionDB::UnLock
#3  rocksdb::PessimisticTransaction::Clear
#4  rocksdb::PessimisticTransaction::Commit
#5  myrocks::Rdb_transaction_impl::commit_no_binlog
#6  commit
#7  myrocks::rocksdb_commit
#8  ha_commit_low
#9  TC_LOG_DUMMY::commit
#10 ha_commit_trans
#11 trans_commit
#12 mysql_execute_command
#13 mysql_parse
#14 dispatch_command


相關文章