From 5f463857645d8016e23afdb02955cdc305bebcfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marko=20M=C3=A4kel=C3=A4?= Date: Thu, 4 Feb 2021 16:38:07 +0200 Subject: [PATCH] MDEV-24731 Excessive mutex contention in DeadlockChecker::check_and_resolve() The DeadlockChecker expects to be able to freeze the waits-for graph. Hence, it is best executed somewhere where we are not holding any additional mutexes. lock_wait(): Defer the deadlock check to this function, instead of executing it in lock_rec_enqueue_waiting(), lock_table_enqueue_waiting(). DeadlockChecker::trx_rollback(): Merge with the only caller, check_and_resolve(). LockMutexGuard: RAII accessor for lock_sys.mutex. lock_sys.deadlocks: Replaces lock_deadlock_found. trx_t: Clean up some comments. --- mysql-test/main/xa.result | 3 +- mysql-test/main/xa.test | 3 +- .../r/innodb_skip_innodb_is_tables.result | 2 +- storage/innobase/CMakeLists.txt | 3 +- storage/innobase/handler/ha_innodb.cc | 2 +- storage/innobase/include/lock0lock.h | 19 +- storage/innobase/include/lock0priv.h | 3 - storage/innobase/include/srv0srv.h | 3 - storage/innobase/include/trx0trx.h | 128 ++-- storage/innobase/lock/lock0lock.cc | 607 +++++++++++------- storage/innobase/lock/lock0prdt.cc | 9 +- storage/innobase/lock/lock0wait.cc | 234 ------- storage/innobase/srv/srv0mon.cc | 6 +- .../r/innodb_i_s_tables_disabled.result | 2 +- 14 files changed, 453 insertions(+), 571 deletions(-) delete mode 100644 storage/innobase/lock/lock0wait.cc diff --git a/mysql-test/main/xa.result b/mysql-test/main/xa.result index 46c0c8551a1..b2c23995442 100644 --- a/mysql-test/main/xa.result +++ b/mysql-test/main/xa.result @@ -305,7 +305,8 @@ INSERT INTO t2 VALUES (1); COMMIT; BEGIN; INSERT INTO t2 VALUES (2); -UPDATE t2 SET a=a+1; +UPDATE t2 SET a=a+2; +UPDATE t2 SET a=a-1; connect con2,localhost,root; XA START 'xid1'; INSERT INTO t1 VALUES (1); diff --git a/mysql-test/main/xa.test b/mysql-test/main/xa.test index f5a5cc96197..690a29ff163 100644 --- a/mysql-test/main/xa.test +++ b/mysql-test/main/xa.test @@ -401,7 +401,8 @@ CREATE TABLE t2 (a INT) ENGINE=InnoDB; INSERT INTO t2 VALUES (1); COMMIT; BEGIN; INSERT INTO t2 VALUES (2); -UPDATE t2 SET a=a+1; +UPDATE t2 SET a=a+2; +UPDATE t2 SET a=a-1; --connect (con2,localhost,root) XA START 'xid1'; diff --git a/mysql-test/suite/innodb/r/innodb_skip_innodb_is_tables.result b/mysql-test/suite/innodb/r/innodb_skip_innodb_is_tables.result index f91d3421796..880a95a471b 100644 --- a/mysql-test/suite/innodb/r/innodb_skip_innodb_is_tables.result +++ b/mysql-test/suite/innodb/r/innodb_skip_innodb_is_tables.result @@ -39,7 +39,7 @@ NAME SUBSYSTEM COUNT MAX_COUNT MIN_COUNT AVG_COUNT COUNT_RESET MAX_COUNT_RESET M metadata_table_handles_opened metadata 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of table handles opened metadata_table_handles_closed metadata 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of table handles closed metadata_table_reference_count metadata 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Table reference counter -lock_deadlocks lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of deadlocks +lock_deadlocks lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Number of deadlocks lock_timeouts lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of lock timeouts lock_rec_lock_waits lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times enqueued into record lock wait queue lock_table_lock_waits lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times enqueued into table lock wait queue diff --git a/storage/innobase/CMakeLists.txt b/storage/innobase/CMakeLists.txt index bb13b2f9b50..88d3b886da4 100644 --- a/storage/innobase/CMakeLists.txt +++ b/storage/innobase/CMakeLists.txt @@ -1,6 +1,6 @@ # Copyright (c) 2006, 2017, Oracle and/or its affiliates. All rights reserved. -# Copyright (c) 2014, 2020, MariaDB Corporation. +# Copyright (c) 2014, 2021, MariaDB Corporation. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -278,7 +278,6 @@ SET(INNOBASE_SOURCES lock/lock0iter.cc lock/lock0prdt.cc lock/lock0lock.cc - lock/lock0wait.cc log/log0log.cc log/log0recv.cc log/log0crypt.cc diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 5b93a7cde55..1ab1c5af28a 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -961,7 +961,7 @@ static SHOW_VAR innodb_status_variables[]= { {"data_written", &export_vars.innodb_data_written, SHOW_SIZE_T}, {"dblwr_pages_written", &export_vars.innodb_dblwr_pages_written,SHOW_SIZE_T}, {"dblwr_writes", &export_vars.innodb_dblwr_writes, SHOW_SIZE_T}, - {"deadlocks", &srv_stats.lock_deadlock_count, SHOW_SIZE_T}, + {"deadlocks", &lock_sys.deadlocks, SHOW_SIZE_T}, {"history_list_length", &export_vars.innodb_history_list_length,SHOW_SIZE_T}, {"ibuf_discarded_delete_marks", &ibuf.n_discarded_ops[IBUF_OP_DELETE_MARK], SHOW_SIZE_T}, diff --git a/storage/innobase/include/lock0lock.h b/storage/innobase/include/lock0lock.h index d66d279bbeb..643f7415767 100644 --- a/storage/innobase/include/lock0lock.h +++ b/storage/innobase/include/lock0lock.h @@ -642,6 +642,8 @@ public: hash_table_t prdt_hash; /** page locks for SPATIAL INDEX */ hash_table_t prdt_page_hash; + /** number of deadlocks detected; protected by mutex */ + ulint deadlocks; /** mutex covering lock waits; @see trx_lock_t::wait_lock */ MY_ALIGNED(CACHE_LINE_SIZE) mysql_mutex_t wait_mutex; @@ -766,6 +768,16 @@ public: { return get_first(prdt_page_hash, id); } }; +/** The lock system */ +extern lock_sys_t lock_sys; + +/** lock_sys.mutex guard */ +struct LockMutexGuard +{ + LockMutexGuard() { lock_sys.mutex_lock(); } + ~LockMutexGuard() { lock_sys.mutex_unlock(); } +}; + /*********************************************************************//** Creates a new record lock and inserts it to the lock queue. Does NOT check for deadlocks or lock compatibility! @@ -835,9 +847,7 @@ Check for deadlocks. @param[in,out] thr query thread @param[in] prdt minimum bounding box (spatial index) @retval DB_LOCK_WAIT if the waiting lock was enqueued -@retval DB_DEADLOCK if this transaction was chosen as the victim -@retval DB_SUCCESS_LOCKED_REC if the other transaction was chosen as a victim - (or it happened to commit) */ +@retval DB_DEADLOCK if this transaction was chosen as the victim */ dberr_t lock_rec_enqueue_waiting( #ifdef WITH_WSREP @@ -871,9 +881,6 @@ lock_rec_free_all_from_discard_page( /*================================*/ const buf_block_t* block); /*!< in: page to be discarded */ -/** The lock system */ -extern lock_sys_t lock_sys; - /** Cancel a waiting lock request and release possibly waiting transactions */ void lock_cancel_waiting_and_release(lock_t *lock); diff --git a/storage/innobase/include/lock0priv.h b/storage/innobase/include/lock0priv.h index 8b9689d70c4..cadfc8c1378 100644 --- a/storage/innobase/include/lock0priv.h +++ b/storage/innobase/include/lock0priv.h @@ -391,9 +391,6 @@ static const byte lock_strength_matrix[5][5] = { /* AI */ { FALSE, FALSE, FALSE, FALSE, TRUE} }; -/** Maximum depth of the DFS stack. */ -static const ulint MAX_STACK_SIZE = 4096; - #define PRDT_HEAPNO PAGE_HEAP_NO_INFIMUM /** Record locking request status */ enum lock_rec_req_status { diff --git a/storage/innobase/include/srv0srv.h b/storage/innobase/include/srv0srv.h index 504f26f7218..457e6dfc435 100644 --- a/storage/innobase/include/srv0srv.h +++ b/storage/innobase/include/srv0srv.h @@ -187,9 +187,6 @@ struct srv_stats_t /** Number of temporary tablespace blocks decrypted */ ulint_ctr_64_t n_temp_blocks_decrypted; - - /** Number of lock deadlocks */ - ulint_ctr_1_t lock_deadlock_count; }; /** We are prepared for a situation that we have this many threads waiting for diff --git a/storage/innobase/include/trx0trx.h b/storage/innobase/include/trx0trx.h index 300b791fc93..916c7fc257a 100644 --- a/storage/innobase/include/trx0trx.h +++ b/storage/innobase/include/trx0trx.h @@ -418,7 +418,8 @@ typedef std::vector > lock_list; /** The locks and state of an active transaction. Protected by lock_sys.mutex, trx->mutex or both. */ -struct trx_lock_t { +struct trx_lock_t +{ /** Lock request being waited for. Set to nonnull when holding lock_sys.mutex, lock_sys.wait_mutex and trx->mutex, by the thread that is executing the transaction. @@ -432,13 +433,10 @@ struct trx_lock_t { ib_uint64_t deadlock_mark; /*!< A mark field that is initialized to and checked against lock_mark_counter by lock_deadlock_recursive(). */ - bool was_chosen_as_deadlock_victim; - /*!< when the transaction decides to - wait for a lock, it sets this to false; - if another transaction chooses this - transaction as a victim in deadlock - resolution, it sets this to true. - Protected by trx->mutex. */ + /** When the transaction decides to wait for a lock, it clears this; + set if another transaction chooses this transaction as a victim in deadlock + resolution. Protected by lock_sys.mutex and lock_sys.wait_mutex. */ + bool was_chosen_as_deadlock_victim; que_thr_t* wait_thr; /*!< query thread belonging to this trx that is in waiting state. For threads suspended in a @@ -666,88 +664,94 @@ private: public: + /** Transaction identifier (0 if no locks were acquired). + Set by trx_sys_t::register_rw() or trx_resurrect() before + the transaction is added to trx_sys.rw_trx_hash. + Cleared in commit_in_memory() after commit_state(), + trx_sys_t::deregister_rw(), release_locks(). */ + trx_id_t id; + /** mutex protecting state and some of lock (some are protected by lock_sys.mutex) */ srw_mutex mutex; - trx_id_t id; /*!< transaction id */ + /** State of the trx from the point of view of concurrency control + and the valid state transitions. - /** State of the trx from the point of view of concurrency control - and the valid state transitions. + Possible states: - Possible states: + TRX_STATE_NOT_STARTED + TRX_STATE_ACTIVE + TRX_STATE_PREPARED + TRX_STATE_PREPARED_RECOVERED (special case of TRX_STATE_PREPARED) + TRX_STATE_COMMITTED_IN_MEMORY (alias below COMMITTED) - TRX_STATE_NOT_STARTED - TRX_STATE_ACTIVE - TRX_STATE_PREPARED - TRX_STATE_PREPARED_RECOVERED (special case of TRX_STATE_PREPARED) - TRX_STATE_COMMITTED_IN_MEMORY (alias below COMMITTED) + Valid state transitions are: - Valid state transitions are: + Regular transactions: + * NOT_STARTED -> ACTIVE -> COMMITTED -> NOT_STARTED - Regular transactions: - * NOT_STARTED -> ACTIVE -> COMMITTED -> NOT_STARTED + Auto-commit non-locking read-only: + * NOT_STARTED -> ACTIVE -> NOT_STARTED - Auto-commit non-locking read-only: - * NOT_STARTED -> ACTIVE -> NOT_STARTED + XA (2PC): + * NOT_STARTED -> ACTIVE -> PREPARED -> COMMITTED -> NOT_STARTED - XA (2PC): - * NOT_STARTED -> ACTIVE -> PREPARED -> COMMITTED -> NOT_STARTED + Recovered XA: + * NOT_STARTED -> PREPARED -> COMMITTED -> (freed) - Recovered XA: - * NOT_STARTED -> PREPARED -> COMMITTED -> (freed) + Recovered XA followed by XA ROLLBACK: + * NOT_STARTED -> PREPARED -> ACTIVE -> COMMITTED -> (freed) - Recovered XA followed by XA ROLLBACK: - * NOT_STARTED -> PREPARED -> ACTIVE -> COMMITTED -> (freed) + XA (2PC) (shutdown or disconnect before ROLLBACK or COMMIT): + * NOT_STARTED -> PREPARED -> (freed) - XA (2PC) (shutdown or disconnect before ROLLBACK or COMMIT): - * NOT_STARTED -> PREPARED -> (freed) + Disconnected XA PREPARE transaction can become recovered: + * ... -> ACTIVE -> PREPARED (connected) -> PREPARED (disconnected) - Disconnected XA can become recovered: - * ... -> ACTIVE -> PREPARED (connected) -> PREPARED (disconnected) - Disconnected means from mysql e.g due to the mysql client disconnection. - Latching and various transaction lists membership rules: + Latching and various transaction lists membership rules: - XA (2PC) transactions are always treated as non-autocommit. + XA (2PC) transactions are always treated as non-autocommit. - Transitions to ACTIVE or NOT_STARTED occur when transaction - is not in rw_trx_hash. + Transitions to ACTIVE or NOT_STARTED occur when transaction + is not in rw_trx_hash. - Autocommit non-locking read-only transactions move between states - without holding any mutex. They are not in rw_trx_hash. + Autocommit non-locking read-only transactions move between states + without holding any mutex. They are not in rw_trx_hash. - All transactions, unless they are determined to be ac-nl-ro, - explicitly tagged as read-only or read-write, will first be put - on the read-only transaction list. Only when a !read-only transaction - in the read-only list tries to acquire an X or IX lock on a table - do we remove it from the read-only list and put it on the read-write - list. During this switch we assign it a rollback segment. + All transactions, unless they are determined to be ac-nl-ro, + explicitly tagged as read-only or read-write, will first be put + on the read-only transaction list. Only when a !read-only transaction + in the read-only list tries to acquire an X or IX lock on a table + do we remove it from the read-only list and put it on the read-write + list. During this switch we assign it a rollback segment. - When a transaction is NOT_STARTED, it can be in trx_list. It cannot be - in rw_trx_hash. + When a transaction is NOT_STARTED, it can be in trx_list. It cannot be + in rw_trx_hash. - ACTIVE->PREPARED->COMMITTED is only possible when trx is in rw_trx_hash. - The transition ACTIVE->PREPARED is protected by trx->mutex. + ACTIVE->PREPARED->COMMITTED is only possible when trx is in rw_trx_hash. + The transition ACTIVE->PREPARED is protected by trx->mutex. - ACTIVE->COMMITTED is possible when the transaction is in - rw_trx_hash. + ACTIVE->COMMITTED is possible when the transaction is in + rw_trx_hash. - Transitions to COMMITTED are protected by trx_t::mutex. */ + Transitions to COMMITTED are protected by trx_t::mutex. */ Atomic_relaxed state; + + /** The locks of the transaction. Protected by lock_sys.mutex + (insertions also by trx_t::mutex). */ + trx_lock_t lock; + #ifdef WITH_WSREP - /** whether wsrep_on(mysql_thd) held at the start of transaction */ - bool wsrep; - bool is_wsrep() const { return UNIV_UNLIKELY(wsrep); } + /** whether wsrep_on(mysql_thd) held at the start of transaction */ + bool wsrep; + bool is_wsrep() const { return UNIV_UNLIKELY(wsrep); } #else /* WITH_WSREP */ - bool is_wsrep() const { return false; } + bool is_wsrep() const { return false; } #endif /* WITH_WSREP */ - ReadView read_view; /*!< consistent read view used in the - transaction, or NULL if not yet set */ - trx_lock_t lock; /*!< Information about the transaction - locks and state. Protected by - lock_sys.mutex (insertions also - by trx_t::mutex). */ + /** Consistent read view of the transaction */ + ReadView read_view; /* These fields are not protected by any mutex. */ diff --git a/storage/innobase/lock/lock0lock.cc b/storage/innobase/lock/lock0lock.cc index 73e7b7d3b71..dd14f2ab7ae 100644 --- a/storage/innobase/lock/lock0lock.cc +++ b/storage/innobase/lock/lock0lock.cc @@ -29,6 +29,7 @@ Created 5/7/1996 Heikki Tuuri #include "univ.i" #include +#include #include #include "lock0lock.h" @@ -53,9 +54,11 @@ Created 5/7/1996 Heikki Tuuri /** The value of innodb_deadlock_detect */ my_bool innobase_deadlock_detect; +#ifdef HAVE_REPLICATION extern "C" void thd_rpl_deadlock_check(MYSQL_THD thd, MYSQL_THD other_thd); extern "C" int thd_need_wait_reports(const MYSQL_THD thd); extern "C" int thd_need_ordering_with(const MYSQL_THD thd, const MYSQL_THD other_thd); +#endif /** Pretty-print a table lock. @param[in,out] file output stream @@ -73,16 +76,13 @@ class DeadlockChecker { public: /** Check if a joining lock request results in a deadlock. If a deadlock is found, we will resolve the deadlock by - choosing a victim transaction and rolling it back. + choosing a victim transaction to be rolled it back. We will attempt to resolve all deadlocks. - @param[in] lock the lock request - @param[in,out] trx transaction requesting the lock + @param[in,out] trx transaction requesting a lock - @return trx if it was chosen as victim - @retval NULL if another victim was chosen, - or there is no deadlock (any more) */ - static const trx_t* check_and_resolve(const lock_t* lock, trx_t* trx); + @return whether the transaction was chosen as victim */ + static bool check_and_resolve(trx_t *trx); private: /** Do a shallow copy. Default destructor OK. @@ -91,18 +91,17 @@ private: @param mark_start visited node counter @param report_waiters whether to call thd_rpl_deadlock_check() */ DeadlockChecker( - const trx_t* trx, + trx_t* trx, const lock_t* wait_lock, ib_uint64_t mark_start, bool report_waiters) : - m_cost(), +#ifdef HAVE_REPLICATION + m_report_waiters(report_waiters), +#endif m_start(trx), - m_too_deep(), m_wait_lock(wait_lock), - m_mark_start(mark_start), - m_n_elems(), - m_report_waiters(report_waiters) + m_mark_start(mark_start) { } @@ -186,18 +185,14 @@ private: @param lock lock causing deadlock */ void notify(const lock_t* lock) const; - /** Select the victim transaction that should be rolledback. - @return victim transaction */ - const trx_t* select_victim() const; - - /** Rollback transaction selected as the victim. */ - void trx_rollback(); + /** @return the victim transaction that should be rolled back */ + trx_t *select_victim() const; /** Looks iteratively for a deadlock. Note: the joining transaction may have been granted its lock by the deadlock checks. - - @return 0 if no deadlock else the victim transaction.*/ - const trx_t* search(); + @return the victim transaction + @return nullptr if no deadlock */ + inline trx_t *search(); /** Print transaction data to the deadlock file and possibly to stderr. @param trx transaction @@ -234,36 +229,38 @@ private: static ib_uint64_t s_lock_mark_counter; /** Calculation steps thus far. It is the count of the nodes visited. */ - ulint m_cost; + ulint m_cost= 0; + +#ifdef HAVE_REPLICATION + /** Set if thd_rpl_deadlock_check() should be called for waits. */ + const bool m_report_waiters; +#endif /** Joining transaction that is requesting a lock in an incompatible mode */ - const trx_t* m_start; + trx_t* const m_start; /** TRUE if search was too deep and was aborted */ - bool m_too_deep; + bool m_too_deep= false; /** Lock that trx wants */ const lock_t* m_wait_lock; /** Value of lock_mark_count at the start of the deadlock check. */ - ib_uint64_t m_mark_start; + const ib_uint64_t m_mark_start; /** Number of states pushed onto the stack */ - size_t m_n_elems; + size_t m_n_elems= 0; /** This is to avoid malloc/free calls. */ - static state_t s_states[MAX_STACK_SIZE]; - - /** Set if thd_rpl_deadlock_check() should be called for waits. */ - const bool m_report_waiters; + static state_t s_states[4096]; }; /** Counter to mark visited nodes during deadlock search. */ ib_uint64_t DeadlockChecker::s_lock_mark_counter = 0; /** The stack used for deadlock searches. */ -DeadlockChecker::state_t DeadlockChecker::s_states[MAX_STACK_SIZE]; +DeadlockChecker::state_t DeadlockChecker::s_states[4096]; #ifdef UNIV_DEBUG /*********************************************************************//** @@ -285,10 +282,6 @@ static bool lock_rec_validate_page(const buf_block_t *block, bool latched) /* The lock system */ lock_sys_t lock_sys; -/** We store info on the latest deadlock error to this buffer. InnoDB -Monitor will then fetch it and print */ -static bool lock_deadlock_found = false; - /** Only created if !srv_read_only_mode */ static FILE* lock_latest_err_file; @@ -507,6 +500,38 @@ static void wsrep_assert_no_bf_bf_wait( /* BF-BF wait is a bug */ ut_error; } + +/*********************************************************************//** +check if lock timeout was for priority thread, +as a side effect trigger lock monitor +@param[in] trx transaction owning the lock +@param[in] locked true if trx and lock_sys.latch is held +@return false for regular lock timeout */ +static +bool +wsrep_is_BF_lock_timeout( + const trx_t* trx, + bool locked = true) +{ + if (trx->error_state != DB_DEADLOCK && trx->is_wsrep() && + srv_monitor_timer && wsrep_thd_is_BF(trx->mysql_thd, FALSE)) { + ib::info() << "WSREP: BF lock wait long for trx:" << ib::hex(trx->id) + << " query: " << wsrep_thd_query(trx->mysql_thd); + if (!locked) { + LockMutexGuard g; + trx_print_latched(stderr, trx, 3000); + } else { + lock_sys.mutex_assert_locked(); + trx_print_latched(stderr, trx, 3000); + } + + srv_print_innodb_monitor = TRUE; + srv_print_innodb_lock_monitor = TRUE; + srv_monitor_timer_schedule_now(); + return true; + } + return false; +} #endif /* WITH_WSREP */ /*********************************************************************//** @@ -588,6 +613,7 @@ lock_rec_has_to_wait( return false; } +#ifdef HAVE_REPLICATION if ((type_mode & LOCK_GAP || lock2->is_gap()) && !thd_need_ordering_with(trx->mysql_thd, lock2->trx->mysql_thd)) { /* If the upper server layer has already decided on the @@ -612,6 +638,7 @@ lock_rec_has_to_wait( return false; } +#endif /* HAVE_REPLICATION */ #ifdef WITH_WSREP /* There should not be two conflicting locks that are @@ -1207,10 +1234,11 @@ lock_rec_create_low( if (holds_trx_mutex) { trx->mutex.wr_lock(); } - c_lock->trx->mutex.wr_lock(); - if (c_lock->trx->lock.wait_thr) { - - c_lock->trx->lock.was_chosen_as_deadlock_victim = TRUE; + trx_t *ctrx = c_lock->trx; + ctrx->mutex.wr_lock(); + if (ctrx->lock.wait_thr) { + ctrx->lock.was_chosen_as_deadlock_victim = true; + ctrx->lock.was_chosen_as_wsrep_victim = true; if (UNIV_UNLIKELY(wsrep_debug)) { wsrep_print_wait_locks(c_lock); @@ -1229,20 +1257,20 @@ lock_rec_create_low( trx->mutex.wr_unlock(); } lock_cancel_waiting_and_release( - c_lock->trx->lock.wait_lock); + ctrx->lock.wait_lock); if (holds_trx_mutex) { trx->mutex.wr_lock(); } - c_lock->trx->mutex.wr_unlock(); + ctrx->mutex.wr_unlock(); /* have to bail out here to avoid lock_set_lock... */ mysql_mutex_unlock(&lock_sys.wait_mutex); return(lock); } mysql_mutex_unlock(&lock_sys.wait_mutex); - c_lock->trx->mutex.wr_unlock(); + ctrx->mutex.wr_unlock(); } else #endif /* WITH_WSREP */ HASH_INSERT(lock_t, hash, lock_hash_get(type_mode), @@ -1281,9 +1309,7 @@ Check for deadlocks. @param[in,out] thr query thread @param[in] prdt minimum bounding box (spatial index) @retval DB_LOCK_WAIT if the waiting lock was enqueued -@retval DB_DEADLOCK if this transaction was chosen as the victim -@retval DB_SUCCESS_LOCKED_REC if the other transaction was chosen as a victim - (or it happened to commit) */ +@retval DB_DEADLOCK if this transaction was chosen as the victim */ dberr_t lock_rec_enqueue_waiting( #ifdef WITH_WSREP @@ -1333,33 +1359,7 @@ lock_rec_enqueue_waiting( lock_prdt_set_prdt(lock, prdt); } - if (ut_d(const trx_t* victim =) - DeadlockChecker::check_and_resolve(lock, trx)) { - ut_ad(victim == trx); - /* There is no need to hold lock_sys.wait_mutex here, - because we are clearing the wait flag on a lock request - that is associated with the current transaction. So, - this is not conflicting with lock_wait(). */ - lock_reset_lock_and_trx_wait(lock); - lock_rec_reset_nth_bit(lock, heap_no); - return DB_DEADLOCK; - } - - if (!trx->lock.wait_lock) { - /* If there was a deadlock but we chose another - transaction as a victim, it is possible that we - already have the lock now granted! */ -#ifdef WITH_WSREP - if (UNIV_UNLIKELY(wsrep_debug)) { - ib::info() << "WSREP: BF thread got lock granted early, ID " << ib::hex(trx->id) - << " query: " << wsrep_thd_query(trx->mysql_thd); - } -#endif - return DB_SUCCESS_LOCKED_REC; - } - trx->lock.wait_thr = thr; - trx->lock.was_chosen_as_deadlock_victim = false; DBUG_LOG("ib_lock", "trx " << ib::hex(trx->id) @@ -1668,6 +1668,178 @@ lock_rec_has_to_wait_in_queue( return(NULL); } +/** Note that a record lock wait started */ +inline void lock_sys_t::wait_start() +{ + mysql_mutex_assert_owner(&wait_mutex); + wait_pending++; + wait_count++; +} + +/** Note that a record lock wait resumed */ +inline +void lock_sys_t::wait_resume(THD *thd, my_hrtime_t start, my_hrtime_t now) +{ + mysql_mutex_assert_owner(&wait_mutex); + wait_pending--; + if (now.val >= start.val) + { + const ulint diff_time= static_cast((now.val - start.val) / 1000); + wait_time+= diff_time; + + if (diff_time > wait_time_max) + wait_time_max= diff_time; + + thd_storage_lock_wait(thd, diff_time); + } +} + +/** Wait for a lock to be released. +@retval DB_DEADLOCK if this transaction was chosen as the deadlock victim +@retval DB_INTERRUPTED if the execution was interrupted by the user +@retval DB_LOCK_WAIT_TIMEOUT if the lock wait timed out +@retval DB_SUCCESS if the lock was granted */ +dberr_t lock_wait(que_thr_t *thr) +{ + trx_t *trx= thr_get_trx(thr); + + if (trx->mysql_thd) + DEBUG_SYNC_C("lock_wait_suspend_thread_enter"); + + /* InnoDB system transactions may use the global value of + innodb_lock_wait_timeout, because trx->mysql_thd == NULL. */ + const ulong innodb_lock_wait_timeout= trx_lock_wait_timeout_get(trx); + const bool no_timeout= innodb_lock_wait_timeout > 100000000; + const my_hrtime_t suspend_time= my_hrtime_coarse(); + ut_ad(!trx->dict_operation_lock_mode || + trx->dict_operation_lock_mode == RW_S_LATCH); + const bool row_lock_wait= thr->lock_state == QUE_THR_LOCK_ROW; + bool had_dict_lock= trx->dict_operation_lock_mode != 0; + + mysql_mutex_lock(&lock_sys.wait_mutex); + trx->mutex.wr_lock(); + trx->error_state= DB_SUCCESS; + + if (!trx->lock.wait_lock) + { + /* The lock has already been released or this transaction + was chosen as a deadlock victim: no need to suspend */ + + if (trx->lock.was_chosen_as_deadlock_victim + IF_WSREP(|| trx->lock.was_chosen_as_wsrep_victim,)) + { + trx->error_state= DB_DEADLOCK; + trx->lock.was_chosen_as_deadlock_victim= false; + } + + mysql_mutex_unlock(&lock_sys.wait_mutex); + trx->mutex.wr_unlock(); + return trx->error_state; + } + + trx->lock.suspend_time= suspend_time; + trx->mutex.wr_unlock(); + + if (row_lock_wait) + lock_sys.wait_start(); + + int err= 0; + int wait_for= 0; + + /* The wait_lock can be cleared by another thread in lock_grant(), + lock_rec_cancel(), or lock_cancel_waiting_and_release(). But, a wait + can only be initiated by the current thread which owns the transaction. */ + if (const lock_t *wait_lock= trx->lock.wait_lock) + { + static_assert(THD_WAIT_TABLE_LOCK != 0, "compatibility"); + static_assert(THD_WAIT_ROW_LOCK != 0, "compatibility"); + wait_for= wait_lock->is_table() ? THD_WAIT_TABLE_LOCK : THD_WAIT_ROW_LOCK; + mysql_mutex_unlock(&lock_sys.wait_mutex); + + if (had_dict_lock) /* Release foreign key check latch */ + row_mysql_unfreeze_data_dictionary(trx); + + timespec abstime; + set_timespec_time_nsec(abstime, suspend_time.val * 1000); + abstime.MY_tv_sec+= innodb_lock_wait_timeout; + thd_wait_begin(trx->mysql_thd, wait_for); + + const bool deadlock= DeadlockChecker::check_and_resolve(trx); + if (deadlock) + trx->error_state= DB_DEADLOCK; + + mysql_mutex_lock(&lock_sys.wait_mutex); + + if (deadlock) + goto end_wait; + + while (trx->lock.wait_lock) + { + if (no_timeout) + mysql_cond_wait(&trx->lock.cond, &lock_sys.wait_mutex); + else + err= mysql_cond_timedwait(&trx->lock.cond, &lock_sys.wait_mutex, + &abstime); + switch (trx->error_state) { + default: + if (trx_is_interrupted(trx)) + /* innobase_kill_query() can only set trx->error_state=DB_INTERRUPTED + for any transaction that is attached to a connection. */ + trx->error_state= DB_INTERRUPTED; + else if (!err) + continue; + else + break; + /* fall through */ + case DB_DEADLOCK: + case DB_INTERRUPTED: + err= 0; + } + break; + } + } + else + had_dict_lock= false; + +end_wait: + if (row_lock_wait) + lock_sys.wait_resume(trx->mysql_thd, suspend_time, my_hrtime_coarse()); + + mysql_mutex_unlock(&lock_sys.wait_mutex); + + if (wait_for) + thd_wait_end(trx->mysql_thd); + + if (had_dict_lock) + row_mysql_freeze_data_dictionary(trx); + + if (!err); +#ifdef WITH_WSREP + else if (trx->is_wsrep() && wsrep_is_BF_lock_timeout(trx, false)); +#endif + else + { + trx->error_state= DB_LOCK_WAIT_TIMEOUT; + MONITOR_INC(MONITOR_TIMEOUT); + } + + if (trx->lock.wait_lock) + { + { + LockMutexGuard g; + mysql_mutex_lock(&lock_sys.wait_mutex); + if (lock_t *lock= trx->lock.wait_lock) + { + trx->mutex.wr_lock(); + lock_cancel_waiting_and_release(lock); + trx->mutex.wr_unlock(); + } + } + mysql_mutex_unlock(&lock_sys.wait_mutex); + } + + return trx->error_state; +} /** Resume a lock wait */ static void lock_wait_end(trx_t *trx) @@ -3196,8 +3368,7 @@ lock_table_remove_low( Enqueues a waiting request for a table lock which cannot be granted immediately. Checks for deadlocks. @retval DB_LOCK_WAIT if the waiting lock was enqueued -@retval DB_DEADLOCK if this transaction was chosen as the victim -@retval DB_SUCCESS if the other transaction committed or aborted */ +@retval DB_DEADLOCK if this transaction was chosen as the victim */ static dberr_t lock_table_enqueue_waiting( @@ -3212,7 +3383,6 @@ lock_table_enqueue_waiting( ) { trx_t* trx; - lock_t* lock; lock_sys.mutex_assert_locked(); ut_ad(!srv_read_only_mode); @@ -3237,40 +3407,16 @@ lock_table_enqueue_waiting( #endif /* WITH_WSREP */ /* Enqueue the lock request that will wait to be granted */ - lock = lock_table_create(table, mode | LOCK_WAIT, trx + lock_table_create(table, mode | LOCK_WAIT, trx #ifdef WITH_WSREP - , c_lock + , c_lock #endif - ); - - const trx_t* victim_trx = - DeadlockChecker::check_and_resolve(lock, trx); - - if (victim_trx) { - ut_ad(victim_trx == trx); - /* The order here is important, we don't want to - lose the state of the lock before calling remove. */ - lock_table_remove_low(lock); - /* There is no need to hold lock_sys.wait_mutex here, - because we are clearing the wait flag on a lock request - that is associated with the current transaction. So, - this is not conflicting with lock_wait(). */ - lock_reset_lock_and_trx_wait(lock); - - return(DB_DEADLOCK); - - } else if (trx->lock.wait_lock == NULL) { - /* Deadlock resolution chose another transaction as a victim, - and we accidentally got our lock granted! */ - - return(DB_SUCCESS); - } + ); trx->lock.wait_thr = thr; trx->lock.was_chosen_as_deadlock_victim = false; MONITOR_INC(MONITOR_TABLELOCK_WAIT); - return(DB_LOCK_WAIT); } @@ -3383,6 +3529,8 @@ lock_table( trx_set_rw_mode(trx); } + err = DB_SUCCESS; + lock_sys.mutex_lock(); /* We have to check if the new lock is compatible with any locks @@ -3393,22 +3541,14 @@ lock_table( trx->mutex.wr_lock(); - /* Another trx has a request on the table in an incompatible - mode: this trx may have to wait */ - - if (wait_for != NULL) { - err = lock_table_enqueue_waiting(flags | mode, table, - thr + if (wait_for) { + err = lock_table_enqueue_waiting(mode, table, thr #ifdef WITH_WSREP , wait_for #endif ); } else { - lock_table_create(table, flags | mode, trx); - - ut_a(!flags || mode == LOCK_S || mode == LOCK_X); - - err = DB_SUCCESS; + lock_table_create(table, mode, trx); } lock_sys.mutex_unlock(); @@ -4036,7 +4176,7 @@ lock_print_info_summary( return(FALSE); } - if (lock_deadlock_found) { + if (lock_sys.deadlocks) { fputs("------------------------\n" "LATEST DETECTED DEADLOCK\n" "------------------------\n", file); @@ -5918,10 +6058,10 @@ DeadlockChecker::notify(const lock_t* lock) const /* It is possible that the joining transaction was granted its lock when we rolled back some other waiting transaction. */ - if (m_start->lock.wait_lock != 0) { + if (auto wait_lock= m_start->lock.wait_lock) { print("*** (2) WAITING FOR THIS LOCK TO BE GRANTED:\n"); - print(m_start->lock.wait_lock); + print(wait_lock); } DBUG_PRINT("ib_lock", ("deadlock detected")); @@ -5938,43 +6078,39 @@ inline bool trx_weight_ge(const trx_t *a, const trx_t *b) return a_notrans != b_notrans ? a_notrans : TRX_WEIGHT(a) >= TRX_WEIGHT(b); } -/** Select the victim transaction that should be rolledback. -@return victim transaction */ -const trx_t* -DeadlockChecker::select_victim() const +/** @return the victim transaction that should be rolled back */ +trx_t *DeadlockChecker::select_victim() const { - lock_sys.mutex_assert_locked(); - ut_ad(m_start->lock.wait_lock != 0); - ut_ad(m_wait_lock->trx != m_start); + lock_sys.mutex_assert_locked(); + trx_t *lock_trx= m_wait_lock->trx; + ut_ad(m_start->lock.wait_lock); + ut_ad(lock_trx != m_start); - if (trx_weight_ge(m_wait_lock->trx, m_start)) { - /* The joining transaction is 'smaller', - choose it as the victim and roll it back. */ + if (trx_weight_ge(lock_trx, m_start)) + { #ifdef WITH_WSREP - if (wsrep_thd_is_BF(m_start->mysql_thd, FALSE)) { - return(m_wait_lock->trx); - } + if (m_start->is_wsrep() && wsrep_thd_is_BF(m_start->mysql_thd, FALSE)) + return lock_trx; #endif /* WITH_WSREP */ - return(m_start); - } + /* The joining transaction is 'smaller', choose it as the victim */ + return m_start; + } #ifdef WITH_WSREP - if (wsrep_thd_is_BF(m_wait_lock->trx->mysql_thd, FALSE)) { - return(m_start); - } + if (lock_trx->is_wsrep() && wsrep_thd_is_BF(lock_trx->mysql_thd, FALSE)) + return m_start; #endif /* WITH_WSREP */ - return(m_wait_lock->trx); + return lock_trx; } /** Looks iteratively for a deadlock. Note: the joining transaction may have been granted its lock by the deadlock checks. -@return 0 if no deadlock else the victim transaction instance.*/ -const trx_t* -DeadlockChecker::search() +@return the victim transaction +@return nullptr if no deadlock */ +inline trx_t* DeadlockChecker::search() { lock_sys.mutex_assert_locked(); - mysql_mutex_assert_owner(&lock_sys.wait_mutex); ut_ad(m_start != NULL); ut_ad(m_wait_lock != NULL); @@ -6040,18 +6176,24 @@ DeadlockChecker::search() return m_start; } +#ifdef HAVE_REPLICATION /* We do not need to report autoinc locks to the upper layer. These locks are released before commit, so they can not cause deadlocks with binlog-fixed commit order. */ - if (m_report_waiters && !(lock->type_mode & LOCK_AUTO_INC)) { + if (m_report_waiters) { + ut_ad(!(lock->type_mode & LOCK_AUTO_INC)); thd_rpl_deadlock_check(m_start->mysql_thd, trx->mysql_thd); } +#endif /* HAVE_REPLICATION */ - lock_t* wait_lock = trx->lock.wait_lock; + if (!trx->lock.wait_thr) { + } else if (lock_t* wait_lock = trx->lock.wait_lock) { + /* Note: Because we are not holding + lock_sys.wait_mutex here, trx->lock.wait_lock + could be reset soon after we read it. */ - if (wait_lock && trx->lock.wait_thr) { /* Another trx ahead has requested a lock in an incompatible mode, and is itself waiting for a lock. */ @@ -6066,12 +6208,12 @@ DeadlockChecker::search() lock = get_first_lock(&heap_no); - if (is_visited(lock)) { - lock = get_next_lock(lock, heap_no); + if (!is_visited(lock)) { + continue; } - } else { - lock = get_next_lock(lock, heap_no); } + + lock = get_next_lock(lock, heap_no); } ut_a(lock == NULL && m_n_elems == 0); @@ -6105,119 +6247,90 @@ DeadlockChecker::rollback_print(const trx_t* trx, const lock_t* lock) print(lock); } -/** Rollback transaction selected as the victim. */ -void -DeadlockChecker::trx_rollback() -{ - lock_sys.mutex_assert_locked(); - mysql_mutex_assert_owner(&lock_sys.wait_mutex); - - trx_t* trx = m_wait_lock->trx; - - print("*** WE ROLL BACK TRANSACTION (1)\n"); -#ifdef WITH_WSREP - if (trx->is_wsrep() && wsrep_thd_is_SR(trx->mysql_thd)) { - wsrep_handle_SR_rollback(m_start->mysql_thd, trx->mysql_thd); - } -#endif - trx->mutex.wr_lock(); - trx->lock.was_chosen_as_deadlock_victim = true; - lock_cancel_waiting_and_release(trx->lock.wait_lock); - trx->mutex.wr_unlock(); -} - /** Check if a joining lock request results in a deadlock. If a deadlock is found, we will resolve the deadlock by -choosing a victim transaction and rolling it back. +choosing a victim transaction to be rolled it back. We will attempt to resolve all deadlocks. -@param[in] lock the lock request -@param[in,out] trx transaction requesting the lock +@param[in,out] trx transaction requesting a lock -@return trx if it was chosen as victim -@retval NULL if another victim was chosen, -or there is no deadlock (any more) */ -const trx_t* -DeadlockChecker::check_and_resolve(const lock_t* lock, trx_t* trx) +@return whether the transaction was chosen as victim */ +inline bool DeadlockChecker::check_and_resolve(trx_t *trx) { - lock_sys.mutex_assert_locked(); - check_trx_state(trx); - ut_ad(!srv_read_only_mode); + ut_ad(trx->state == TRX_STATE_ACTIVE); + ut_ad(!srv_read_only_mode); - if (!innobase_deadlock_detect) { - return(NULL); - } + if (!innobase_deadlock_detect || !trx->lock.wait_lock) + return false; - /* Release the mutex to obey the latching order. - This is safe, because DeadlockChecker::check_and_resolve() - is invoked when a lock wait is enqueued for the currently - running transaction. Because m_trx is a running transaction - (it is not currently suspended because of a lock wait), - its state can only be changed by this thread, which is - currently associated with the transaction. */ - - trx->mutex.wr_unlock(); - - const trx_t* victim_trx; - const bool report_waiters = trx->mysql_thd - && thd_need_wait_reports(trx->mysql_thd); - - /* Try and resolve as many deadlocks as possible. */ - do { - mysql_mutex_lock(&lock_sys.wait_mutex); - DeadlockChecker checker(trx, lock, s_lock_mark_counter, - report_waiters); - - victim_trx = checker.search(); - - /* Search too deep, we rollback the joining transaction only - if it is possible to rollback. Otherwise we rollback the - transaction that is holding the lock that the joining - transaction wants. */ - if (checker.is_too_deep()) { - - ut_ad(trx == checker.m_start); - ut_ad(trx == victim_trx); - - rollback_print(victim_trx, lock); - - MONITOR_INC(MONITOR_DEADLOCK); - srv_stats.lock_deadlock_count.inc(); - mysql_mutex_unlock(&lock_sys.wait_mutex); - break; - - } else if (victim_trx != NULL && victim_trx != trx) { - - ut_ad(victim_trx == checker.m_wait_lock->trx); - - checker.trx_rollback(); - - lock_deadlock_found = true; - - MONITOR_INC(MONITOR_DEADLOCK); - srv_stats.lock_deadlock_count.inc(); - } - - mysql_mutex_unlock(&lock_sys.wait_mutex); - } while (victim_trx != NULL && victim_trx != trx); - - /* If the joining transaction was selected as the victim. */ - if (victim_trx != NULL) { - - print("*** WE ROLL BACK TRANSACTION (2)\n"); -#ifdef WITH_WSREP - if (trx->is_wsrep() && wsrep_thd_is_SR(trx->mysql_thd)) { - wsrep_handle_SR_rollback(trx->mysql_thd, - victim_trx->mysql_thd); - } +#ifdef HAVE_REPLICATION + const bool report_waiters= thd_need_wait_reports(trx->mysql_thd); +#else + const bool report_waiters= false; #endif - lock_deadlock_found = true; - } + trx_t *victim_trx; - trx->mutex.wr_lock(); + for (;;) + { + LockMutexGuard g; // FIXME: only lock_sys.wait_mutex? + lock_t *lock= trx->lock.wait_lock; - return(victim_trx); + if (!lock) + return false; + + if (trx->lock.was_chosen_as_deadlock_victim) + return true; + + DeadlockChecker checker(trx, lock, s_lock_mark_counter, report_waiters && + !(lock->type_mode & LOCK_AUTO_INC)); + victim_trx= checker.search(); + + /* Search too deep, we rollback the joining transaction only if it + is possible to rollback. Otherwise we rollback the transaction + that is holding the lock that the joining transaction wants. */ + if (checker.is_too_deep()) + { + ut_ad(trx == checker.m_start); + ut_ad(trx == victim_trx); + rollback_print(trx, lock); +self_victim: + print("*** WE ROLL BACK TRANSACTION (2)\n"); + } + else if (victim_trx == trx) + goto self_victim; + else if (!victim_trx) + return false; + else + { + mysql_mutex_lock(&lock_sys.wait_mutex); + victim_trx->mutex.wr_lock(); + const auto state= victim_trx->state; + if (state != TRX_STATE_ACTIVE) + { + ut_ad(state == TRX_STATE_COMMITTED_IN_MEMORY); + mysql_mutex_unlock(&lock_sys.wait_mutex); + victim_trx->mutex.wr_unlock(); + return false; + } + print("*** WE ROLL BACK TRANSACTION (1)\n"); + victim_trx->lock.was_chosen_as_deadlock_victim= true; + lock_cancel_waiting_and_release(victim_trx->lock.wait_lock); + mysql_mutex_unlock(&lock_sys.wait_mutex); + victim_trx->mutex.wr_unlock(); + } + lock_sys.deadlocks++; + break; + } + + ut_ad(trx->state == TRX_STATE_ACTIVE); + +#ifdef WITH_WSREP + if (victim_trx->is_wsrep() && wsrep_thd_is_SR(victim_trx->mysql_thd)) + wsrep_handle_SR_rollback(trx->mysql_thd, victim_trx->mysql_thd); +#endif + + return victim_trx == trx; } /*************************************************************//** diff --git a/storage/innobase/lock/lock0prdt.cc b/storage/innobase/lock/lock0prdt.cc index ccf7f8d0b55..2f36bd3bef8 100644 --- a/storage/innobase/lock/lock0prdt.cc +++ b/storage/innobase/lock/lock0prdt.cc @@ -571,18 +571,11 @@ lock_prdt_insert_check_and_lock( lock_sys.mutex_unlock(); - switch (err) { - case DB_SUCCESS_LOCKED_REC: - err = DB_SUCCESS; - /* fall through */ - case DB_SUCCESS: + if (err == DB_SUCCESS) { /* Update the page max trx id field */ page_update_max_trx_id(block, buf_block_get_page_zip(block), trx->id, mtr); - default: - /* We only care about the two return values. */ - break; } return(err); diff --git a/storage/innobase/lock/lock0wait.cc b/storage/innobase/lock/lock0wait.cc deleted file mode 100644 index 6c9da9518b8..00000000000 --- a/storage/innobase/lock/lock0wait.cc +++ /dev/null @@ -1,234 +0,0 @@ -/***************************************************************************** - -Copyright (c) 1996, 2016, Oracle and/or its affiliates. All Rights Reserved. -Copyright (c) 2014, 2021, MariaDB Corporation. - -This program is free software; you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation; version 2 of the License. - -This program is distributed in the hope that it will be useful, but WITHOUT -ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program; if not, write to the Free Software Foundation, Inc., -51 Franklin Street, Fifth Floor, Boston, MA 02110-1335 USA - -*****************************************************************************/ - -/**************************************************//** -@file lock/lock0wait.cc -The transaction lock system - -Created 25/5/2010 Sunny Bains -*******************************************************/ - -#define LOCK_MODULE_IMPLEMENTATION - -#include "univ.i" -#include -#include - -#include "srv0mon.h" -#include "que0que.h" -#include "lock0lock.h" -#include "row0mysql.h" -#include "srv0start.h" -#include "lock0priv.h" - -#ifdef WITH_WSREP -/*********************************************************************//** -check if lock timeout was for priority thread, -as a side effect trigger lock monitor -@param[in] trx transaction owning the lock -@param[in] locked true if trx and lock_sys.mutex is ownd -@return false for regular lock timeout */ -static -bool -wsrep_is_BF_lock_timeout( - const trx_t* trx, - bool locked = true) -{ - if (trx->error_state != DB_DEADLOCK && trx->is_wsrep() && - srv_monitor_timer && wsrep_thd_is_BF(trx->mysql_thd, FALSE)) { - ib::info() << "WSREP: BF lock wait long for trx:" << ib::hex(trx->id) - << " query: " << wsrep_thd_query(trx->mysql_thd); - if (!locked) { - lock_sys.mutex_lock(); - } - - lock_sys.mutex_assert_locked(); - - trx_print_latched(stderr, trx, 3000); - - if (!locked) { - lock_sys.mutex_unlock(); - } - - srv_print_innodb_monitor = TRUE; - srv_print_innodb_lock_monitor = TRUE; - srv_monitor_timer_schedule_now(); - return true; - } - return false; -} -#endif /* WITH_WSREP */ - -/** Note that a record lock wait started */ -inline void lock_sys_t::wait_start() -{ - mysql_mutex_assert_owner(&wait_mutex); - wait_pending++; - wait_count++; -} - -/** Note that a record lock wait resumed */ -inline -void lock_sys_t::wait_resume(THD *thd, my_hrtime_t start, my_hrtime_t now) -{ - mysql_mutex_assert_owner(&wait_mutex); - wait_pending--; - if (now.val >= start.val) - { - const ulint diff_time= static_cast((now.val - start.val) / 1000); - wait_time+= diff_time; - - if (diff_time > wait_time_max) - wait_time_max= diff_time; - - thd_storage_lock_wait(thd, diff_time); - } -} - -/** Wait for a lock to be released. -@retval DB_DEADLOCK if this transaction was chosen as the deadlock victim -@retval DB_INTERRUPTED if the execution was interrupted by the user -@retval DB_LOCK_WAIT_TIMEOUT if the lock wait timed out -@retval DB_SUCCESS if the lock was granted */ -dberr_t lock_wait(que_thr_t *thr) -{ - trx_t *trx= thr_get_trx(thr); - - if (trx->mysql_thd) - DEBUG_SYNC_C("lock_wait_suspend_thread_enter"); - - /* InnoDB system transactions may use the global value of - innodb_lock_wait_timeout, because trx->mysql_thd == NULL. */ - const ulong innodb_lock_wait_timeout= trx_lock_wait_timeout_get(trx); - const bool no_timeout= innodb_lock_wait_timeout > 100000000; - const my_hrtime_t suspend_time= my_hrtime_coarse(); - ut_ad(!trx->dict_operation_lock_mode || - trx->dict_operation_lock_mode == RW_S_LATCH); - const bool row_lock_wait= thr->lock_state == QUE_THR_LOCK_ROW; - bool had_dict_lock= trx->dict_operation_lock_mode != 0; - - mysql_mutex_lock(&lock_sys.wait_mutex); - trx->mutex.wr_lock(); - trx->error_state= DB_SUCCESS; - - if (!trx->lock.wait_lock) - { - /* The lock has already been released or this transaction - was chosen as a deadlock victim: no need to suspend */ - - if (trx->lock.was_chosen_as_deadlock_victim) - { - trx->error_state= DB_DEADLOCK; - trx->lock.was_chosen_as_deadlock_victim= false; - } - - mysql_mutex_unlock(&lock_sys.wait_mutex); - trx->mutex.wr_unlock(); - return trx->error_state; - } - - trx->lock.suspend_time= suspend_time; - trx->mutex.wr_unlock(); - - if (row_lock_wait) - lock_sys.wait_start(); - - int err= 0; - - /* The wait_lock can be cleared by another thread in lock_grant(), - lock_rec_cancel(), or lock_cancel_waiting_and_release(). But, a wait - can only be initiated by the current thread which owns the transaction. */ - if (const lock_t *wait_lock= trx->lock.wait_lock) - { - if (had_dict_lock) /* Release foreign key check latch */ - { - mysql_mutex_unlock(&lock_sys.wait_mutex); - row_mysql_unfreeze_data_dictionary(trx); - mysql_mutex_lock(&lock_sys.wait_mutex); - } - timespec abstime; - set_timespec_time_nsec(abstime, suspend_time.val * 1000); - abstime.MY_tv_sec+= innodb_lock_wait_timeout; - thd_wait_begin(trx->mysql_thd, wait_lock->is_table() - ? THD_WAIT_TABLE_LOCK : THD_WAIT_ROW_LOCK); - while (trx->lock.wait_lock) - { - if (no_timeout) - mysql_cond_wait(&trx->lock.cond, &lock_sys.wait_mutex); - else - err= mysql_cond_timedwait(&trx->lock.cond, &lock_sys.wait_mutex, - &abstime); - switch (trx->error_state) { - default: - if (trx_is_interrupted(trx)) - /* innobase_kill_query() can only set trx->error_state=DB_INTERRUPTED - for any transaction that is attached to a connection. */ - trx->error_state= DB_INTERRUPTED; - else if (!err) - continue; - else - break; - /* fall through */ - case DB_DEADLOCK: - case DB_INTERRUPTED: - err= 0; - } - break; - } - thd_wait_end(trx->mysql_thd); - } - else - had_dict_lock= false; - - if (row_lock_wait) - lock_sys.wait_resume(trx->mysql_thd, suspend_time, my_hrtime_coarse()); - - mysql_mutex_unlock(&lock_sys.wait_mutex); - - if (had_dict_lock) - row_mysql_freeze_data_dictionary(trx); - - if (!err); -#ifdef WITH_WSREP - else if (trx->is_wsrep() && wsrep_is_BF_lock_timeout(trx, false)); -#endif - else - { - trx->error_state= DB_LOCK_WAIT_TIMEOUT; - MONITOR_INC(MONITOR_TIMEOUT); - } - - if (trx->lock.wait_lock) - { - { - lock_sys.mutex_lock(); - mysql_mutex_lock(&lock_sys.wait_mutex); - if (lock_t *lock= trx->lock.wait_lock) - { - trx->mutex.wr_lock(); - lock_cancel_waiting_and_release(lock); - trx->mutex.wr_unlock(); - } - lock_sys.mutex_unlock(); - mysql_mutex_unlock(&lock_sys.wait_mutex); - } - } - - return trx->error_state; -} diff --git a/storage/innobase/srv/srv0mon.cc b/storage/innobase/srv/srv0mon.cc index 4ccd0adb360..7795400edfd 100644 --- a/storage/innobase/srv/srv0mon.cc +++ b/storage/innobase/srv/srv0mon.cc @@ -91,7 +91,8 @@ static monitor_info_t innodb_counter_info[] = MONITOR_DEFAULT_START, MONITOR_MODULE_LOCK}, {"lock_deadlocks", "lock", "Number of deadlocks", - MONITOR_DEFAULT_ON, + static_cast( + MONITOR_EXISTING | MONITOR_DEFAULT_ON | MONITOR_DISPLAY_CURRENT), MONITOR_DEFAULT_START, MONITOR_DEADLOCK}, {"lock_timeouts", "lock", "Number of lock timeouts", @@ -1879,6 +1880,9 @@ srv_mon_process_existing_counter( case MONITOR_OVLD_PAGES_DECRYPTED: value = srv_stats.pages_decrypted; break; + case MONITOR_DEADLOCK: + value = lock_sys.deadlocks; + break; default: ut_error; diff --git a/storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result b/storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result index 27db55e15d6..28b0f6bb767 100644 --- a/storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result +++ b/storage/rocksdb/mysql-test/rocksdb/r/innodb_i_s_tables_disabled.result @@ -21,7 +21,7 @@ NAME SUBSYSTEM COUNT MAX_COUNT MIN_COUNT AVG_COUNT COUNT_RESET MAX_COUNT_RESET M metadata_table_handles_opened metadata 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of table handles opened metadata_table_handles_closed metadata 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of table handles closed metadata_table_reference_count metadata 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Table reference counter -lock_deadlocks lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of deadlocks +lock_deadlocks lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 value Number of deadlocks lock_timeouts lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of lock timeouts lock_rec_lock_waits lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times enqueued into record lock wait queue lock_table_lock_waits lock 0 NULL NULL NULL 0 NULL NULL NULL NULL NULL NULL NULL 0 counter Number of times enqueued into table lock wait queue