summaryrefslogtreecommitdiff
path: root/sql/mdl.h
diff options
context:
space:
mode:
Diffstat (limited to 'sql/mdl.h')
-rw-r--r--sql/mdl.h384
1 files changed, 301 insertions, 83 deletions
diff --git a/sql/mdl.h b/sql/mdl.h
index 3ae7cdc743d..848c446496f 100644
--- a/sql/mdl.h
+++ b/sql/mdl.h
@@ -26,28 +26,121 @@ class THD;
class MDL_context;
class MDL_lock;
class MDL_ticket;
+class Deadlock_detection_context;
/**
Type of metadata lock request.
- - High-priority shared locks differ from ordinary shared locks by
- that they ignore pending requests for exclusive locks.
- - Upgradable shared locks can be later upgraded to exclusive
- (because of that their acquisition involves implicit
- acquisition of global intention-exclusive lock).
-
@sa Comments for MDL_object_lock::can_grant_lock() and
- MDL_global_lock::can_grant_lock() for details.
+ MDL_global_lock::can_grant_lock() for details.
*/
-enum enum_mdl_type {MDL_SHARED=0, MDL_SHARED_HIGH_PRIO,
- MDL_SHARED_UPGRADABLE, MDL_INTENTION_EXCLUSIVE,
- MDL_EXCLUSIVE};
-
-
-/** States which a metadata lock ticket can have. */
+enum enum_mdl_type {
+ /*
+ An intention exclusive metadata lock. Used only for global locks.
+ Owner of this type of lock can acquire upgradable exclusive locks on
+ individual objects.
+ Compatible with other IX locks, but is incompatible with global S lock.
+ */
+ MDL_INTENTION_EXCLUSIVE= 0,
+ /*
+ A shared metadata lock.
+ To be used in cases when we are interested in object metadata only
+ and there is no intention to access object data (e.g. for stored
+ routines or during preparing prepared statements).
+ We also mis-use this type of lock for open HANDLERs, since lock
+ acquired by this statement has to be compatible with lock acquired
+ by LOCK TABLES ... WRITE statement, i.e. SNRW (We can't get by by
+ acquiring S lock at HANDLER ... OPEN time and upgrading it to SR
+ lock for HANDLER ... READ as it doesn't solve problem with need
+ to abort DML statements which wait on table level lock while having
+ open HANDLER in the same connection).
+ To avoid deadlock which may occur when SNRW lock is being upgraded to
+ X lock for table on which there is an active S lock which is owned by
+ thread which waits in its turn for table-level lock owned by thread
+ performing upgrade we have to use thr_abort_locks_for_thread()
+ facility in such situation.
+ This problem does not arise for locks on stored routines as we don't
+ use SNRW locks for them. It also does not arise when S locks are used
+ during PREPARE calls as table-level locks are not acquired in this
+ case.
+ */
+ MDL_SHARED,
+ /*
+ A high priority shared metadata lock.
+ Used for cases when there is no intention to access object data (i.e.
+ data in the table).
+ "High priority" means that, unlike other shared locks, it is granted
+ ignoring pending requests for exclusive locks. Intended for use in
+ cases when we only need to access metadata and not data, e.g. when
+ filling an INFORMATION_SCHEMA table.
+ Since SH lock is compatible with SNRW lock, the connection that
+ holds SH lock lock should not try to acquire any kind of table-level
+ or row-level lock, as this can lead to a deadlock. Moreover, after
+ acquiring SH lock, the connection should not wait for any other
+ resource, as it might cause starvation for X locks and a potential
+ deadlock during upgrade of SNW or SNRW to X lock (e.g. if the
+ upgrading connection holds the resource that is being waited for).
+ */
+ MDL_SHARED_HIGH_PRIO,
+ /*
+ A shared metadata lock for cases when there is an intention to read data
+ from table.
+ A connection holding this kind of lock can read table metadata and read
+ table data (after acquiring appropriate table and row-level locks).
+ This means that one can only acquire TL_READ, TL_READ_NO_INSERT, and
+ similar table-level locks on table if one holds SR MDL lock on it.
+ To be used for tables in SELECTs, subqueries, and LOCK TABLE ... READ
+ statements.
+ */
+ MDL_SHARED_READ,
+ /*
+ A shared metadata lock for cases when there is an intention to modify
+ (and not just read) data in the table.
+ A connection holding SW lock can read table metadata and modify or read
+ table data (after acquiring appropriate table and row-level locks).
+ To be used for tables to be modified by INSERT, UPDATE, DELETE
+ statements, but not LOCK TABLE ... WRITE or DDL). Also taken by
+ SELECT ... FOR UPDATE.
+ */
+ MDL_SHARED_WRITE,
+ /*
+ An upgradable shared metadata lock which blocks all attempts to update
+ table data, allowing reads.
+ A connection holding this kind of lock can read table metadata and read
+ table data.
+ Can be upgraded to X metadata lock.
+ Note, that since this type of lock is not compatible with SNRW or SW
+ lock types, acquiring appropriate engine-level locks for reading
+ (TL_READ* for MyISAM, shared row locks in InnoDB) should be
+ contention-free.
+ To be used for the first phase of ALTER TABLE, when copying data between
+ tables, to allow concurrent SELECTs from the table, but not UPDATEs.
+ */
+ MDL_SHARED_NO_WRITE,
+ /*
+ An upgradable shared metadata lock which allows other connections
+ to access table metadata, but not data.
+ It blocks all attempts to read or update table data, while allowing
+ INFORMATION_SCHEMA and SHOW queries.
+ A connection holding this kind of lock can read table metadata modify and
+ read table data.
+ Can be upgraded to X metadata lock.
+ To be used for LOCK TABLES WRITE statement.
+ Not compatible with any other lock type except S and SH.
+ */
+ MDL_SHARED_NO_READ_WRITE,
+ /*
+ An exclusive metadata lock.
+ A connection holding this lock can modify both table's metadata and data.
+ No other type of metadata lock can be granted while this lock is held.
+ To be used for CREATE/DROP/RENAME TABLE statements and for execution of
+ certain phases of other DDL statements.
+ */
+ MDL_EXCLUSIVE,
+ /* This should be the last !!! */
+ MDL_TYPE_END};
-enum enum_mdl_state { MDL_PENDING, MDL_ACQUIRED };
/** Maximal length of key for metadata locking subsystem. */
#define MAX_MDLKEY_LENGTH (1 + NAME_LEN + 1 + NAME_LEN + 1)
@@ -77,11 +170,11 @@ public:
it's necessary to have a separate namespace for them since
MDL_key is also used outside of the MDL subsystem.
*/
- enum enum_mdl_namespace { TABLE=0,
+ enum enum_mdl_namespace { GLOBAL=0,
+ TABLE,
FUNCTION,
PROCEDURE,
- TRIGGER,
- GLOBAL };
+ TRIGGER };
const uchar *ptr() const { return (uchar*) m_ptr; }
uint length() const { return m_length; }
@@ -217,7 +310,7 @@ public:
DBUG_ASSERT(ticket == NULL);
type= type_arg;
}
- bool is_shared() const { return type < MDL_INTENTION_EXCLUSIVE; }
+ uint get_deadlock_weight() const;
static MDL_request *create(MDL_key::enum_mdl_namespace mdl_namespace,
const char *db, const char *name,
@@ -297,21 +390,25 @@ public:
void set_cached_object(void *cached_object,
mdl_cached_object_release_hook release_hook);
MDL_context *get_ctx() const { return m_ctx; }
- bool is_shared() const { return m_type < MDL_INTENTION_EXCLUSIVE; }
bool is_upgradable_or_exclusive() const
{
- return m_type == MDL_SHARED_UPGRADABLE || m_type == MDL_EXCLUSIVE;
+ return m_type == MDL_SHARED_NO_WRITE ||
+ m_type == MDL_SHARED_NO_READ_WRITE ||
+ m_type == MDL_EXCLUSIVE;
}
- bool upgrade_shared_lock_to_exclusive();
- void downgrade_exclusive_lock();
+ enum_mdl_type get_type() const { return m_type; }
+ void downgrade_exclusive_lock(enum_mdl_type type);
+
+ bool has_stronger_or_equal_type(enum_mdl_type type) const;
+
+ bool is_incompatible_when_granted(enum_mdl_type type) const;
+ bool is_incompatible_when_waiting(enum_mdl_type type) const;
+
private:
friend class MDL_context;
- friend class MDL_global_lock;
- friend class MDL_object_lock;
MDL_ticket(MDL_context *ctx_arg, enum_mdl_type type_arg)
: m_type(type_arg),
- m_state(MDL_PENDING),
m_ctx(ctx_arg),
m_lock(NULL)
{}
@@ -321,9 +418,6 @@ private:
private:
/** Type of metadata lock. Externally accessible. */
enum enum_mdl_type m_type;
- /** State of the metadata lock ticket. Context private. */
- enum enum_mdl_state m_state;
-
/**
Context of the owner of the metadata lock ticket. Externally accessible.
*/
@@ -331,6 +425,7 @@ private:
/** Pointer to the lock object for this lock ticket. Context private. */
MDL_lock *m_lock;
+
private:
MDL_ticket(const MDL_ticket &); /* not implemented */
MDL_ticket &operator=(const MDL_ticket &); /* not implemented */
@@ -359,26 +454,29 @@ public:
typedef Ticket_list::Iterator Ticket_iterator;
+ enum mdl_signal_type { NO_WAKE_UP = 0,
+ NORMAL_WAKE_UP,
+ VICTIM_WAKE_UP,
+ TIMEOUT_WAKE_UP };
+
MDL_context();
void destroy();
- bool try_acquire_shared_lock(MDL_request *mdl_request);
- bool acquire_exclusive_lock(MDL_request *mdl_request);
- bool acquire_exclusive_locks(MDL_request_list *requests);
- bool try_acquire_exclusive_lock(MDL_request *mdl_request);
+ bool try_acquire_lock(MDL_request *mdl_request);
+ bool acquire_lock(MDL_request *mdl_request);
+ bool acquire_locks(MDL_request_list *requests);
+ bool upgrade_shared_lock_to_exclusive(MDL_ticket *mdl_ticket);
+
bool clone_ticket(MDL_request *mdl_request);
- bool wait_for_locks(MDL_request_list *requests);
+ bool wait_for_lock(MDL_request *mdl_request);
void release_all_locks_for_name(MDL_ticket *ticket);
void release_lock(MDL_ticket *ticket);
- bool is_exclusive_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
- const char *db,
- const char *name);
bool is_lock_owner(MDL_key::enum_mdl_namespace mdl_namespace,
- const char *db, const char *name);
-
+ const char *db, const char *name,
+ enum_mdl_type mdl_type);
bool has_lock(MDL_ticket *mdl_savepoint, MDL_ticket *mdl_ticket);
@@ -391,88 +489,208 @@ public:
{
/*
NULL savepoint represents the start of the transaction.
- Checking for m_lt_or_ha_sentinel also makes sure we never
+ Checking for m_trans_sentinel also makes sure we never
return a pointer to HANDLER ticket as a savepoint.
*/
- return m_tickets.front() == m_lt_or_ha_sentinel ? NULL : m_tickets.front();
+ return m_tickets.front() == m_trans_sentinel ? NULL : m_tickets.front();
}
- void set_lt_or_ha_sentinel()
+ void set_trans_sentinel()
{
- m_lt_or_ha_sentinel= mdl_savepoint();
+ m_trans_sentinel= mdl_savepoint();
}
- MDL_ticket *lt_or_ha_sentinel() const { return m_lt_or_ha_sentinel; }
+ MDL_ticket *trans_sentinel() const { return m_trans_sentinel; }
- void clear_lt_or_ha_sentinel()
+ void reset_trans_sentinel(MDL_ticket *sentinel_arg)
{
- m_lt_or_ha_sentinel= NULL;
+ m_trans_sentinel= sentinel_arg;
}
- void move_ticket_after_lt_or_ha_sentinel(MDL_ticket *mdl_ticket);
+ void move_ticket_after_trans_sentinel(MDL_ticket *mdl_ticket);
void release_transactional_locks();
void rollback_to_savepoint(MDL_ticket *mdl_savepoint);
- bool can_wait_lead_to_deadlock() const;
-
inline THD *get_thd() const { return m_thd; }
+ bool acquire_global_intention_exclusive_lock(MDL_request *mdl_request);
+
/**
Wake up context which is waiting for a change of MDL_lock state.
*/
- void awake()
+ void awake(mdl_signal_type signal)
{
- pthread_cond_signal(&m_ctx_wakeup_cond);
+ pthread_mutex_lock(&m_signal_lock);
+ m_signal= signal;
+ pthread_cond_signal(&m_signal_cond);
+ pthread_mutex_unlock(&m_signal_lock);
}
- bool try_acquire_global_intention_exclusive_lock(MDL_request *mdl_request);
- bool acquire_global_intention_exclusive_lock(MDL_request *mdl_request);
-
- bool acquire_global_shared_lock();
- void release_global_shared_lock();
+ void init(THD *thd_arg) { m_thd= thd_arg; }
- /**
- Check if this context owns global lock of particular type.
- */
- bool is_global_lock_owner(enum_mdl_type type_arg)
+ void set_needs_thr_lock_abort(bool needs_thr_lock_abort)
{
- MDL_request mdl_request;
- bool not_used;
- mdl_request.init(MDL_key::GLOBAL, "", "", type_arg);
- return find_ticket(&mdl_request, &not_used);
+ /*
+ @note In theory, this member should be modified under protection
+ of some lock since it can be accessed from different threads.
+ In practice, this is not necessary as code which reads this
+ value and so might miss the fact that value was changed will
+ always re-try reading it after small timeout and therefore
+ will see the new value eventually.
+ */
+ m_needs_thr_lock_abort= TRUE;
+ }
+ bool get_needs_thr_lock_abort() const
+ {
+ return m_needs_thr_lock_abort;
}
- void init(THD *thd_arg) { m_thd= thd_arg; }
+ bool find_deadlock(Deadlock_detection_context *deadlock_ctx);
private:
+ /**
+ All MDL tickets acquired by this connection.
+
+ The order of tickets in m_tickets list.
+ ---------------------------------------
+ The entire set of locks acquired by a connection
+ can be separated in two subsets: transactional and
+ non-transactional locks.
+
+ Transactional locks are locks with automatic scope. They
+ are accumulated in the course of a transaction, and
+ released only on COMMIT, ROLLBACK or ROLLBACK TO SAVEPOINT.
+ They must not be (and never are) released manually,
+ i.e. with release_lock() call.
+
+ Non-transactional locks are taken for locks that span
+ multiple transactions or savepoints.
+ These are: HANDLER SQL locks (HANDLER SQL is
+ transaction-agnostic), LOCK TABLES locks (you can COMMIT/etc
+ under LOCK TABLES, and the locked tables stay locked), and
+ SET GLOBAL READ_ONLY=1 global shared lock.
+
+ Transactional locks are always prepended to the beginning
+ of the list. In other words, they are stored in reverse
+ temporal order. Thus, when we rollback to a savepoint,
+ we start popping and releasing tickets from the front
+ until we reach the last ticket acquired after the
+ savepoint.
+
+ Non-transactional locks are always stored after
+ transactional ones, and among each other can be
+ split into three sets:
+
+ [LOCK TABLES locks] [HANDLER locks] [GLOBAL READ LOCK locks]
+
+ The following is known about these sets:
+
+ * we can never have both HANDLER and LOCK TABLES locks
+ together -- HANDLER statements are prohibited under LOCK
+ TABLES, entering LOCK TABLES implicitly closes all open
+ HANDLERs.
+ * GLOBAL READ LOCK locks are always stored after LOCK TABLES
+ locks and after HANDLER locks. This is because one can't say
+ SET GLOBAL read_only=1 or FLUSH TABLES WITH READ LOCK
+ if one has locked tables. One can, however, LOCK TABLES
+ after having entered the read only mode. Note, that
+ subsequent LOCK TABLES statement will unlock the previous
+ set of tables, but not the GRL!
+ There are no HANDLER locks after GRL locks because
+ SET GLOBAL read_only performs a FLUSH TABLES WITH
+ READ LOCK internally, and FLUSH TABLES, in turn, implicitly
+ closes all open HANDLERs.
+ However, one can open a few HANDLERs after entering the
+ read only mode.
+ */
Ticket_list m_tickets;
/**
- This member has two uses:
- 1) When entering LOCK TABLES mode, remember the last taken
- metadata lock. COMMIT/ROLLBACK must preserve these metadata
- locks.
- 2) When we have an open HANDLER tables, store the position
- in the list beyond which we keep locks for HANDLER tables.
- COMMIT/ROLLBACK must, again, preserve HANDLER metadata locks.
+ Separates transactional and non-transactional locks
+ in m_tickets list, @sa m_tickets.
*/
- MDL_ticket *m_lt_or_ha_sentinel;
+ MDL_ticket *m_trans_sentinel;
THD *m_thd;
/**
+ TRUE - if for this context we will break protocol and try to
+ acquire table-level locks while having only S lock on
+ some table.
+ To avoid deadlocks which might occur during concurrent
+ upgrade of SNRW lock on such object to X lock we have to
+ abort waits for table-level locks for such connections.
+ FALSE - Otherwise.
+ */
+ bool m_needs_thr_lock_abort;
+
+ /**
+ Read-write lock protecting m_waiting_for member.
+
+ TODO/FIXME: Replace with RW-lock which will prefer readers
+ on all platforms and not only on Linux.
+ */
+ rw_lock_t m_waiting_for_lock;
+ MDL_ticket *m_waiting_for;
+ uint m_deadlock_weight;
+ /**
Condvar which is used for waiting until this context's pending
request can be satisfied or this thread has to perform actions
- to resolve potential deadlock (we subscribe for such notification
- by adding ticket corresponding to the request to an appropriate
- queue of waiters).
+ to resolve a potential deadlock (we subscribe to such
+ notification by adding a ticket corresponding to the request
+ to an appropriate queue of waiters).
*/
- pthread_cond_t m_ctx_wakeup_cond;
+ pthread_mutex_t m_signal_lock;
+ pthread_cond_t m_signal_cond;
+ mdl_signal_type m_signal;
+
private:
MDL_ticket *find_ticket(MDL_request *mdl_req,
- bool *is_lt_or_ha);
+ bool *is_transactional);
void release_locks_stored_before(MDL_ticket *sentinel);
-
- bool try_acquire_lock_impl(MDL_request *mdl_request);
bool acquire_lock_impl(MDL_request *mdl_request);
- bool acquire_exclusive_lock_impl(MDL_request *mdl_request);
- friend bool MDL_ticket::upgrade_shared_lock_to_exclusive();
+ bool find_deadlock();
+
+ void will_wait_for(MDL_ticket *pending_ticket)
+ {
+ rw_wrlock(&m_waiting_for_lock);
+ m_waiting_for= pending_ticket;
+ rw_unlock(&m_waiting_for_lock);
+ }
+
+ void set_deadlock_weight(uint weight)
+ {
+ /*
+ m_deadlock_weight should not be modified while m_waiting_for is
+ non-NULL as in this case this context might participate in deadlock
+ and so m_deadlock_weight can be accessed from other threads.
+ */
+ DBUG_ASSERT(m_waiting_for == NULL);
+ m_deadlock_weight= weight;
+ }
+
+ void stop_waiting()
+ {
+ rw_wrlock(&m_waiting_for_lock);
+ m_waiting_for= NULL;
+ rw_unlock(&m_waiting_for_lock);
+ }
+
+ void wait_reset()
+ {
+ pthread_mutex_lock(&m_signal_lock);
+ m_signal= NO_WAKE_UP;
+ pthread_mutex_unlock(&m_signal_lock);
+ }
+
+ mdl_signal_type wait();
+ mdl_signal_type timed_wait(ulong timeout);
+
+ mdl_signal_type peek_signal()
+ {
+ mdl_signal_type result;
+ pthread_mutex_lock(&m_signal_lock);
+ result= m_signal;
+ pthread_mutex_unlock(&m_signal_lock);
+ return result;
+ }
+
private:
MDL_context(const MDL_context &rhs); /* not implemented */
MDL_context &operator=(MDL_context &rhs); /* not implemented */
@@ -487,9 +705,9 @@ void mdl_destroy();
Functions in the server's kernel used by metadata locking subsystem.
*/
-extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use);
+extern bool mysql_notify_thread_having_shared_lock(THD *thd, THD *in_use,
+ bool needs_thr_lock_abort);
extern void mysql_ha_flush(THD *thd);
-extern void mysql_abort_transactions_with_shared_lock(const MDL_key *mdl_key);
extern "C" const char *set_thd_proc_info(THD *thd, const char *info,
const char *calling_function,
const char *calling_file,