MDL生命周期
/** Duration of metadata lock. 元数据锁的持续时间*/
enum enum_mdl_duration {
/**
Locks with statement duration are automatically released at the end of statement or transaction.
语句或者事务结束(提交或者回滚)时自动释放
*/
MDL_STATEMENT = 0,
/**
Locks with transaction duration are automatically released at the end of transaction.
整个事务提交或回滚后,锁才会被释放
*/
MDL_TRANSACTION,
/**
Locks with explicit duration survive the end of statement and transaction.
They have to be released explicitly by calling MDL_context::release_lock().
锁不会因为语句或事务的结束而自动释放 显式锁 需要手动调用MDL_context::release_lock()
*/
MDL_EXPLICIT,
MDL_DURATION_END
};
MDL命名空间
//元数据锁(MDL)可以作用的不同对象命名空间
enum enum_mdl_namespace {
GLOBAL = 0, //global read lock
BACKUP_LOCK,//防止任何可能导致不一致备份的操作,主要包括大多数 DDL 语句和某些管理命令
TABLESPACE,//
SCHEMA,
TABLE,
FUNCTION,
PROCEDURE,
TRIGGER,
EVENT,
COMMIT,
USER_LEVEL_LOCK,
LOCKING_SERVICE, //插件提供的读写锁
SRID,
ACL_CACHE,
COLUMN_STATISTICS,//直方图统计信息
RESOURCE_GROUPS,
FOREIGN_KEY,
CHECK_CONSTRAINT,//约束
NAMESPACE_END
};
MDL锁类型
enum enum_mdl_type {
/*
An intention exclusive metadata lock. Used only for scoped locks.
When acquiring objects in a schema, we lock the schema with IX
to prevent the schema from being deleted.
Compatible with other IX locks, but is incompatible with scoped S and X locks.
*/
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
such as SHOW CREATE TABLE, SHOE COLUMNS FROM TABLE
*/
MDL_SHARED,
/*
A high priority shared metadata lock.
Used for cases when there is no intention to access object data.
系统表场景
*/
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 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, SELECT ... FOR UPDATE.
(不适用的场景 LOCK TABLE ... WRITE or DDL).
*/
MDL_SHARED_WRITE,
/*
A version of MDL_SHARED_WRITE lock which has lower priority than
MDL_SHARED_READ_ONLY locks. Used by DML statements modifying
tables and using the LOW_PRIORITY clause.
(such as INSERT LOW_PRIORITY、UPDATE LOW_PRIORITY、DELETE LOW_PRIORITY)
*/
MDL_SHARED_WRITE_LOW_PRIO,
/*
An upgradable shared metadata lock which allows concurrent updates and
reads of table data. 允许其他线程并发更新预读取表数据
A connection holding this kind of lock can read table metadata and read table data.
It should not modify data as this lock.
Can be upgraded to SNW, SNRW and X locks. Once SU lock is upgraded to X
or SNRW lock data modification can happen freely.
CREATE TABLE、ALTER TABLE 的第一阶段使用 !!!!!!
*/
MDL_SHARED_UPGRADABLE,
/*
A shared metadata lock for cases when we need to read data from table
and block all concurrent modifications to it (for both data and metadata).
持有该锁的对象会阻塞其他所有的修改(元数据与数据)
LOCK TABLES READ 的第一阶段使用!!!!!!!
*/
MDL_SHARED_READ_ONLY,
/*
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.
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.
LOCK TABLES WRITE 第一阶段使用!!!!!
*/
MDL_SHARED_NO_READ_WRITE,
/*DDL的最后阶段 GET_LOCK RELEASE_LOCK*/
MDL_EXCLUSIVE,
MDL_TYPE_END
};
MDL锁请求状态
/* 锁请求可能存在的状态 */
enum enum_psi_status {
PENDING = 0, //表示锁请求尚未被处理,正在等待获取锁
GRANTED, //表示锁请求已经被成功授予,即线程或事务已经获得了所请求的锁
PRE_ACQUIRE_NOTIFY, //表示锁管理器即将授予锁,在正式授予之前发出的通知状态
POST_RELEASE_NOTIFY //锁已经被释放后发出的通知状态
};
MDL锁等待状态
enum enum_wait_status {
WS_EMPTY = 0, //初始状态
GRANTED, //已授权
VICTIM, //死锁
TIMEOUT, //超时
KILLED //连接断开
};
MDL_context::acquire_lock_local
获取当前节点上的锁
bool MDL_context::acquire_lock_local(MDL_request *mdl_request,
Timeout_type lock_wait_timeout) {
MDL_lock *lock;
MDL_ticket *ticket = nullptr;
struct timespec abs_timeout;
MDL_wait::enum_wait_status wait_status;
/* 设置终止的等待时间 */
set_timespec(&abs_timeout, lock_wait_timeout);
/* 无竞争条件下尝试获取lock*/
if (try_acquire_lock_impl(mdl_request, &ticket)) return true;
if (mdl_request->ticket) {
return false;//获取成功即可返回
}
//尝试获取lock失败 但是我们知道想要获取的lock正在被其他线程使用
lock = ticket->m_lock;
lock->m_waiting.add_ticket(ticket);//将当前的线程想要获取的ticket加入到等待队列中
/*
加入等待队列中 必须保证现在的等待槽为空
如果等待槽不为空,则可能意味着该线程已经在等待其他锁
*/
m_wait.reset_status();
//通知所有与当前锁票冲突的锁持有者
if (lock->needs_notification(ticket)) lock->notify_conflicting_locks(this);
mysql_prlock_unlock(&lock->m_rwlock);
will_wait_for(ticket);//将当前的锁票加入到等待图中 用于死锁检测
bool delayed_find_deadlock = false;
if (lock->key.mdl_namespace() != MDL_key::ACL_CACHE ||
ticket->m_type != MDL_SHARED ||
get_owner()->might_have_commit_order_waiters()) {
find_deadlock();
} else if (has_locks()) {
// Locks in ACL_CACHE namespace always need connection check, so
assert(lock->needs_connection_check());
delayed_find_deadlock = true;
}
if (lock->needs_notification(ticket) || lock->needs_connection_check()) {
struct timespec abs_shortwait;
set_timespec(&abs_shortwait, 1);
wait_status = MDL_wait::WS_EMPTY;
while (cmp_timespec(&abs_shortwait, &abs_timeout) <= 0) {
/* abs_timeout is far away. Wait a short while and notify locks. */
wait_status = m_wait.timed_wait(m_owner, &abs_shortwait, false,
mdl_request->key.get_wait_state_name());
if (wait_status != MDL_wait::WS_EMPTY) {
break;
}
if (lock->needs_connection_check() && !m_owner->is_connected()) {
// 处理的是用户级别的锁,并且检测到客户端已经断开连接时,系统不应无限期等待锁的释放
if (!m_wait.set_status(MDL_wait::KILLED))
wait_status = MDL_wait::KILLED;
break;
}
//通知其他冲突的锁
if (lock->needs_notification(ticket)) {
mysql_prlock_wrlock(&lock->m_rwlock);
lock->notify_conflicting_locks(this);
mysql_prlock_unlock(&lock->m_rwlock);
}
if (delayed_find_deadlock) {//死锁检测
find_deadlock();
delayed_find_deadlock = false;
}
set_timespec(&abs_shortwait, 1);//更新时间
}
if (wait_status == MDL_wait::WS_EMPTY)
wait_status = m_wait.timed_wait(m_owner, &abs_timeout, true,
mdl_request->key.get_wait_state_name());
} else {
wait_status = m_wait.timed_wait(m_owner, &abs_timeout, true,
mdl_request->key.get_wait_state_name());
}
done_waiting_for();
//经循环等待后未获取到lock
if (wait_status != MDL_wait::GRANTED) {
lock->remove_ticket(this, m_pins, &MDL_lock::m_waiting, ticket);
if (ticket->m_hton_notified) {
mysql_mdl_set_status(ticket->m_psi, MDL_ticket::POST_RELEASE_NOTIFY);
m_owner->notify_hton_post_release_exclusive(&mdl_request->key);
}
MDL_ticket::destroy(ticket);
switch (wait_status) {//判断是何种原因
case MDL_wait::VICTIM:
my_error(ER_LOCK_DEADLOCK, MYF(0));
break;
case MDL_wait::TIMEOUT:
my_error(ER_LOCK_WAIT_TIMEOUT, MYF(0));
break;
case MDL_wait::KILLED:
if (get_owner()->is_killed() == ER_QUERY_TIMEOUT) {
my_error(ER_QUERY_TIMEOUT, MYF(0));
} else {
my_error(ER_QUERY_INTERRUPTED, MYF(0));
}
break;
default:
assert(0);
break;
}
return true;
}
/*
We have been granted our request.
State of MDL_lock object is already being appropriately updated by a
concurrent thread (@sa MDL_lock:reschedule_waiters()).
So all we need to do is to update MDL_context and MDL_request objects.
*/
assert(wait_status == MDL_wait::GRANTED);
m_ticket_store.push_front(mdl_request->duration, ticket);//管理和存储元数据锁,将当前的锁票存储进去
mdl_request->ticket = ticket;//表示当前锁请求成功获取到lock
//mysql_mdl_set_status(ticket->m_psi, MDL_ticket::GRANTED);
return false;
}
/**
Wait for the status to be assigned to this wait slot.
@param owner MDL context owner.
@param abs_timeout Absolute time after which waiting should stop.
@param set_status_on_timeout true - If in case of timeout waiting
context should close the wait slot by
sending TIMEOUT to itself.
false - Otherwise.
@param wait_state_name Thread state name to be set for duration of wait.
@returns Signal posted.
*/
MDL_wait::enum_wait_status MDL_wait::timed_wait(
MDL_context_owner *owner, struct timespec *abs_timeout,
bool set_status_on_timeout, const PSI_stage_info *wait_state_name) {
PSI_stage_info old_stage;
enum_wait_status result;
int wait_result = 0;
mysql_mutex_lock(&m_LOCK_wait_status);
owner->ENTER_COND(&m_COND_wait_status, &m_LOCK_wait_status, wait_state_name,
&old_stage);
thd_wait_begin(nullptr, THD_WAIT_META_DATA_LOCK);
while (!m_wait_status && !owner->is_killed() && !is_timeout(wait_result)) {
wait_result = mysql_cond_timedwait(&m_COND_wait_status, &m_LOCK_wait_status,
abs_timeout);
}
thd_wait_end(nullptr);
if (m_wait_status == WS_EMPTY) {
/*
Wait has ended not due to a status being set from another
thread but due to this connection/statement being killed or a
time out.
To avoid races, which may occur if another thread sets
GRANTED status before the code which calls this method
processes the abort/timeout, we assign the status under
protection of the m_LOCK_wait_status, within the critical
section. An exception is when set_status_on_timeout is
false, which means that the caller intends to restart the
wait.
*/
if (owner->is_killed())
m_wait_status = KILLED;
else if (set_status_on_timeout)
m_wait_status = TIMEOUT;
}
result = m_wait_status;
mysql_mutex_unlock(&m_LOCK_wait_status);
owner->EXIT_COND(&old_stage);
return result;
}
//存储已经授权的锁票
void MDL_ticket_store::push_front(enum_mdl_duration dur, MDL_ticket *ticket) {
++m_count;//票据计数
m_durations[dur].m_ticket_list.push_front(ticket);//加入到指定持续时间的相应列表中
if (m_count < THRESHOLD) {
return;
}
if (m_count == THRESHOLD) {//超过阈值之后 搞一个map映射提高查找效率
// If this is the first time we cross the threshold, the map must
// be allocated
if (m_map == nullptr) {
m_map.reset(new Ticket_map{INITIAL_BUCKET_COUNT, Hash{}, Key_equal{}});
}
// In any event, it should now be empty
assert(m_map->empty());
/*
When the THRESHOLD value is reached, the unordered map must be
populated with all the tickets added before reaching the
threshold.
*/
for_each_ticket_in_ticket_lists(
[this](MDL_ticket *t, enum_mdl_duration da) {
m_map->emplace(t->get_key(), MDL_ticket_handle{t, da});
});
assert(m_map->size() == m_count);
return;
}
m_map->emplace(ticket->get_key(), MDL_ticket_handle{ticket, dur});
assert(m_map->size() == m_count);
}
MDL_context::try_acquire_lock_impl
尝试获取当前节点上的锁,如果想获取的锁票并未被其他线程持有,可直接获取并返回;如果被其他线程持有,则获取该票据信息,随后进入acquire_lock_local的超时等待逻辑
bool MDL_context::try_acquire_lock_impl(MDL_request *mdl_request,
MDL_ticket **out_ticket) {
MDL_lock *lock;
MDL_key *key = &mdl_request->key;
MDL_ticket *ticket;
enum_mdl_duration found_duration;
MDL_lock::fast_path_state_t unobtrusive_lock_increment;
bool force_slow;
bool pinned;
assert(mdl_request->ticket == nullptr);
/* Don't take chances in production. */
mdl_request->ticket = nullptr;
mysql_mutex_assert_not_owner(&LOCK_open);
/*
检查当前上下文是否已经持有一个与请求锁兼容的锁票据 存在相同的或者已经存在更强的
*/
if ((ticket = find_ticket(mdl_request, &found_duration))) {
assert(ticket->m_lock);//确保找到的锁对象关联了lock信息
//确保找到的锁对象的兼容性 比当前请求锁相等或者更大
assert(ticket->has_stronger_or_equal_type(mdl_request->type));
/*
If the request is for a transactional lock, and we found
a transactional lock, just reuse the found ticket. //可以重用已经加过的锁 比如在一个事务内 多次的DML
It's possible that we found a transactional lock,
but the request is for a HANDLER lock. In that case HANDLER
code will clone the ticket (see below why it's needed).
If the request is for a transactional lock, and we found
a HANDLER lock, create a copy, to make sure that when user
does HANDLER CLOSE, the transactional lock is not released.
If the request is for a handler lock, and we found a
HANDLER lock, also do the clone. HANDLER CLOSE for one alias
should not release the lock on the table HANDLER opened through
a different alias.
*/
mdl_request->ticket = ticket;
if ((found_duration != mdl_request->duration || //找到的锁票与当前请求的锁生命周期不同
//当前请求的锁生命周期是显式的 则克隆锁票
mdl_request->duration == MDL_EXPLICIT) && clone_ticket(mdl_request)) {
/* Clone failed. */
mdl_request->ticket = nullptr;
return true;
}
return false;
}
/*
Prepare context for lookup in MDL_map container by allocating pins
if necessary. This also ensures that this MDL_context has pins allocated
and ready for future attempts elements from MDL_map container (which
might happen during lock release).
*/
if (fix_pins()) return true;
// 创建锁票
if (!(ticket = MDL_ticket::create(this, mdl_request->type
#ifndef NDEBUG
,
mdl_request->duration
#endif
)))
return true;
/*
Get increment for "fast path" or indication that this is
request for "obtrusive" type of lock outside of critical section.
判断加锁的两种方式
unobtrusive_lock_increment = 1 表示 unobtrusive
unobtrusive_lock_increment = 0 表示 obtrusive
*/
unobtrusive_lock_increment =
MDL_lock::get_unobtrusive_lock_increment(mdl_request);
/*
If this "obtrusive" type we have to take "slow path".
If this context has open HANDLERs we have to take "slow path"
as well for MDL_object_lock::notify_conflicting_locks() to work
properly.
*/
force_slow = !unobtrusive_lock_increment || m_needs_thr_lock_abort; //是否强制使用slow_path加锁方式
/*
If "obtrusive" lock is requested we need to "materialize" all fast
path tickets, so MDL_lock::can_grant_lock() can safely assume
that all granted "fast path" locks belong to different context.
当确实是一种obtrusive的lock请求
需要将之前已经使用fast_path(场景简单时的加锁方式,减少处理时间,提高性能)方式加的lock物化
并且加入到已授权队列中
*/
if (!unobtrusive_lock_increment) materialize_fast_path_locks();
assert(ticket->m_psi == nullptr);
ticket->m_psi = mysql_mdl_create(
ticket, key, mdl_request->type, mdl_request->duration,
MDL_ticket::PENDING, mdl_request->m_src_file, mdl_request->m_src_line);
retry: //如何通过fast_path高效获取lock
if (!(lock = mdl_locks.find_or_insert(m_pins, key, &pinned))) {
/*
If SEs were notified about impending lock acquisition, the failure
to acquire it requires the same notification as lock release.
*/
if (ticket->m_hton_notified) {
mysql_mdl_set_status(ticket->m_psi, MDL_ticket::POST_RELEASE_NOTIFY);
m_owner->notify_hton_post_release_exclusive(key);
}
MDL_ticket::destroy(ticket);
return true;
}
assert(mdl_locks.is_lock_object_singleton(key) == !pinned);
if (!force_slow) { // fast path
MDL_lock::fast_path_state_t old_state = lock->m_fast_path_state;
bool first_use;
do {
/*
Check if hash look-up returned object marked as destroyed or
it was marked as such while it was pinned by us. If yes we
need to unpin it and retry look-up.
*/
if (old_state & MDL_lock::IS_DESTROYED) {
if (pinned) lf_hash_search_unpin(m_pins);
DEBUG_SYNC(get_thd(), "mdl_acquire_lock_is_destroyed_fast_path");
goto retry;
}
/*
Check that there are no granted/pending "obtrusive" locks and nobody
even is about to try to check if such lock can be acquired.
In these cases we need to take "slow path".
*/
if (old_state & MDL_lock::HAS_OBTRUSIVE) goto slow_path; //跳转到slow_path
/*
If m_fast_path_state doesn't have HAS_SLOW_PATH set and all "fast"
path counters are 0 then we are about to use an unused MDL_lock
object. We need to decrement unused objects counter eventually.
*/
first_use = (old_state == 0);
/*
Now we simply need to increment m_fast_path_state with a value which
corresponds to type of our request (i.e. increment part this member
which contains counter which corresponds to this type).
This needs to be done as atomical operation with the above checks,
which is achieved by using atomic compare-and-swap.
@sa MDL_object_lock::m_unobtrusive_lock_increment for explanation
why overflow is not an issue here.
*/
} while (!lock->fast_path_state_cas(//原子操作增加计数器
&old_state, old_state + unobtrusive_lock_increment));
if (pinned) lf_hash_search_unpin(m_pins);
if (first_use && pinned) mdl_locks.lock_object_used();
/*
已经获取到lock 可以看到 通过fast_path获取的lock是没有加入到m_granted队列中的
当出现obtrusive的请求时 会将所有通过fast_path获取的lock加入到m_granted队列
*/
ticket->m_lock = lock;
ticket->m_is_fast_path = true;
m_ticket_store.push_front(mdl_request->duration, ticket);
mdl_request->ticket = ticket;
mysql_mdl_set_status(ticket->m_psi, MDL_ticket::GRANTED);
return false;
}
slow_path:
mysql_prlock_wrlock(&lock->m_rwlock);
MDL_lock::fast_path_state_t state = lock->m_fast_path_state;
if (state & MDL_lock::IS_DESTROYED) {//检查锁对象是否已经被销毁
mysql_prlock_unlock(&lock->m_rwlock);
if (pinned) lf_hash_search_unpin(m_pins);
DEBUG_SYNC(get_thd(), "mdl_acquire_lock_is_destroyed_slow_path");
goto retry;
}
if (pinned) lf_hash_search_unpin(m_pins);
bool first_obtrusive_lock =
(unobtrusive_lock_increment == 0) &&
((lock->m_obtrusive_locks_granted_waiting_count++) == 0);
bool first_use = false;
if (!(state & MDL_lock::HAS_SLOW_PATH) || first_obtrusive_lock) {
do {
first_use = (state == 0);
} while (!lock->fast_path_state_cas(
&state, state | MDL_lock::HAS_SLOW_PATH |
(first_obtrusive_lock ? MDL_lock::HAS_OBTRUSIVE : 0)));
}
if (first_use && pinned) mdl_locks.lock_object_used();
ticket->m_lock = lock;
if (lock->can_grant_lock(mdl_request->type, this)) {//检查是否可以授予lock
lock->m_granted.add_ticket(ticket);//加入到授权队列
if (lock->is_affected_by_max_write_lock_count()) {
if (lock->count_piglets_and_hogs(mdl_request->type))
lock->reschedule_waiters();
}
mysql_prlock_unlock(&lock->m_rwlock);
//将票据添加到上下文的票据存储中
m_ticket_store.push_front(mdl_request->duration, ticket);
mdl_request->ticket = ticket;
mysql_mdl_set_status(ticket->m_psi, MDL_ticket::GRANTED);
} else
*out_ticket = ticket;
return false;
}
MDL_wait::timed_wait
在通过try_acquire_lock_impl
获取lock
失败后,首先将等待的票据信息加入到等待队列中,并通过MDL_wait::timed_wait
方法等待该票据信息被其他线程释放,并唤醒自身,获取该票据
MDL_wait::enum_wait_status MDL_wait::timed_wait(
MDL_context_owner *owner,
struct timespec *abs_timeout,//等待的终止时间
bool set_status_on_timeout, //超时时是否将等待状态设置为 TIMEOUT
const PSI_stage_info *wait_state_name) {
PSI_stage_info old_stage;
enum_wait_status result;
int wait_result = 0;
mysql_mutex_lock(&m_LOCK_wait_status);
owner->ENTER_COND(&m_COND_wait_status, &m_LOCK_wait_status, wait_state_name,
&old_stage);
thd_wait_begin(nullptr, THD_WAIT_META_DATA_LOCK);
while (!m_wait_status && !owner->is_killed() && !is_timeout(wait_result)) {
wait_result = mysql_cond_timedwait(&m_COND_wait_status, &m_LOCK_wait_status,
abs_timeout);
}
thd_wait_end(nullptr);
if (m_wait_status == WS_EMPTY) {
if (owner->is_killed())
m_wait_status = KILLED;
else if (set_status_on_timeout)
m_wait_status = TIMEOUT;
}
result = m_wait_status;
mysql_mutex_unlock(&m_LOCK_wait_status);
owner->EXIT_COND(&old_stage);
return result;
}
MDL_lock::can_grant_lock
当一个线程想要获取某个锁票,会使用该方法检查锁请求是否不与其他已有的或等待中的锁请求发生冲突,是否可以被立即授予
bool MDL_lock::can_grant_lock(enum_mdl_type type_arg,
const MDL_context *requestor_ctx) const {
bool can_grant = false;
bitmap_t waiting_incompat_map = incompatible_waiting_types_bitmap()[type_arg]; //与请求锁类型不兼容的等待锁
bitmap_t granted_incompat_map = incompatible_granted_types_bitmap()[type_arg]; //与请求锁类型不兼容的已授锁
/*
m_waiting 所有正在等待的锁请求的集合
m_granted 所有已经授予的锁请求的集合
New lock request can be satisfied iff:
- There are no incompatible types of satisfied requests in other contexts
- There are no waiting requests which have higher priority than this request.
*/
if (!(m_waiting.bitmap() & waiting_incompat_map)) { //是否有任何等待的锁请求与新请求的锁类型不兼容
if (!(fast_path_granted_bitmap() & granted_incompat_map)) {//是否有通过“快速路径”授予的锁与新请求的锁类型不兼容
if (!(m_granted.bitmap() & granted_incompat_map))//已授锁集合中是否存在不兼容的锁
can_grant = true;
else {
Ticket_iterator it(m_granted);
MDL_ticket *ticket;
/*
There is an incompatible lock. Check that it belongs to some other context.
*/
while ((ticket = it++)) {
if (ticket->get_ctx() != requestor_ctx &&
ticket->is_incompatible_when_granted(type_arg))
/*
如果找到一张属于其他上下文且与新请求锁类型冲突的票据,则立即停止遍历,
因为这表示无法授予新的锁请求。
*/
break;
}
if (ticket == nullptr)
/* 不兼容的锁属于当前context 当前上下文持有的锁不会引起冲突 可以授权 */
can_grant = true;
}
} else {
/***/
}
}
return can_grant;
}
MDL_context::acquire_lock_remote
MDL_context::acquire_lock_local
成功后,通过该方法获取远端节点上的锁
bool MDL_context::acquire_lock_remote(MDL_request *mdl_request, MDL_ticket *ticket) {
if ((mdl_request->type == MDL_EXCLUSIVE &&
MDL_lock::needs_hton_notification(mdl_request->key.mdl_namespace()))/*X lock 且 是需要广播的类型*/
|| is_notify_ctc_se(mdl_request->type, mdl_request->key.mdl_namespace())) //需要通知SE
{
mysql_mdl_set_status(ticket->m_psi, MDL_ticket::PRE_ACQUIRE_NOTIFY);
bool victimized;
if (m_owner->notify_hton_pre_acquire_exclusive(&mdl_request->key, &victimized)) {
/*加锁失败后释放掉local_lock*/
ticket->m_hton_notified = false;
release_lock(mdl_request->duration, ticket);
mdl_request->ticket = nullptr;
return true;
}
//加锁成功
ticket->m_hton_notified = true;//设为true 在release_lock的时候表示需要去远端解锁
mysql_mdl_set_status(ticket->m_psi, MDL_ticket::PENDING);
}
mysql_mdl_set_status(ticket->m_psi, MDL_ticket::GRANTED);
return false;
}
//需要去广播的锁空间类型
bool MDL_lock::needs_hton_notification(
MDL_key::enum_mdl_namespace mdl_namespace) {
switch (mdl_namespace) {
case MDL_key::TABLESPACE:
case MDL_key::SCHEMA:
case MDL_key::TABLE:
case MDL_key::FUNCTION:
case MDL_key::PROCEDURE:
case MDL_key::TRIGGER:
case MDL_key::EVENT:
return true;
default:
return false;
}
}
static bool is_notify_ctc_se(enum_mdl_type mdl_type, MDL_key::enum_mdl_namespace mdl_namespace) {
switch (mdl_namespace) {
case MDL_key::GLOBAL:
case MDL_key::BACKUP_LOCK:
if (mdl_type == MDL_SHARED) {// GLOBAL 与 BACKUP_LOCK 的 MDL_SHARED需要广播
return true;
}
break;
default:
if (mdl_type == MDL_EXCLUSIVE) {
return true;
}
return false;
}
return false;
}
MDL_context::release_lock
释放当前的锁
void MDL_context::release_locks(MDL_release_locks_visitor *visitor) {
/* Remove matching lock tickets from the context. */
MDL_ticket *ticket;
MDL_ticket_store::List_iterator it_ticket =
m_ticket_store.list_iterator(MDL_EXPLICIT);//所有需要显式释放的锁票
while ((ticket = it_ticket++)) {
assert(ticket->m_lock);
//锁票必须对应一个具体的锁信息
if (visitor->release(ticket)) release_lock(MDL_EXPLICIT, ticket);
}
}
void MDL_context::release_lock(enum_mdl_duration duration, MDL_ticket *ticket) {
MDL_lock *lock = ticket->m_lock;
MDL_key key_for_hton;
mysql_mutex_assert_not_owner(&LOCK_open);
//先将锁票从store中移除
m_ticket_store.remove(duration, ticket);
/*
如果即将释放的锁要求在释放后通知SE,我们需要将其 MDL_key 暂存
以便能够在相应的 MDL_lock 对象因锁释放而被释放之后 还能够根据其键值通知SE
*/
if (ticket->m_hton_notified) key_for_hton.mdl_key_init(&lock->key);
/*
两种锁请求的不同处理机制
fast_path: 在低并发或无冲突的情况下 适用于那些预期不会与其他产生冲突的锁请求或者请求兼容
slow_path: 高并发环境或存在潜在冲突的情况下 多一些检查 检查现有锁的状态、等待队列中的其他请求、处理死锁检测
*/
if (ticket->m_is_fast_path) {
MDL_lock::fast_path_state_t unobtrusive_lock_increment =
lock->get_unobtrusive_lock_increment(ticket->get_type());
//是否有多个锁共享同一个对象(单例)
bool is_singleton = mdl_locks.is_lock_object_singleton(&lock->key);
MDL_lock::fast_path_state_t old_state = lock->m_fast_path_state;
bool last_use;
do {
if (old_state & MDL_lock::HAS_OBTRUSIVE) {
mysql_prlock_wrlock(&lock->m_rwlock);
last_use = (lock->fast_path_state_add(-unobtrusive_lock_increment) ==
unobtrusive_lock_increment);
if (lock->m_obtrusive_locks_granted_waiting_count)
lock->reschedule_waiters();//释放所持有的锁 并唤醒等待队列中可以获取到锁的线程
mysql_prlock_unlock(&lock->m_rwlock);
goto end_fast_path;
}
last_use = (old_state == unobtrusive_lock_increment);
} while (!lock->fast_path_state_cas(&old_state, old_state - unobtrusive_lock_increment));
end_fast_path:
/* Don't count singleton MDL_lock objects as unused. */
if (last_use && !is_singleton) mdl_locks.lock_object_unused(this, m_pins);
} else {
lock->remove_ticket(this, m_pins, &MDL_lock::m_granted, ticket);
}
if (ticket->m_hton_notified) { //对于需要通知SE的lock
mysql_mdl_set_status(ticket->m_psi, MDL_ticket::POST_RELEASE_NOTIFY);
m_owner->notify_hton_post_release_exclusive(&key_for_hton);
}
MDL_ticket::destroy(ticket);
}
MDL_lock::reschedule_waiter
用于确定当前等待的锁请求中哪些可以被满足,然后授予这些上下文锁并唤醒它们
void MDL_lock::reschedule_waiters() {
MDL_lock::Ticket_iterator it(m_waiting);
MDL_ticket *ticket;
while ((ticket = it++)) {
if (can_grant_lock(ticket->get_type(), ticket->get_ctx())) {
if (!ticket->get_ctx()->m_wait.set_status(MDL_wait::GRANTED)) {
//授权成功后 更新两个队列
m_waiting.remove_ticket(ticket);
m_granted.add_ticket(ticket);
if (is_affected_by_max_write_lock_count()) {
if (count_piglets_and_hogs(ticket->get_type())) {
it.rewind();
continue;
}
}
}
//无法更新则继续保留在等待队列中
}
}
/*...........................*/
}