MySQL源代码:如何对读写锁进行处理_MySQL
bitsCN.com
转载请署名:印风
-----------------------------------------------------------
最近碰到一个问题,线上一台机器在等待信号量时间过长,mysql的监控线程认为此时mysqld已经hang住了,于是自杀重启。这里涉及到一个有趣的问题,也就是mysql如何对读写锁进行处理。
主要包括三个部分:
1. 建锁
2. 加锁
3. 解锁
4. 监控锁
以下内容基于Percona5.5.18进行分析
1.创建锁
锁的创建实际上就是初始化一个RW结构体(rw_lock_t),实际调用函数如下:
# define rw_lock_create(K, L, level) /
rw_lock_create_func((L),#L)
在rw_lock_create上有三个参数,在实际场景锁时只用到第2个参数
其中K表示mysql_pfs_key_t,level显示当前的操作类型(起码看起来是的,在文件sync0sync.h中定义),看起来k是为performance schema准备的,而k代表了当前操作所在的层次。
例如:purge线程的读写锁创建:
rw_lock_create(trx_purge_latch_key,
&purge_sys->latch,SYNC_PURGE_LATCH);
我们进去rw_lock_create_func看看到底是怎么创建的。
可以看到这个函数的逻辑其实很简单:
lock->lock_word =X_LOCK_DECR; //关键字段
用于限制读写锁的最大并发数,代码里的注释如下:
/* 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.*/
在尝试加锁时会调用rw_lock_lock_word_decr减少lock_word
在初始化一系列变量后,执行:
lock->event = os_event_create(NULL);
lock->wait_ex_event = os_event_create(NULL);
os_event_create用于创建一个系统信号,实际上最终创建的还是互斥量(os_fast_mutex_init(&(event->os_mutex));以及条件变量(os_cond_init(&(event->cond_var));)
最后将lock加入到全局链表rw_lock_list中
2.加锁
加锁函数由宏定义,实际调用函数为:
1)写锁
# define rw_lock_x_lock(M) /
rw_lock_x_lock_func((M),0, __FILE__, __LINE__)
当申请写锁时,执行如下步骤:
(1).调用rw_lock_x_lock_low函数去获取锁,如果得到锁,则rw_x_spin_round_count += i后直接返回,如果得不到锁,继续执行
(2).loop过程中只执行一次rw_x_spin_wait_count++
(3).在毫秒级别的loop多次等待
while (i && lock->lock_word if(srv_spin_wait_delay) {
ut_delay(ut_rnd_interval(0,
srv_spin_wait_delay));
}
i++;
}
这里涉及到两个系统变量:
innodb_sync_spin_loops(SYNC_SPIN_ROUNDS)
innodb_spin_wait_delay(srv_spin_wait_delay)
在SYNC_SPIN_ROUNDS循环里调用函数ut_delay,这个函数很简单,就是做了delay*50次空循环
Ut_delay(uint delay):
for(i = 0; i j+= i;
UT_RELAX_CPU();
}
其中,UT_RELAX_CPU()会调用汇编指令来独占CPU,以防止线程切换
(4).如果loop的次数等于SYNC_SPIN_ROUNDS,调用os_thread_yield(实际调用pthread_yield,导致调用线程放弃CPU的占用)将线程挂起;否则挑到1继续loop
(5).在sync_primary_wait_array里获取一个cell(占个坑?)。调用sync_array_reserve_cell,看起来有1000个坑位(sync_primary_wait_array->n_cells)
(6).再次调用rw_lock_x_lock_low函数尝试获取锁,若成功获得,则返回
(7).调用sync_array_wait_event等待条件变量,然后返回1继续loop
具体的加锁函数(rw_lock_x_lock_low)稍后分析
2)读锁
# define rw_lock_s_lock(M) /
rw_lock_s_lock_func((M),0, __FILE__, __LINE__)
这个函数定义在sync0rw.ic里,函数也很简单,如下:
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;
}
这里首先调用rw_lock_s_lock_low进行加锁,如果加锁不成功,则调用rw_lock_s_lock_spin进行等待,rw_lock_s_lock_spin的代码逻辑与rw_lock_x_lock_func有些相似,这里不再赘述。
在rw_lock_s_lock_spin里会递归的调用到rw_lock_s_lock_low函数;
看起来实际的加锁和解锁操作是通过对计数器来控制的,
(1)在函数rw_lock_s_lock_low中
rw_lock_lock_word_decr (lock, 1),对lock->lock_word减去1
减数成功返回true,否则返回false
这部分的逻辑还是很简单的。
(2)在函数rw_lock_x_lock_low中,调用:
rw_lock_lock_word_decr(lock, X_LOCK_DECR),对lock->lock_word减去X_LOCK_DECR
减数成功后,执行:
rw_lock_set_writer_id_and_recursion_flag(lock,pass ? FALSE : TRUE)来设置:
lock->writer_thread = s_thread_get_curr_id()
lock->recursive = TRUE
然后调用rw_lock_x_lock_wait函数等待lock->lock_word=0,也就是说等待所有的读锁退出。
看到一个比较有意思的现象,在.ic的代码里看到使用了宏
INNODB_RW_LOCKS_USE_ATOMICS,这是跟gcc的版本相关的,通过使用gcc的内建函数来实现原子操作。
3.解锁
解锁操作包括解除读锁(#define rw_lock_s_unlock(L) rw_lock_s_unlock_gen(L, 0))和解除写锁操作(#definerw_lock_x_unlock(L) rw_lock_x_unlock_gen(L, 0))
实际调用函数为rw_lock_s_unlock_func和rw_lock_x_unlock_func
1)解除读锁(rw_lock_s_unlock_func)
增加计数rw_lock_lock_word_incr(lock, 1)
2)解除写锁(rw_lock_x_unlock_func)
执行如下操作
(1)如果是最后一个递归调用锁的线程,设置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)增加计数rw_lock_lock_word_incr(lock,X_LOCK_DECR) == X_LOCK_DECR,这时候需要向等待锁的线程发送信号:
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函数会发送一个pthread_cond_broadcast给等待的线程
4.监控读写锁
为了防止mysqld被hang住导致的长时间等待rw锁,error监控线程会对长时间等待的线程进行监控。这个线程每1秒loop一次
(os_event_wait_time_low(srv_error_event, 1000000, sig_count);)
函数入口:srv_error_monitor_thread
函数sync_array_print_long_waits()用于处理长时间等待信号量的线程,流程如下:
1. 查看sync_primary_wait_array数组中的所有等待线程。
->大于240秒时,向错误日志中输出警告,设置noticed = TRUE;
->大于600秒时,设置fatal =TRUE;
2.当noticed为true时,打印出innodb监控信息,然后sleep30秒
3. 返回fatal值
当函数sync_primary_wait_array返回true时,对于同一个等待线程还会有十次机会,也就是300 + 1*10(监控线程每次loop sleep 1s)秒的时间;如果挺不过去,监控线程就会执行一个断言失败:
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是一个宏:
#define ut_error assert(0)
断言失败导致mysqld crash
在函数srv_error_monitor_thread里发现一个比较有意思的参数srv_kill_idle_transaction,对应的系统变量为innodb_kill_idle_transaction,用于清理在一段时间内的空闲事务。这个变量指定了空闲事务的最长时间。具体实现分析,且听下回分解
作者 记录成长之路 bitsCN.com

핫 AI 도구

Undresser.AI Undress
사실적인 누드 사진을 만들기 위한 AI 기반 앱

AI Clothes Remover
사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool
무료로 이미지를 벗다

Clothoff.io
AI 옷 제거제

Video Face Swap
완전히 무료인 AI 얼굴 교환 도구를 사용하여 모든 비디오의 얼굴을 쉽게 바꾸세요!

인기 기사

뜨거운 도구

메모장++7.3.1
사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전
중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기
강력한 PHP 통합 개발 환경

드림위버 CS6
시각적 웹 개발 도구

SublimeText3 Mac 버전
신 수준의 코드 편집 소프트웨어(SublimeText3)

뜨거운 주제











Linux에서 컬 버전을 업데이트하려면 다음 단계를 따르세요. 현재 컬 버전을 확인하세요. 먼저 현재 시스템에 설치된 컬 버전을 확인해야 합니다. 터미널을 열고 다음 명령을 실행합니다. 컬 --version 이 명령은 현재 컬 버전 정보를 표시합니다. 사용 가능한 컬 버전 확인: 컬을 업데이트하기 전에 사용 가능한 최신 버전을 확인해야 합니다. 최신 버전의 컬을 찾으려면 컬의 공식 웹사이트(curl.haxx.se)나 관련 소프트웨어 소스를 방문하세요. 컬 소스 코드 다운로드: 컬 또는 브라우저를 사용하여 선택한 컬 버전의 소스 코드 파일(일반적으로 .tar.gz 또는 .tar.bz2)을 다운로드합니다.

Linux 커널은 소스 코드가 전용 코드 저장소에 저장되어 있는 오픈 소스 운영 체제 커널입니다. 이번 글에서는 리눅스 커널 소스코드의 저장 경로를 자세히 분석하고, 독자들의 이해를 돕기 위해 구체적인 코드 예시를 활용하겠습니다. 1. Linux 커널 소스 코드 저장 경로 Linux 커널 소스 코드는 linux라는 Git 저장소에 저장되어 있으며, 이 저장소는 [https://github.com/torvalds/linux](http://github.com/torvalds/linux)에서 호스팅됩니다.

보기 단계: 1. 설치 디렉토리를 찾거나 온라인으로 봅니다. 2. 소스 코드의 압축을 풉니다. 3. 텍스트 편집기 또는 통합 개발 환경을 사용합니다. 4. 소스 코드를 탐색하고 봅니다. 자세한 소개: 1. 설치 디렉터리 찾기 또는 온라인 보기: JDK가 설치된 경우 JDK 설치 디렉터리에서 Java 소스 코드를 찾을 수 있습니다. JDK 설치 디렉토리에는 일반적으로 Java 코어 클래스 라이브러리의 소스 코드가 포함된 src.zip 또는 이와 유사한 압축 파일이 있으며 Java 소스 코드를 온라인으로 볼 수도 있습니다.

Tomcat 소스 코드를 보는 단계: 1. Tomcat 소스 코드를 다운로드합니다. 2. IDEA에서 Tomcat 소스 코드를 가져옵니다. 3. 소스 코드를 봅니다. 4. Tomcat의 작동 원리를 이해합니다. 6. 주의사항 7. 지속적으로 학습하고 업데이트하세요. 8. 도구와 플러그인을 사용하세요. 자세한 소개: 1. Tomcat 소스 코드를 다운로드하려면 먼저 Tomcat 소스 코드를 얻어야 합니다. Apache Tomcat 등의 공식 웹사이트에서 소스 코드 패키지를 다운로드할 수 있습니다.

Java에서 소스 코드 파일의 접미사는 일반적으로 .java입니다. Java 프로그램을 작성하면 Java 소스 코드가 포함된 접미사 .java가 붙은 소스 코드 파일이 생성됩니다. 예를 들어, 간단한 Java 소스 코드 파일의 이름은 MyClass.java로 지정할 수 있습니다. 여기서 MyClass는 클래스 이름이고 .java는 파일의 접미사입니다.

이것은 Linux 커널 소스 코드 배포를 심층적으로 탐구하는 1500 단어 분량의 기사입니다. 제한된 공간으로 인해 Linux 커널 소스 코드의 조직 구조에 중점을 두고 독자의 이해를 돕기 위해 몇 가지 구체적인 코드 예제를 제공합니다. Linux 커널은 소스 코드가 GitHub에서 호스팅되는 오픈 소스 운영 체제 커널입니다. 전체 Linux 커널 소스 코드 배포판은 매우 방대하며 여러 하위 시스템 및 모듈과 관련된 수십만 줄의 코드를 포함합니다. Linux 커널 소스 코드를 더 깊이 이해하려면

소스 코드를 읽어 최신 PHP 코드 사양의 디자인 원칙과 목표를 어떻게 이해할 수 있습니까? 소개: 고품질 PHP 코드를 작성할 때 특정 코딩 표준을 따르는 것이 매우 중요합니다. 코드 사양을 통해 코드의 가독성, 유지 관리성, 확장성을 향상시킬 수 있습니다. PHP 언어에는 PSR(PHPStandardsRecommendations)이라는 널리 채택된 코드 사양이 있습니다. 이 기사에서는 최신 PHP 코드 사양의 소스 코드를 읽는 방법을 소개합니다.

하얼빈 의과대학 임상약학 취업 전망은 어떻습니까? 전국 취업 상황이 낙관적이지는 않지만 약학 졸업생의 취업 전망은 여전히 좋습니다. 전반적으로 제약산업 졸업생의 공급은 수요보다 적다. 제약회사와 제약공장은 이러한 졸업생을 흡수하는 주요 통로이기도 하다. 보도에 따르면 최근 몇 년간 조제약품, 천연의약화학 등 전공 대학원생의 수급비율은 1:10에 달하기도 했다. 임상약학전공 취업방향: 임상의학을 전공하는 학생은 졸업 후 의료보건학과, 의학연구 및 기타 학과에서 진료, 예방, 의학연구 등에 종사할 수 있습니다. 채용 직위: 의료 담당자, 제약 영업 담당자, 영업 담당자, 영업 관리자, 지역 영업 관리자, 투자 관리자, 제품 관리자, 제품 전문가, 간호사
