Bien que les bases de données relationnelles modernes soient de plus en plus similaires, elles peuvent avoir des mécanismes complètement différents derrière leur mise en œuvre. En termes d'utilisation réelle, en raison de l'existence de spécifications de syntaxe SQL, il n'est pas difficile pour nous de nous familiariser avec plusieurs bases de données relationnelles, mais il peut y avoir autant de méthodes d'implémentation de verrous qu'il y a de bases de données.
Microsoft Sql Server ne fournissait les verrous de page qu'avant 2005. Ce n'est que dans la version 2005 qu'il a commencé à prendre en charge la concurrence optimiste et la concurrence pessimiste sont autorisées en mode optimiste. ressource rare. Plus il y a de verrous, plus il y a de verrous, plus la surcharge est importante. Afin d'éviter une chute des performances due à l'augmentation rapide du nombre de verrous, il prend en charge un mécanisme appelé mise à niveau des verrous. Une fois le verrouillage de ligne mis à niveau vers un verrouillage de page, les performances de concurrence reviendront à leur point d'origine.
En fait, il existe encore de nombreux différends sur l'interprétation des fonctions de verrouillage par différents moteurs d'exécution dans une même base de données. MyISAM ne prend en charge que le verrouillage au niveau des tables, ce qui convient à la lecture simultanée, mais présente certaines limitations en matière de modification simultanée. Innodb est très similaire à Oracle, offrant une prise en charge de la lecture cohérente et du verrouillage de ligne sans verrouillage. La différence évidente avec Sql Server est qu'à mesure que le nombre total de verrous augmente, Innodb n'a qu'à payer un petit prix.
Innodb prend en charge les verrous de ligne, et il n'y a pas de surcharge particulièrement importante dans la description des verrous. Par conséquent, il n'est pas nécessaire d'avoir recours à un mécanisme de mise à niveau des verrous comme mesure de secours après qu'un grand nombre de verrous provoque une dégradation des performances.
Extraite du fichier lock0priv.h, la définition des verrous de ligne par Innodb est la suivante :
/** 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; };
Bien que le contrôle de concurrence puisse être affiné au niveau des lignes, la gestion des verrous est organisée en unités de pages. Dans la conception d'Innodb, la seule page de données peut être déterminée via les deux conditions nécessaires : l'identifiant d'espace et le numéro de page n_bits indiquent le nombre de bits nécessaires pour décrire les informations de verrouillage de ligne de la page.
Chaque enregistrement dans la même page de données se voit attribuer un numéro de séquence unique croissant : heap_no Si vous voulez savoir si une certaine ligne d'enregistrements est verrouillée, il vous suffit de déterminer si le numéro dans la position heap_no du bitmap est. un. Étant donné que le bitmap de verrouillage alloue de l'espace mémoire en fonction du nombre d'enregistrements dans la page de données, il n'est pas explicitement défini et les enregistrements de page peuvent continuer à augmenter, donc un espace de taille LOCK_PAGE_BITMAP_MARGIN est réservé.
/** * 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
Supposons que la page de données avec l'identifiant d'espace = 20, le numéro de page = 100 contient actuellement 160 enregistrements et que les enregistrements avec heap_no de 2, 3 et 4 ont été verrouillés, alors la structure lock_rec_t et la page de données correspondantes doivent être décrites comme ceci :
Remarque :
Le bitmap de verrouillage dans la mémoire doit être distribué linéairement. La structure bidimensionnelle montrée dans l'image est pour faciliter la description
La structure bitmap et lock_rec_t sont une structure continue. mémoire, et la relation de référence dans l'image est aussi un dessin Besoin
Vous pouvez voir que les deuxième, troisième et quatrième positions du bitmap correspondant à la page sont toutes mises à 1. La mémoire consommée en décrivant une page de données Le verrouillage des rangées est assez limité du point de vue de la perception. Quelle place occupe-t-il spécifiquement ? On peut calculer :
160 / 8 + 8 + 1 = 29octets.
160 enregistrements correspondent à 160 bits
+8 car 64 bits doivent être réservés
+1 car 1 octet est réservé dans le code source
pour éviter que la valeur du résultat ne soit trop petite Question, +1 supplémentaire ajouté ici. Cela peut éviter les erreurs causées par la division entière. S'il y a 161 enregistrements, sinon +1, les 20 octets calculés ne suffisent pas pour décrire les informations de verrouillage de tous les enregistrements (sans utiliser de bits réservés).
Extrait du fichier 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); }
Innodb prend également en charge les verrous de table. Les verrous de table peuvent être divisés en deux catégories : les verrous d'intention et les verrous à incrémentation automatique. La structure des données est définie comme suit :
Extrait du fichier lock0priv.h
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; };
Extrait du fichier ut0lst.h
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>
Les structures lock_rec_t et lock_table_t ci-dessus ne sont que des définitions distinctes. Les verrous sont générés dans les transactions, donc chaque transaction aura. un verrou de ligne et un verrou de table correspondants. La structure du verrou est définie comme suit :
Extrait du fichier 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; /*!< index for a record lock */ dict_index_t* index; /* lock details */ union { /* table lock */ lock_table_t tab_lock; /* record lock */ lock_rec_t rec_lock; } un_member; };
lock_t est défini en fonction de chaque page (ou table) de chaque transaction, mais une transaction implique souvent plusieurs pages. , donc une liste chaînée trx_locks est nécessaire. Concatène toutes les informations de verrouillage liées à une transaction. En plus d'interroger toutes les informations de verrouillage en fonction des transactions, le scénario réel nécessite également que le système soit capable de détecter rapidement et efficacement si un enregistrement de ligne a été verrouillé. Par conséquent, il doit exister une variable globale pour prendre en charge l’interrogation des informations de verrouillage pour les enregistrements de ligne. Innodb a choisi la table de hachage, qui est définie comme suit :
Extrait du fichier lock0lock.h
/** 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; };
La fonction lock_sys_create est chargée d'initialiser la structure lock_sys_t au démarrage de la base de données. La variable srv_lock_table_size détermine la taille du nombre d'emplacements de hachage dans rec_hash. La valeur clé de la table de hachage rec_hash est calculée en utilisant l'identifiant d'espace et le numéro de page de la page.
Extrait des fichiers lock0lock.ic, ut0rnd.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) << 8) + n1) ^ UT_HASH_RANDOM_MASK ) + n2 ); }
Cela signifiera qu'il n'y a aucun moyen de nous permettre de savoir directement si une certaine ligne est verrouillée. Au lieu de cela, vous devez d'abord obtenir l'identifiant de l'espace et le numéro de page via la page où il se trouve, obtenir la valeur de la clé via la fonction lock_rec_fold, puis obtenir lock_rec_t via une requête de hachage, puis analyser le bitmap selon heap_no pour enfin déterminer le verrouiller les informations. La fonction lock_rec_get_first implémente la logique ci-dessus :
这里返回的其实是lock_t对象,摘自lock0lock.cc文件
/** * 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位。
上述各种数据结构,对其整理关系如下图所示:
注:
lock_sys_t中的slot颜色与lock_t颜色相同则表明lock_sys_t slot持有lock_t 指针信息,实在是没法连线,不然图很混乱
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!