Basic Data Structure

  1. struct lock_sys_t {
  2. char pad1[INNOBASE_CACHE_LINE_SIZE];
  3. /*!< padding to prevent other
  4. memory update hotspots from
  5. residing on the same memory
  6. cache line */
  7. LockMutex mutex; /*!< Mutex protecting the
  8. locks */
  9. hash_table_t *rec_hash; /*!< hash table of the record
  10. locks */
  11. hash_table_t *prdt_hash; /*!< hash table of the predicate
  12. lock */
  13. hash_table_t *prdt_page_hash; /*!< hash table of the page
  14. lock */
  15. #ifdef UNIV_DEBUG
  16. /** Lock timestamp counter */
  17. uint64_t m_seq;
  18. #endif /* UNIV_DEBUG */
  19. };

Initialization at server boot up time (i.e., srv_start())

  1. void lock_sys_create(
  2. ulint n_cells) /*!< in: number of slots in lock hash table */
  3. {
  4. mutex_create(LATCH_ID_LOCK_SYS, &lock_sys->mutex);
  5. lock_sys->rec_hash = hash_create(n_cells);
  6. lock_sys->prdt_hash = hash_create(n_cells);
  7. lock_sys->prdt_page_hash = hash_create(n_cells);
  8. }
  9. lock_sys_create(srv_lock_table_size);
  10. /* normalize lock_sys */
  11. srv_lock_table_size = 5 * (srv_buf_pool_size / UNIV_PAGE_SIZE);

Initially, we assume there might be 5 row locks per page. We might need to change the size of lock hash table which is also protected by the mutex.

  1. /** Resize the lock hash tables.
  2. @param[in] n_cells number of slots in lock hash table */
  3. void lock_sys_resize(ulint n_cells) {
  4. hash_table_t *old_hash;
  5. lock_mutex_enter();
  6. old_hash = lock_sys->rec_hash;
  7. lock_sys->rec_hash = hash_create(n_cells);
  8. HASH_MIGRATE(old_hash, lock_sys->rec_hash, lock_t, hash, lock_rec_lock_fold);
  9. hash_table_free(old_hash);
  10. lock_mutex_exit();
  11. }

Mutex is used to sync all operations need to acquire rec/prdt locks.

  1. static void row_ins_foreign_trx_print(trx_t *trx) /*!< in: transaction */
  2. {
  3. lock_mutex_enter();
  4. n_rec_locks = lock_number_of_rows_locked(&trx->lock);
  5. n_trx_locks = UT_LIST_GET_LEN(trx->lock.trx_locks);
  6. heap_size = mem_heap_get_size(trx->lock.lock_heap);
  7. lock_mutex_exit();
  8. }
  9. //Create a row lock
  10. lock_t *RecLock::create(trx_t *trx, bool add_to_hash, const lock_prdt_t *prdt) {
  11. //create lock record
  12. lock_t *lock = lock_alloc(trx, m_index, m_mode, m_rec_id, m_size);
  13. //hookup to the lock hash table
  14. lock_add(lock, add_to_hash);
  15. }
  16. /**
  17. Record lock ID */
  18. struct RecID {
  19. /**
  20. Tablespace ID */
  21. space_id_t m_space_id;
  22. /**
  23. Page number within the space ID */
  24. page_no_t m_page_no;
  25. /**
  26. Heap number within the page */?????
  27. uint32_t m_heap_no;
  28. /**
  29. Hashed key value */
  30. ulint m_fold;
  31. };

DeadLock Detection

Not all operations will trigger the deadlock detection. It will only be triggered by If we cannot get the required lock immediately. For example:

  1. dberr_t lock_rec_insert_check_and_lock {
  2. const lock_t *wait_for =
  3. lock_rec_other_has_conflicting(type_mode, block, heap_no, trx);
  4. if (wait_for != NULL) {
  5. RecLock rec_lock(thr, index, block, heap_no, type_mode);
  6. // This will trigger the deadlock detection
  7. err = rec_lock.add_to_waitq(wait_for);
  8. }
  9. }

Basic idea:

find a circle in the wait graph (i.e. directed graph).

Basic data structure:

  1. class DeadlockChecker {
  2. @param trx the start transaction (start node)
  3. @param wait_lock lock that a transaction wants
  4. @param mark_start visited node counter */
  5. DeadlockChecker(const trx_t *trx, const lock_t *wait_lock,
  6. uint64_t mark_start)
  7. }

Basic DFS search structure:

  1. /** DFS state information, used during deadlock checking. */
  2. struct state_t {
  3. const lock_t *m_lock; /*!< Current lock */
  4. const lock_t *m_wait_lock; /*!< Waiting for lock */
  5. ulint m_heap_no; /*!< heap number if rec lock */
  6. };

Basic Entry Point

  1. /** Check and resolve any deadlocks
  2. @param[in, out] lock The lock being acquired
  3. @return DB_LOCK_WAIT, DB_DEADLOCK, or
  4. DB_SUCCESS_LOCKED_REC; DB_SUCCESS_LOCKED_REC means that
  5. there was a deadlock, but another transaction was chosen
  6. as a victim, and we got the lock immediately: no need to
  7. wait then */
  8. dberr_t RecLock::deadlock_check(lock_t *lock) {
  9. const trx_t *victim_trx = DeadlockChecker::check_and_resolve(lock, m_trx);
  10. {
  11. /* Try and resolve as many deadlocks as possible. */
  12. do {
  13. DeadlockChecker checker(trx, lock, s_lock_mark_counter);
  14. victim_trx = checker.search();
  15. } while (victim_trx != NULL && victim_trx != trx);
  16. }

Background reading

INNODB LOCK

https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html

How to create a deadlock

https://stackoverflow.com/questions/31552766/how-to-cause-deadlock-on-mysql

How to detect circle in directed graph

https://www.geeksforgeeks.org/detect-cycle-in-a-graph/ https://www.youtube.com/watch?v=joqmqvHC_Bo