Mysqlロックの内部実装メカニズムについて語る記事

青灯夜游
リリース: 2022-09-13 19:45:40
転載
2476 人が閲覧しました

この記事では、Mysql ロックの内部実装メカニズムについて説明します。皆様のお役に立てれば幸いです。

Mysqlロックの内部実装メカニズムについて語る記事

注: リストされているコードはすべて Mysql-5.6

# のものですが、リレーショナル データベースはますます進歩しています。最近ではより人気があり、それらはより類似していますが、その背後にある実装メカニズムはまったく異なる場合があります。実際に使用する場合、SQL 構文の仕様が存在するため、複数のリレーショナル データベースに慣れることは難しくありませんが、ロックの実装方法はデータベースの数だけ存在する可能性があります。

Microsoft SQL Server2005 は、以前はページ ロックのみを提供していました。オプティミスティック同時実行数ペシミスティック同時実行数をサポートし始めたのは、2005 バージョンになってからです。行レベルのロックは次のとおりです。 SQL の場合 サーバーの設計上、ロックは希少なリソースです。ロックの数が増えると、オーバーヘッドも大きくなります。ロック数の急激な増加による崖のようなパフォーマンスの低下を避けるためです。ロックでは、ロック アップグレードメカニズムと呼ばれるメソッドがサポートされており、行ロックがページ ロックにアップグレードされると、同時実行パフォーマンスは元の点に戻ります。

実際、同じデータベース内であっても、実行エンジンが異なれば、ロック関数の解釈も異なります。 MyISAM の場合、テーブル ロックのみがサポートされており、同時読み取りは許容されますが、同時変更は不十分です。 Innodb は Oracle に非常に似ており、非ロックの一貫した読み取り行ロック のサポートを提供します。SQL Server との明らかな違いは、ロックの総数が増加するにつれて、Innodb が必要とするのは支払う 少額の代償を支払うこと。

行ロックの構造

Innodb は行ロックをサポートしており、ロックの記述に特に大きなオーバーヘッドはありません。したがって、多数のロックがパフォーマンス低下を引き起こした後の救済措置として、ロック アップグレード メカニズムは必要ありません。

lock0priv.h ファイルからの抜粋では、Innodb は行ロックを次のように定義しています:

/** Record lock for a page */
struct lock_rec_t {
    /* space id */
    ulint  space;	
    
    /* page number */
    ulint  page_no;
    
    /**
     * number of bits in the lock bitmap; 
     * NOTE: the lock bitmap is placed immediately after the lock struct 
     */
    ulint  n_bits;			
};
ログイン後にコピー

同時実行制御を行に合わせて調整できることを理解するのは難しくありません。レベル、ロックはページに基づいています。 きめ細かい組織管理。 Innodb の設計では、スペース ID とページ番号の 2 つの必要な条件によって唯一のデータ ページを決定できます。n_bits は、ページの行ロック情報を記述するために必要なビット数を示します。

同じデータ ページ内で、各レコードは一意の連続増加番号 Heap_no に割り当てられます。レコードの行がロックされているかどうかを知りたい場合は、その位置の番号がロックされているかどうかを判断するだけで済みます。位置チャート Heap_no は 1 つです。ロック ビットマップはデータ ページ内のレコード数に基づいてメモリ領域を割り当てるため、明示的に定義されておらず、ページ レコードは増加し続ける可能性があるため、LOCK_PAGE_BITMAP_MARGIN サイズの領域が予約されます。

/** 
 * Safety margin when creating a new record lock: this many extra records
 * can be inserted to the page without need to create a lock with 
 * a bigger bitmap
 */
#define LOCK_PAGE_BITMAP_MARGIN	 64
ログイン後にコピー

スペース ID = 20、ページ番号 = 100 のデータ ページには現在 160 レコードがあり、heap_no が 2、3、および 4 のレコードがロックされていると仮定します。その後、対応する lock_rec_t 構造体とデータがロックされます。ページは次のようになります。 説明:

Mysqlロックの内部実装メカニズムについて語る記事

注:

  • メモリ内のロック ビットマップは線形に分散される必要があります。図に示した次元構造は説明を容易にするためです。
  • bitmap 構造体と lock_rec_t 構造体は連続したメモリであり、図中の参照関係も描画に必要です。

ページに対応するビットマップの 2 番目、3 番目、および 4 番目の位置が表示されます。すべて 1 に設定すると、データ ページ行ロックによって消費されるメモリが知覚の観点から非常に制限されていることを示します。 ?計算できます:
160 / 8 8 1 = 29バイト。

  • 160 レコードは 160 ビットに対応します
  • 8 は 64 ビットを予約する必要があるためです
  • 1 はソース コードで 1 バイトが予約されているためです

ここに 1 が追加されていますが、これはおそらく、整数の除算による結果の値が小さくなる問題を回避するためです。 161 レコードがある場合、それが 1 でない場合、計算された 20 バイトでは、すべてのレコードのロック情報を記述するのに十分ではありません (予約ビットを使用しない場合)。

lock0priv.h ファイルからの抜粋:

/* lock_rec_create函数代码片段 */
n_bits = page_dir_get_n_heap(page) + LOCK_PAGE_BITMAP_MARGIN;
n_bytes = 1 + n_bits / 8;

/* 注意这里是分配的连续内存 */
lock = static_cast<lock_t>(
    mem_heap_alloc(trx->lock.lock_heap, sizeof(lock_t) + n_bytes)
);


/**
 * Gets the number of records in the heap.
 * @return number of user records 
 */
UNIV_INLINE ulint page_dir_get_n_heap(const page_t* page)	
{
    return(page_header_get_field(page, PAGE_N_HEAP) & 0x7fff);
}</lock_t>
ログイン後にコピー

テーブル ロックの構造

Innodb はテーブル ロックもサポートしており、テーブル ロックは 2 つのカテゴリに分類できます。 Lock、自動インクリメント ロックのデータ構造は次のように定義されます。

lock0priv.hFile

struct lock_table_t {
    /* database table in dictionary cache */
    dict_table_t*  table;
    
    /* list of locks on the same table */
    UT_LIST_NODE_T(lock_t)  locks;
};
ログイン後にコピー

から抜粋 ut0lst.h# から抜粋##File

struct ut_list_node {
    /* pointer to the previous node, NULL if start of list */
    TYPE*  prev;
    
    /* pointer to next node, NULL if end of list */
    TYPE*  next;
};


#define UT_LIST_NODE_T(TYPE)  ut_list_node<type></type>
ログイン後にコピー
トランザクション内のロックの説明

上記の lock_rec_t 構造体と lock_table_t 構造体は別個の定義です。ロックはトランザクション内で生成されるため、各トランザクションに対応する行ロックとテーブル ロックは、対応するロック構造があり、その定義は次のとおりです:

lock0priv.h ファイルからの抜粋

/** Lock struct; protected by lock_sys->mutex */
struct lock_t {
    /* transaction owning the lock */
    trx_t*  trx;
    
    /* list of the locks of the transaction */
    UT_LIST_NODE_T(lock_t)  trx_locks;	
    
    /** 
     * lock type, mode, LOCK_GAP or LOCK_REC_NOT_GAP,
     * LOCK_INSERT_INTENTION, wait flag, ORed 
     */
    ulint  type_mode;
    
    /* hash chain node for a record lock */
    hash_node_t  hash;	
    
    /*!<p>      lock_t是根据每个事务每个页(或表)来定义的,但是一个事务往往涉及到多个页,因此需要链表<strong>trx_locks</strong>串联起一个事务相关的所有锁信息。除了需要根据事务查询到所有锁信息,实际场景还要求系统必须能够快速高效的检测出某个行记录是否已经上锁。因此必须有一个全局变量支持对行记录进行锁信息的查询。Innodb选择了哈希表,其定义如下:</p><p>      摘自<strong>lock0lock.h</strong>文件</p><pre class="brush:php;toolbar:false">/** The lock system struct */
struct lock_sys_t {
    /* Mutex protecting the locks */
    ib_mutex_t  mutex;		
    
    /* 就是这里: hash table of the record locks */
    hash_table_t*  rec_hash;	
    
    /* Mutex protecting the next two fields */
    ib_mutex_t  wait_mutex;
    
    /** 
     * Array  of user threads suspended while waiting forlocks within InnoDB,
     * protected by the lock_sys->wait_mutex 
     */
    srv_slot_t*  waiting_threads;
    
    /*
     * highest slot ever used in the waiting_threads array,
     * protected by lock_sys->wait_mutex 
     */
    srv_slot_t*  last_slot;
    
    /** 
     * TRUE if rollback of all recovered transactions is complete. 
     * Protected by lock_sys->mutex 
     */
    ibool  rollback_complete;
		
    /* Max wait time */
    ulint  n_lock_max_wait_time;

    /**
     * Set to the event that is created in the lock wait monitor thread.
     * A value of 0 means the thread is not active
     */
    os_event_t	timeout_event;		

    /* True if the timeout thread is running */
    bool  timeout_thread_active;
};
ログイン後にコピー

      函数lock_sys_create在database start之际负责初始化lock_sys_t结构。rec_hash的hash slot数量由srv_lock_table_size变量决定。rec_hash哈希表的key值通过页的space id,page number计算得出。

      摘自lock0lock.icut0rnd.ic 文件

/**
 * Calculates the fold value of a page file address: used in inserting or
 * searching for a lock in the hash table.
 *
 * @return folded value 
 */
UNIV_INLINE ulint lock_rec_fold(ulint space, ulint page_no)
{
    return(ut_fold_ulint_pair(space, page_no));
}


/**
 * Folds a pair of ulints.
 *
 * @return folded value 
 */
UNIV_INLINE ulint ut_fold_ulint_pair(ulint n1, ulint n2)
{
    return (
        (
            (((n1 ^ n2 ^ UT_HASH_RANDOM_MASK2) <p>      这将意味着无法提供一个手段使得我们可以直接得知某一行是否上锁。而是应该先通过其所在的页得到space id、page number通过<strong>lock_rec_fold</strong>函数得出key值而后经过hash查询得到lock_rec_t,而后根据heap_no扫描bit map,最终确定锁信息。<strong>lock_rec_get_first</strong>函数实现了上述逻辑:</p><p>      这里返回的其实是<strong>lock_t</strong>对象,摘自<strong>lock0lock.cc</strong>文件</p><pre class="brush:php;toolbar:false">/**
 * Gets the first explicit lock request on a record.
 *
 * @param block   : block containing the record 
 * @param heap_no : heap number of the record 
 *
 * @return first lock, NULL if none exists 
 */
UNIV_INLINE lock_t* lock_rec_get_first(const buf_block_t* block, ulint heap_no)
{
    lock_t*  lock;

    ut_ad(lock_mutex_own());

    for (lock = lock_rec_get_first_on_page(block); lock;
         lock = lock_rec_get_next_on_page(lock)
    ) {
        if (lock_rec_get_nth_bit(lock, heap_no)) {
            break;
        }
    }

    return(lock);
}
ログイン後にコピー

      锁维护以页的粒度,不是一个最高效直接的方式,明显的时间换空间,这种设计使得锁的开销很小。某一事务对任一行上锁的开销都是一样的,锁数量的上升也不会带来额外的内存消耗。

      每个事务都对应一个trx_t的内存对象,其中保存着该事务锁信息链表和正在等待的锁信息。因此存在如下两种途径对锁进行查询:

  • 根据事务: 通过trx_t对象的trx_locks链表,再通过lock_t对象中的trx_locks遍历可得某事务持有、等待的所有锁信息。
  • 根据记录: 根据记录所在的页,通过space id、page number在lock_sys_t结构中定位到lock_t对象,扫描bitmap找到heap_no对应的bit位。

      上述各种数据结构,对其整理关系如下图所示:

Mysqlロックの内部実装メカニズムについて語る記事

注:

  • lock_sys_t中的slot颜色与lock_t颜色相同则表明lock_sys_t slot持有lock_t
    指针信息,实在是没法连线,不然图很混乱

【相关推荐:mysql视频教程

以上がMysqlロックの内部実装メカニズムについて語る記事の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

関連ラベル:
ソース:juejin.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
最新の問題
人気のチュートリアル
詳細>
関連するチュートリアル
人気のおすすめ
最新のコース
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート