Heim  >  Artikel  >  Datenbank  >  Ein Artikel über den internen Implementierungsmechanismus der MySQL-Sperre

Ein Artikel über den internen Implementierungsmechanismus der MySQL-Sperre

青灯夜游
青灯夜游nach vorne
2022-09-13 19:45:402443Durchsuche

In diesem Artikel geht es um den internen Implementierungsmechanismus der MySQL-Sperre. Ich hoffe, dass er für alle hilfreich ist.

Ein Artikel über den internen Implementierungsmechanismus der MySQL-Sperre

Hinweis: Die aufgeführten Codes stammen alle aus Mysql-5.6

Obwohl relationale Datenbanken immer ähnlicher werden, können die dahinter stehenden Implementierungsmechanismen sehr unterschiedlich sein. Im Hinblick auf die tatsächliche Verwendung ist es für uns aufgrund der vorhandenen SQL-Syntaxspezifikationen nicht schwierig, mit mehreren relationalen Datenbanken vertraut zu sein, aber es kann so viele Sperrimplementierungsmethoden wie Datenbanken geben.

  Microsoft Sql Server bot nur Seitensperren vor 2005. Erst in der Version 2005 wurden im optimistischen Modus Sperren auf Zeilenebene unterstützt Sperren sind eine knappe Ressource. Je größer die Anzahl, desto größer der Overhead. Um einen klippenartigen Leistungsabfall aufgrund der schnellen Zunahme der Sperren zu vermeiden, wird ein Mechanismus namens „Sperren-Upgrade“ unterstützt. Sobald die Zeilensperre auf eine Seitensperre aktualisiert wird, kehrt die Parallelitätsleistung zum Ursprung zurück. Tatsächlich haben verschiedene Ausführungs-Engines selbst in derselben Datenbank immer noch unterschiedliche Interpretationen der Sperrfunktion. Für MyISAM sind nur Tabellensperren akzeptabel, gleichzeitige Änderungen reichen jedoch nicht aus. Innodb ist Oracle sehr ähnlich und bietet Unterstützung für „nicht sperrende konsistente Lesevorgänge“ und „Zeilensperren“. Der offensichtliche Unterschied zu SQL Server besteht darin, dass Innodb nur einen geringen Preis zahlen muss. Zeilensperrenstruktur

Innodb unterstützt Zeilensperren, und die Beschreibung der Sperren erfordert keinen besonders großen Aufwand. Daher besteht kein Bedarf für einen Sperraktualisierungsmechanismus als Rettungsmaßnahme, nachdem eine große Anzahl von Sperren zu Leistungseinbußen führt. ​ Auszug aus der Datei lock0priv.h, Innodbs Definition von Zeilensperren lautet wie folgt:

/** 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;			
};

​ Es ist nicht schwer zu erkennen, dass die Parallelitätskontrolle zwar auf Zeilenebene verfeinert werden kann, Sperren jedoch auf dieser organisiert und verwaltet werden die Seitengranularität. Im Design von Innodb kann die einzige Datenseite durch die beiden erforderlichen Bedingungen Space-ID und Seitennummer bestimmt werden. n_bits gibt an, wie viele Bits erforderlich sind, um die Zeilensperrinformationen der Seite zu beschreiben.  Jedem Datensatz auf derselben Datenseite wird eine eindeutige fortlaufend steigende Sequenznummer zugewiesen: heap_no Wenn Sie wissen möchten, ob eine bestimmte Zeile von Datensätzen gesperrt ist, müssen Sie nur feststellen, ob sich die Nummer an der Position heap_no der Bitmap befindet eins. Da die Sperrbitmap Speicherplatz basierend auf der Anzahl der Datensätze auf der Datenseite zuweist, ist sie nicht explizit definiert und die Seitendatensätze können weiter zunehmen, sodass ein Speicherplatz der Größe LOCK_PAGE_BITMAP_MARGIN reserviert wird.

/** 
 * 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

Angenommen, die Datenseite mit der Leerzeichen-ID = 20 und der Seitennummer = 100 enthält derzeit 160 Datensätze und die Datensätze mit der Heap_Nr. 2, 3 und 4 wurden gesperrt. Dann sollten die entsprechende lock_rec_t-Struktur und die entsprechende Datenseite beschrieben werden so:

Hinweis:

Die im Bild gezeigte zweidimensionale Struktur sollte der Einfachheit halber linear verteilt sein.

Die Bitmap und die lock_rec_t-Struktur sind ein kontinuierlicher Speicher . Die Referenzbeziehung im Bild ist auch zum Zeichnen erforderlich

Ein Artikel über den internen Implementierungsmechanismus der MySQL-Sperre

Sie können sehen, dass die zweite, dritte und vierte Position der dieser Seite entsprechenden Bitmap alle auf 1 gesetzt sind. Der von der Zeilensperre verbrauchte Speicher, der a beschreibt Die Datenseite ist aus Sicht der Wahrnehmung recht begrenzt. Wie viel nimmt sie konkret ein? Wir können berechnen:

160 / 8 + 8 + 1 = 29 Byte.
  • 160 Datensätze entsprechen 160 Bit
+8 liegt daran, dass 64 Bit reserviert werden müssen

+1 liegt daran, dass 1 Byte im Quellcode reserviert ist
Hier gibt es eine zusätzliche +1, wahrscheinlich um eine ganzzahlige Division zu vermeiden Der resultierende Zahlenwert ist zu klein. Wenn es 161 Datensätze gibt und es nicht +1 ist, reichen die berechneten 20 Bytes nicht aus, um die Sperrinformationen aller Datensätze zu beschreiben (ohne Verwendung reservierter Bits).

    Auszug aus der Datei
  • 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>
  • Tabellensperrenstruktur
  • Tabellensperren können in zwei Kategorien unterteilt werden: Absichtssperren. Die Datenstruktur von automatischen Inkrementierungssperren ist wie folgt definiert:

Auszug aus der Datei „lock0priv .h“ Die Transaktion entspricht der Zeilensperre. Die Tabellensperre hat eine entsprechende Sperrstruktur, die wie folgt definiert ist:                   

/** 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位。

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

Ein Artikel über den internen Implementierungsmechanismus der MySQL-Sperre

注:

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

【相关推荐:mysql视频教程

Das obige ist der detaillierte Inhalt vonEin Artikel über den internen Implementierungsmechanismus der MySQL-Sperre. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:juejin.cn. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen