diff options
author | Sergey Vojtovich <svoj@mariadb.org> | 2015-02-27 11:30:35 +0400 |
---|---|---|
committer | Sergey Vojtovich <svoj@mariadb.org> | 2015-03-04 13:34:53 +0400 |
commit | 5c6aa4dea00c4e289491dfa717a25b56ec9441c2 (patch) | |
tree | 6f5a781883e0414975f6928eecd0729013efa0ec | |
parent | d9e3a9f20e59142666c72c7fb876fbf05b0cf510 (diff) | |
download | mariadb-git-5c6aa4dea00c4e289491dfa717a25b56ec9441c2.tar.gz |
MDEV-6089 - MySQL WL#7305 "Improve MDL scalability by using lock-free hash"
Replaced hash with lock-free hash.
-rw-r--r-- | mysql-test/r/create_or_replace.result | 12 | ||||
-rw-r--r-- | sql/mdl.cc | 292 | ||||
-rw-r--r-- | sql/mdl.h | 4 |
3 files changed, 137 insertions, 171 deletions
diff --git a/mysql-test/r/create_or_replace.result b/mysql-test/r/create_or_replace.result index ac6eb7693a6..f73943be5c8 100644 --- a/mysql-test/r/create_or_replace.result +++ b/mysql-test/r/create_or_replace.result @@ -264,8 +264,8 @@ select * from information_schema.metadata_lock_info; THREAD_ID LOCK_MODE LOCK_DURATION LOCK_TYPE TABLE_SCHEMA TABLE_NAME # MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Global read lock # MDL_SHARED_NO_READ_WRITE MDL_EXPLICIT Table metadata lock test t1 -# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test # MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock mysqltest2 +# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test # MDL_SHARED_NO_READ_WRITE MDL_EXPLICIT Table metadata lock mysqltest2 t2 create or replace table test.t1; ERROR 42000: A table must have at least 1 column @@ -291,8 +291,8 @@ select * from information_schema.metadata_lock_info; THREAD_ID LOCK_MODE LOCK_DURATION LOCK_TYPE TABLE_SCHEMA TABLE_NAME # MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Global read lock # MDL_SHARED_NO_READ_WRITE MDL_EXPLICIT Table metadata lock test t1 -# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test # MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock mysqltest2 +# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test # MDL_SHARED_NO_READ_WRITE MDL_EXPLICIT Table metadata lock mysqltest2 t2 create or replace table test.t1 (a int) select 1 as 'a', 2 as 'a'; ERROR 42S21: Duplicate column name 'a' @@ -400,29 +400,29 @@ select * from information_schema.metadata_lock_info; THREAD_ID LOCK_MODE LOCK_DURATION LOCK_TYPE TABLE_SCHEMA TABLE_NAME # MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Global read lock # MDL_SHARED_NO_READ_WRITE MDL_EXPLICIT Table metadata lock test t1 -# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test # MDL_SHARED_READ MDL_EXPLICIT Table metadata lock test t2 +# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test create or replace table t1 (i int); select * from information_schema.metadata_lock_info; THREAD_ID LOCK_MODE LOCK_DURATION LOCK_TYPE TABLE_SCHEMA TABLE_NAME # MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Global read lock # MDL_SHARED_NO_READ_WRITE MDL_EXPLICIT Table metadata lock test t1 -# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test # MDL_SHARED_READ MDL_EXPLICIT Table metadata lock test t2 +# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test create or replace table t1 like t2; select * from information_schema.metadata_lock_info; THREAD_ID LOCK_MODE LOCK_DURATION LOCK_TYPE TABLE_SCHEMA TABLE_NAME # MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Global read lock # MDL_SHARED_NO_READ_WRITE MDL_EXPLICIT Table metadata lock test t1 -# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test # MDL_SHARED_READ MDL_EXPLICIT Table metadata lock test t2 +# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test create or replace table t1 select 1 as f1; select * from information_schema.metadata_lock_info; THREAD_ID LOCK_MODE LOCK_DURATION LOCK_TYPE TABLE_SCHEMA TABLE_NAME # MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Global read lock # MDL_SHARED_NO_READ_WRITE MDL_EXPLICIT Table metadata lock test t1 -# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test # MDL_SHARED_READ MDL_EXPLICIT Table metadata lock test t2 +# MDL_INTENTION_EXCLUSIVE MDL_EXPLICIT Schema metadata lock test drop table t1; unlock tables; # diff --git a/sql/mdl.cc b/sql/mdl.cc index 85936ca461b..fcb03d61fab 100644 --- a/sql/mdl.cc +++ b/sql/mdl.cc @@ -17,7 +17,7 @@ #include "sql_class.h" #include "debug_sync.h" #include "sql_array.h" -#include <hash.h> +#include <lf.h> #include <mysqld_error.h> #include <mysql/plugin.h> #include <mysql/service_thd_wait.h> @@ -26,12 +26,10 @@ #include "wsrep_thd.h" #ifdef HAVE_PSI_INTERFACE -static PSI_mutex_key key_MDL_map_mutex; static PSI_mutex_key key_MDL_wait_LOCK_wait_status; static PSI_mutex_info all_mdl_mutexes[]= { - { &key_MDL_map_mutex, "MDL_map::mutex", 0}, { &key_MDL_wait_LOCK_wait_status, "MDL_wait::LOCK_wait_status", 0} }; @@ -121,15 +119,12 @@ class MDL_map public: void init(); void destroy(); - MDL_lock *find_or_insert(const MDL_key *key); - unsigned long get_lock_owner(const MDL_key *key); - void remove(MDL_lock *lock); + MDL_lock *find_or_insert(LF_PINS *pins, const MDL_key *key); + unsigned long get_lock_owner(LF_PINS *pins, const MDL_key *key); + void remove(LF_PINS *pins, MDL_lock *lock); + LF_PINS *get_pins() { return lf_hash_get_pins(&m_locks); } private: - bool move_from_hash_to_lock_mutex(MDL_lock *lock); - /** All acquired locks in the server. */ - HASH m_locks; - /* Protects access to m_locks hash. */ - mysql_mutex_t m_mutex; + LF_HASH m_locks; /**< All acquired locks in the server. */ /** Pre-allocated MDL_lock object for GLOBAL namespace. */ MDL_lock *m_global_lock; /** Pre-allocated MDL_lock object for COMMIT namespace. */ @@ -479,7 +474,8 @@ public: void reschedule_waiters(); - void remove_ticket(Ticket_list MDL_lock::*queue, MDL_ticket *ticket); + void remove_ticket(LF_PINS *pins, Ticket_list MDL_lock::*queue, + MDL_ticket *ticket); bool visit_subgraph(MDL_ticket *waiting_ticket, MDL_wait_for_graph_visitor *gvisitor); @@ -520,36 +516,44 @@ public: public: + MDL_lock() + : m_hog_lock_count(0), + m_strategy(0) + { mysql_prlock_init(key_MDL_lock_rwlock, &m_rwlock); } + MDL_lock(const MDL_key *key_arg) : key(key_arg), m_hog_lock_count(0), - m_state(0) + m_strategy(&m_scoped_lock_strategy) { + DBUG_ASSERT(key_arg->mdl_namespace() == MDL_key::GLOBAL || + key_arg->mdl_namespace() == MDL_key::COMMIT); mysql_prlock_init(key_MDL_lock_rwlock, &m_rwlock); - switch (key_arg->mdl_namespace()) - { - case MDL_key::GLOBAL: - case MDL_key::SCHEMA: - case MDL_key::COMMIT: - m_strategy= &m_scoped_lock_strategy; - break; - default: - m_strategy= &m_object_lock_strategy; - } } - virtual ~MDL_lock() + ~MDL_lock() + { mysql_prlock_destroy(&m_rwlock); } + + static void lf_alloc_constructor(uchar *arg) + { new (arg + LF_HASH_OVERHEAD) MDL_lock(); } + + static void lf_alloc_destructor(uchar *arg) + { ((MDL_lock*)(arg + LF_HASH_OVERHEAD))->~MDL_lock(); } + + static void lf_hash_initializer(LF_HASH *hash __attribute__((unused)), + MDL_lock *lock, MDL_key *key_arg) { - mysql_prlock_destroy(&m_rwlock); + DBUG_ASSERT(key_arg->mdl_namespace() != MDL_key::GLOBAL && + key_arg->mdl_namespace() != MDL_key::COMMIT); + new (&lock->key) MDL_key(key_arg); + if (key_arg->mdl_namespace() == MDL_key::SCHEMA) + lock->m_strategy= &m_scoped_lock_strategy; + else + lock->m_strategy= &m_object_lock_strategy; } - /** - Lock state: first 31 bits are reference counter, 32-nd bit is deleted flag. - */ - static const int32 DELETED= 1 << 31; - int32 m_state; -private: const MDL_lock_strategy *m_strategy; +private: static const MDL_scoped_lock m_scoped_lock_strategy; static const MDL_object_lock m_object_lock_strategy; }; @@ -616,65 +620,45 @@ void mdl_destroy() } -static inline int mdl_iterate_lock(MDL_lock *lock, - int (*callback)(MDL_ticket *ticket, void *arg), - void *arg) +struct mdl_iterate_arg +{ + int (*callback)(MDL_ticket *ticket, void *arg); + void *argument; +}; + + +static my_bool mdl_iterate_lock(MDL_lock *lock, mdl_iterate_arg *arg) { + int res= FALSE; + /* + We can skip check for m_strategy here, becase m_granted + must be empty for such locks anyway. + */ + mysql_prlock_rdlock(&lock->m_rwlock); MDL_lock::Ticket_iterator ticket_it(lock->m_granted); MDL_ticket *ticket; - int res= 0; - mysql_prlock_rdlock(&lock->m_rwlock); - while ((ticket= ticket_it++) && !(res= callback(ticket, arg))) /* no-op */; + while ((ticket= ticket_it++) && !(res= arg->callback(ticket, arg->argument))) + /* no-op */; mysql_prlock_unlock(&lock->m_rwlock); - return res; + return MY_TEST(res); } int mdl_iterate(int (*callback)(MDL_ticket *ticket, void *arg), void *arg) { - DYNAMIC_ARRAY locks; - uint i; - int res; DBUG_ENTER("mdl_iterate"); + mdl_iterate_arg argument= { callback, arg }; + LF_PINS *pins= mdl_locks.get_pins(); + int res= 1; - if ((res= mdl_iterate_lock(mdl_locks.m_global_lock, callback, arg)) || - (res= mdl_iterate_lock(mdl_locks.m_commit_lock, callback, arg))) - DBUG_RETURN(res); - - my_init_dynamic_array(&locks, sizeof(MDL_lock*), 512, 1, MYF(0)); - - /* Collect all locks first */ - mysql_mutex_lock(&mdl_locks.m_mutex); - if (allocate_dynamic(&locks, mdl_locks.m_locks.records)) - { - res= 1; - mysql_mutex_unlock(&mdl_locks.m_mutex); - goto end; - } - for (i= 0; i < mdl_locks.m_locks.records; i++) + if (pins) { - MDL_lock *lock= (MDL_lock*) my_hash_element(&mdl_locks.m_locks, i); - my_atomic_add32_explicit(&lock->m_state, 1, MY_MEMORY_ORDER_RELAXED); - insert_dynamic(&locks, &lock); + res= mdl_iterate_lock(mdl_locks.m_global_lock, &argument) || + mdl_iterate_lock(mdl_locks.m_commit_lock, &argument) || + lf_hash_iterate(&mdl_locks.m_locks, pins, + (my_hash_walk_action) mdl_iterate_lock, &argument); + lf_hash_put_pins(pins); } - mysql_mutex_unlock(&mdl_locks.m_mutex); - - /* Now show them */ - for (i= 0; i < locks.elements; i++) - { - MDL_lock *lock= (MDL_lock*) *dynamic_element(&locks, i, MDL_lock**); - res|= mdl_iterate_lock(lock, callback, arg); - - mysql_prlock_wrlock(&lock->m_rwlock); - int32 old_state= my_atomic_add32_explicit(&lock->m_state, -1, - MY_MEMORY_ORDER_RELAXED); - mysql_prlock_unlock(&lock->m_rwlock); - - if (unlikely(old_state == MDL_lock::DELETED + 1)) - delete lock; - } -end: - delete_dynamic(&locks); DBUG_RETURN(res); } @@ -697,9 +681,12 @@ void MDL_map::init() m_global_lock= new (std::nothrow) MDL_lock(&global_lock_key); m_commit_lock= new (std::nothrow) MDL_lock(&commit_lock_key); - mysql_mutex_init(key_MDL_map_mutex, &m_mutex, NULL); - my_hash_init2(&m_locks, 0, &my_charset_bin, 16 /* FIXME */, 0, 0, - mdl_locks_key, mdl_hash_function, 0, 0); + lf_hash_init(&m_locks, sizeof(MDL_lock), LF_HASH_UNIQUE, 0, 0, + mdl_locks_key, &my_charset_bin); + m_locks.alloc.constructor= MDL_lock::lf_alloc_constructor; + m_locks.alloc.destructor= MDL_lock::lf_alloc_destructor; + m_locks.initializer= (lf_hash_initializer) MDL_lock::lf_hash_initializer; + m_locks.hash_function= mdl_hash_function; } @@ -713,9 +700,8 @@ void MDL_map::destroy() delete m_global_lock; delete m_commit_lock; - DBUG_ASSERT(!m_locks.records); - mysql_mutex_destroy(&m_mutex); - my_hash_free(&m_locks); + DBUG_ASSERT(!my_atomic_load32(&m_locks.count)); + lf_hash_destroy(&m_locks); } @@ -728,7 +714,7 @@ void MDL_map::destroy() @retval NULL - Failure (OOM). */ -MDL_lock* MDL_map::find_or_insert(const MDL_key *mdl_key) +MDL_lock* MDL_map::find_or_insert(LF_PINS *pins, const MDL_key *mdl_key) { MDL_lock *lock; @@ -755,64 +741,21 @@ MDL_lock* MDL_map::find_or_insert(const MDL_key *mdl_key) } retry: - mysql_mutex_lock(&m_mutex); - if (!(lock= (MDL_lock*) my_hash_search_using_hash_value(&m_locks, - mdl_key->hash_value(), - mdl_key->ptr(), - mdl_key->length()))) - { - /* No lock object found so we need to create a new one. */ - lock= new (std::nothrow) MDL_lock(mdl_key); - if (!lock || my_hash_insert(&m_locks, (uchar*)lock)) - { - delete lock; - mysql_mutex_unlock(&m_mutex); + while (!(lock= (MDL_lock*) lf_hash_search_using_hash_value(&m_locks, pins, + mdl_key->hash_value(), mdl_key->ptr(), mdl_key->length()))) + if (lf_hash_insert(&m_locks, pins, (uchar*) mdl_key) == -1) return NULL; - } - } - - if (move_from_hash_to_lock_mutex(lock)) - goto retry; - - return lock; -} - - -/** - Release MDL_map::m_mutex mutex and lock MDL_lock::m_rwlock for lock - object from the hash. Handle situation when object was released - while we held no locks. - - @retval FALSE - Success. - @retval TRUE - Object was released while we held no mutex, caller - should re-try looking up MDL_lock object in the hash. -*/ - -bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock) -{ - DBUG_ASSERT(!(my_atomic_load32(&lock->m_state) & MDL_lock::DELETED)); - mysql_mutex_assert_owner(&m_mutex); - - my_atomic_add32_explicit(&lock->m_state, 1, MY_MEMORY_ORDER_RELAXED); - mysql_mutex_unlock(&m_mutex); mysql_prlock_wrlock(&lock->m_rwlock); - int32 old_state= my_atomic_add32_explicit(&lock->m_state, -1, - MY_MEMORY_ORDER_RELAXED); - if (unlikely(old_state & MDL_lock::DELETED)) + if (unlikely(!lock->m_strategy)) { - /* - Object was released while we held no locks, we need to - release it if no others hold references to it, while our own - reference count ensured that the object as such haven't got - its memory released yet. - */ mysql_prlock_unlock(&lock->m_rwlock); - if (old_state == MDL_lock::DELETED + 1) - delete lock; - return TRUE; + lf_hash_search_unpin(pins); + goto retry; } - return FALSE; + lf_hash_search_unpin(pins); + + return lock; } @@ -821,7 +764,7 @@ bool MDL_map::move_from_hash_to_lock_mutex(MDL_lock *lock) */ unsigned long -MDL_map::get_lock_owner(const MDL_key *mdl_key) +MDL_map::get_lock_owner(LF_PINS *pins, const MDL_key *mdl_key) { MDL_lock *lock; unsigned long res= 0; @@ -837,14 +780,21 @@ MDL_map::get_lock_owner(const MDL_key *mdl_key) } else { - mysql_mutex_lock(&m_mutex); - lock= (MDL_lock*) my_hash_search_using_hash_value(&m_locks, - mdl_key->hash_value(), - mdl_key->ptr(), - mdl_key->length()); + lock= (MDL_lock*) lf_hash_search_using_hash_value(&m_locks, pins, + mdl_key->hash_value(), + mdl_key->ptr(), + mdl_key->length()); if (lock) + { + /* + We can skip check for m_strategy here, becase m_granted + must be empty for such locks anyway. + */ + mysql_prlock_rdlock(&lock->m_rwlock); res= lock->get_lock_owner(); - mysql_mutex_unlock(&m_mutex); + mysql_prlock_unlock(&lock->m_rwlock); + lf_hash_search_unpin(pins); + } } return res; } @@ -856,7 +806,7 @@ MDL_map::get_lock_owner(const MDL_key *mdl_key) it. */ -void MDL_map::remove(MDL_lock *lock) +void MDL_map::remove(LF_PINS *pins, MDL_lock *lock) { if (lock->key.mdl_namespace() == MDL_key::GLOBAL || lock->key.mdl_namespace() == MDL_key::COMMIT) @@ -869,20 +819,9 @@ void MDL_map::remove(MDL_lock *lock) return; } - mysql_mutex_lock(&m_mutex); - my_hash_delete(&m_locks, (uchar*) lock); - - /* - Destroy the MDL_lock object, but ensure that anyone that is - holding a reference to the object is not remaining, if so he - has the responsibility to release it. - */ - int32 old_state= my_atomic_add32_explicit(&lock->m_state, MDL_lock::DELETED, - MY_MEMORY_ORDER_RELAXED); - mysql_mutex_unlock(&m_mutex); + lock->m_strategy= 0; mysql_prlock_unlock(&lock->m_rwlock); - if (!old_state) - delete lock; + lf_hash_delete(&m_locks, pins, lock->key.ptr(), lock->key.length()); } @@ -896,7 +835,8 @@ MDL_context::MDL_context() : m_owner(NULL), m_needs_thr_lock_abort(FALSE), - m_waiting_for(NULL) + m_waiting_for(NULL), + m_pins(NULL) { mysql_prlock_init(key_MDL_context_LOCK_waiting_for, &m_LOCK_waiting_for); } @@ -921,6 +861,14 @@ void MDL_context::destroy() DBUG_ASSERT(m_tickets[MDL_EXPLICIT].is_empty()); mysql_prlock_destroy(&m_LOCK_waiting_for); + if (m_pins) + lf_hash_put_pins(m_pins); +} + + +bool MDL_context::fix_pins() +{ + return m_pins ? false : (m_pins= mdl_locks.get_pins()) == 0; } @@ -1660,12 +1608,13 @@ MDL_lock::get_lock_owner() const /** Remove a ticket from waiting or pending queue and wakeup up waiters. */ -void MDL_lock::remove_ticket(Ticket_list MDL_lock::*list, MDL_ticket *ticket) +void MDL_lock::remove_ticket(LF_PINS *pins, Ticket_list MDL_lock::*list, + MDL_ticket *ticket) { mysql_prlock_wrlock(&m_rwlock); (this->*list).remove_ticket(ticket); if (is_empty()) - mdl_locks.remove(this); + mdl_locks.remove(pins, this); else { /* @@ -1913,6 +1862,9 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, return FALSE; } + if (fix_pins()) + return TRUE; + if (!(ticket= MDL_ticket::create(this, mdl_request->type #ifndef DBUG_OFF , mdl_request->duration @@ -1921,7 +1873,7 @@ MDL_context::try_acquire_lock_impl(MDL_request *mdl_request, return TRUE; /* The below call implicitly locks MDL_lock::m_rwlock on success. */ - if (!(lock= mdl_locks.find_or_insert(key))) + if (!(lock= mdl_locks.find_or_insert(m_pins, key))) { MDL_ticket::destroy(ticket); return TRUE; @@ -1964,6 +1916,17 @@ MDL_context::clone_ticket(MDL_request *mdl_request) { MDL_ticket *ticket; + + /* + Since in theory we can clone ticket belonging to a different context + we need to prepare target context for possible attempts to release + lock and thus possible removal of MDL_lock from MDL_map container. + So we allocate pins to be able to work with this container if they + are not allocated already. + */ + if (fix_pins()) + return TRUE; + /* By submitting mdl_request->type to MDL_ticket::create() we effectively downgrade the cloned lock to the level of @@ -2103,7 +2066,7 @@ MDL_context::acquire_lock(MDL_request *mdl_request, double lock_wait_timeout) if (wait_status != MDL_wait::GRANTED) { - lock->remove_ticket(&MDL_lock::m_waiting, ticket); + lock->remove_ticket(m_pins, &MDL_lock::m_waiting, ticket); MDL_ticket::destroy(ticket); switch (wait_status) { @@ -2560,7 +2523,7 @@ void MDL_context::release_lock(enum_mdl_duration duration, MDL_ticket *ticket) DBUG_ASSERT(this == ticket->get_ctx()); - lock->remove_ticket(&MDL_lock::m_granted, ticket); + lock->remove_ticket(m_pins, &MDL_lock::m_granted, ticket); m_tickets[duration].remove(ticket); MDL_ticket::destroy(ticket); @@ -2721,7 +2684,8 @@ MDL_context::is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace, unsigned long MDL_context::get_lock_owner(MDL_key *key) { - return mdl_locks.get_lock_owner(key); + fix_pins(); + return mdl_locks.get_lock_owner(m_pins, key); } diff --git a/sql/mdl.h b/sql/mdl.h index ee1c418eb74..f7a5d160e53 100644 --- a/sql/mdl.h +++ b/sql/mdl.h @@ -28,7 +28,7 @@ #include <my_sys.h> #include <m_string.h> #include <mysql_com.h> -#include <hash.h> +#include <lf.h> #include <algorithm> @@ -917,6 +917,7 @@ private: readily available to the wait-for graph iterator. */ MDL_wait_for_subgraph *m_waiting_for; + LF_PINS *m_pins; private: MDL_ticket *find_ticket(MDL_request *mdl_req, enum_mdl_duration *duration); @@ -924,6 +925,7 @@ private: void release_lock(enum_mdl_duration duration, MDL_ticket *ticket); bool try_acquire_lock_impl(MDL_request *mdl_request, MDL_ticket **out_ticket); + bool fix_pins(); public: THD *get_thd() const { return m_owner->get_thd(); } |