您好,登錄后才能下訂單哦!
這期內(nèi)容當(dāng)中小編將會(huì)給大家?guī)?lái)有關(guān)MySQL中怎么處理讀寫(xiě)鎖,文章內(nèi)容豐富且以專(zhuān)業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
1.創(chuàng)建鎖
鎖的創(chuàng)建實(shí)際上就是初始化一個(gè)RW結(jié)構(gòu)體(rw_lock_t),實(shí)際調(diào)用函數(shù)如下:
# define rw_lock_create(K, L, level) \
rw_lock_create_func((L),#L)
在rw_lock_create上有三個(gè)參數(shù),在實(shí)際場(chǎng)景鎖時(shí)只用到第2個(gè)參數(shù)
其中K表示mysql_pfs_key_t,level顯示當(dāng)前的操作類(lèi)型(起碼看起來(lái)是的,在文件sync0sync.h中定義),看起來(lái)k是為performance schema準(zhǔn)備的,而k代表了當(dāng)前操作所在的層次。
例如:purge線程的讀寫(xiě)鎖創(chuàng)建:
rw_lock_create(trx_purge_latch_key,
&purge_sys->latch,SYNC_PURGE_LATCH);
我們進(jìn)去rw_lock_create_func看看到底是怎么創(chuàng)建的。
可以看到這個(gè)函數(shù)的邏輯其實(shí)很簡(jiǎn)單:
lock->lock_word =X_LOCK_DECR; //關(guān)鍵字段
用于限制讀寫(xiě)鎖的最大并發(fā)數(shù),代碼里的注釋如下:
/* We decrement lock_word by this amountfor each x_lock. It is also the
start value for the lock_word, meaning thatit limits the maximum number
of concurrent read locks before the rw_lockbreaks. The current value of
0x00100000 allows 1,048,575 concurrentreaders and 2047 recursive writers.*/
在嘗試加鎖時(shí)會(huì)調(diào)用rw_lock_lock_word_decr減少lock_word
在初始化一系列變量后,執(zhí)行:
lock->event = os_event_create(NULL);
lock->wait_ex_event = os_event_create(NULL);
os_event_create用于創(chuàng)建一個(gè)系統(tǒng)信號(hào),實(shí)際上最終創(chuàng)建的還是互斥量(os_fast_mutex_init(&(event->os_mutex));以及條件變量(os_cond_init(&(event->cond_var));)
最后將lock加入到全局鏈表rw_lock_list中
2.加鎖
加鎖函數(shù)由宏定義,實(shí)際調(diào)用函數(shù)為:
1)寫(xiě)鎖
# define rw_lock_x_lock(M) \
rw_lock_x_lock_func((M),0, __FILE__, __LINE__)
當(dāng)申請(qǐng)寫(xiě)鎖時(shí),執(zhí)行如下步驟:
(1).調(diào)用rw_lock_x_lock_low函數(shù)去獲取鎖,如果得到鎖,則rw_x_spin_round_count += i后直接返回,如果得不到鎖,繼續(xù)執(zhí)行
(2).loop過(guò)程中只執(zhí)行一次rw_x_spin_wait_count++
(3).在毫秒級(jí)別的loop多次等待
while (i < SYNC_SPIN_ROUNDS
&& lock->lock_word <= 0) {
if(srv_spin_wait_delay) {
ut_delay(ut_rnd_interval(0,
srv_spin_wait_delay));
}
i++;
}
這里涉及到兩個(gè)系統(tǒng)變量:
innodb_sync_spin_loops(SYNC_SPIN_ROUNDS)
innodb_spin_wait_delay(srv_spin_wait_delay)
在SYNC_SPIN_ROUNDS循環(huán)里調(diào)用函數(shù)ut_delay,這個(gè)函數(shù)很簡(jiǎn)單,就是做了delay*50次空循環(huán)
Ut_delay(uint delay):
for(i = 0; i < delay * 50; i++) {
j+= i;
UT_RELAX_CPU();
}
其中,UT_RELAX_CPU()會(huì)調(diào)用匯編指令來(lái)獨(dú)占CPU,以防止線程切換
(4).如果loop的次數(shù)等于SYNC_SPIN_ROUNDS,調(diào)用os_thread_yield(實(shí)際調(diào)用pthread_yield,導(dǎo)致調(diào)用線程放棄CPU的占用)將線程掛起;否則挑到1繼續(xù)loop
(5).在sync_primary_wait_array里獲取一個(gè)cell(占個(gè)坑?)。調(diào)用sync_array_reserve_cell,看起來(lái)有1000個(gè)坑位(sync_primary_wait_array->n_cells)
(6).再次調(diào)用rw_lock_x_lock_low函數(shù)嘗試獲取鎖,若成功獲得,則返回
(7).調(diào)用sync_array_wait_event等待條件變量,然后返回1繼續(xù)loop
具體的加鎖函數(shù)(rw_lock_x_lock_low)稍后分析
2)讀鎖
# define rw_lock_s_lock(M) \
rw_lock_s_lock_func((M),0, __FILE__, __LINE__)
這個(gè)函數(shù)定義在sync0rw.ic里,函數(shù)也很簡(jiǎn)單,如下:
if (rw_lock_s_lock_low(lock, pass, file_name, line)) {
return; /* Success */
}else {
/* Did not succeed, try spin wait */
rw_lock_s_lock_spin(lock, pass, file_name, line);
return;
}
這里首先調(diào)用rw_lock_s_lock_low進(jìn)行加鎖,如果加鎖不成功,則調(diào)用rw_lock_s_lock_spin進(jìn)行等待,rw_lock_s_lock_spin的代碼邏輯與rw_lock_x_lock_func有些相似,這里不再贅述。
在rw_lock_s_lock_spin里會(huì)遞歸的調(diào)用到rw_lock_s_lock_low函數(shù);
看起來(lái)實(shí)際的加鎖和解鎖操作是通過(guò)對(duì)計(jì)數(shù)器來(lái)控制的,
(1)在函數(shù)rw_lock_s_lock_low中
rw_lock_lock_word_decr (lock, 1),對(duì)lock->lock_word減去1
減數(shù)成功返回true,否則返回false
這部分的邏輯還是很簡(jiǎn)單的。
(2)在函數(shù)rw_lock_x_lock_low中,調(diào)用:
rw_lock_lock_word_decr(lock, X_LOCK_DECR),對(duì)lock->lock_word減去X_LOCK_DECR
減數(shù)成功后,執(zhí)行:
rw_lock_set_writer_id_and_recursion_flag(lock,pass ? FALSE : TRUE)來(lái)設(shè)置:
lock->writer_thread = s_thread_get_curr_id()
lock->recursive = TRUE
然后調(diào)用rw_lock_x_lock_wait函數(shù)等待lock->lock_word=0,也就是說(shuō)等待所有的讀鎖退出。
看到一個(gè)比較有意思的現(xiàn)象,在.ic的代碼里看到使用了宏
INNODB_RW_LOCKS_USE_ATOMICS,這是跟gcc的版本相關(guān)的,通過(guò)使用gcc的內(nèi)建函數(shù)來(lái)實(shí)現(xiàn)原子操作。
3.解鎖
解鎖操作包括解除讀鎖(#define rw_lock_s_unlock(L) rw_lock_s_unlock_gen(L, 0))和解除寫(xiě)鎖操作(#definerw_lock_x_unlock(L) rw_lock_x_unlock_gen(L, 0))
實(shí)際調(diào)用函數(shù)為rw_lock_s_unlock_func和rw_lock_x_unlock_func
1)解除讀鎖(rw_lock_s_unlock_func)
增加計(jì)數(shù)rw_lock_lock_word_incr(lock, 1)
2)解除寫(xiě)鎖(rw_lock_x_unlock_func)
執(zhí)行如下操作
(1)如果是最后一個(gè)遞歸調(diào)用鎖的線程,設(shè)置lock->recursive= FALSE; 代碼里的注釋如下:
/* lock->recursive flag also indicatesif lock->writer_thread is
valid or stale. If we are the last of the recursive callers
then we must unset lock->recursive flag to indicate that the
lock->writer_thread is now stale.
Note that since we still hold the x-lock we can safely read the
lock_word. */
(2)增加計(jì)數(shù)rw_lock_lock_word_incr(lock,X_LOCK_DECR) == X_LOCK_DECR,這時(shí)候需要向等待鎖的線程發(fā)送信號(hào):
if (lock->waiters) {
rw_lock_reset_waiter_flag(lock);
os_event_set(lock->event);
sync_array_object_signalled(sync_primary_wait_array);
}
os_event_set函數(shù)會(huì)發(fā)送一個(gè)pthread_cond_broadcast給等待的線程
4.監(jiān)控讀寫(xiě)鎖
為了防止mysqld被hang住導(dǎo)致的長(zhǎng)時(shí)間等待rw鎖,error監(jiān)控線程會(huì)對(duì)長(zhǎng)時(shí)間等待的線程進(jìn)行監(jiān)控。這個(gè)線程每1秒loop一次
(os_event_wait_time_low(srv_error_event, 1000000, sig_count);)
函數(shù)入口:srv_error_monitor_thread
函數(shù)sync_array_print_long_waits()用于處理長(zhǎng)時(shí)間等待信號(hào)量的線程,流程如下:
1. 查看sync_primary_wait_array數(shù)組中的所有等待線程。
->大于240秒時(shí),向錯(cuò)誤日志中輸出警告,設(shè)置noticed = TRUE;
->大于600秒時(shí),設(shè)置fatal =TRUE;
2.當(dāng)noticed為true時(shí),打印出innodb監(jiān)控信息,然后sleep30秒
3. 返回fatal值
當(dāng)函數(shù)sync_primary_wait_array返回true時(shí),對(duì)于同一個(gè)等待線程還會(huì)有十次機(jī)會(huì),也就是300 + 1*10(監(jiān)控線程每次loop sleep 1s)秒的時(shí)間;如果挺不過(guò)去,監(jiān)控線程就會(huì)執(zhí)行一個(gè)斷言失?。?br/>
if (fatal_cnt > 10) {
fprintf(stderr,
"InnoDB:Error: semaphore wait has lasted"
"> %lu seconds\n"
"InnoDB:We intentionally crash the server,"
"because it appears to be hung.\n",
(ulong) srv_fatal_semaphore_wait_threshold);
ut_error;
}
ut_error是一個(gè)宏:
#define ut_error assert(0)
斷言失敗導(dǎo)致mysqld crash
在函數(shù)srv_error_monitor_thread里發(fā)現(xiàn)一個(gè)比較有意思的參數(shù)srv_kill_idle_transaction,對(duì)應(yīng)的系統(tǒng)變量為innodb_kill_idle_transaction,用于清理在一段時(shí)間內(nèi)的空閑事務(wù)。這個(gè)變量指定了空閑事務(wù)的最長(zhǎng)時(shí)間。
上述就是小編為大家分享的MySQL中怎么處理讀寫(xiě)鎖了,如果剛好有類(lèi)似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。