赶在2014之前,写下2013最后一篇技术博客,介绍一下InnDB的并发控制模型
背景知识
InnoDB中为什么需要并发控制?要回答这个问题,需要先聊聊context switch,当同时执行的进程/线程超过cpu核心数(超线程的数目)时,cpu以时间片轮转调度执行多个进程的时候就有context switch了,当进程A的时间片用完需要调度另外进程B时,需要将进程A的状态和参数保存在堆栈中,等再次调度到A时,再从堆栈中恢复状态和参数,这个切换的代价就是context switch,context switch的代价根据CPU性能及进程空间大小的不同,开销从几微秒(us)到几毫秒(ms)级别(参考:Quantifying The Cost of Context Switch,how-long-does-it-take-to-make-context),相对于一次内存读写操作(100ns左右),context switch代价不可小视(按照100us计算的话,相当于1000次内存访问)
innodb_thread_concurrency
这个参数就是控制进入到InnoDB层的并发线程数目,默认为0,表示不受限制,线上我们一般设置为32或者64
当同时并发(逻辑上)的进程/线程数目远远超过cpu核心数时,context switch就会非常频繁,最终导致性能问题(一轮context switch的时间也许够处理一个任务了),这时候如果限制并发运行的进程/线程数目在cpu核心数,就会极大提高性能,看效果图:
当innodb_thread_concurrency设置为0时,innodb并发控制不起作用,导致进入到innodb层的线程数不受控制,可以从图中观察到QPS急剧下降,当并发线程超过4096时,MySQL基本不能响应了(机器硬件:2个cpu,12核心/cpu,192G内存),然而设置innodb_thread_concurrency为32时,性能下降得没有那么厉害
InnoDB并发控制实现
算法介绍(innodb_thread_concurrency > 0):
1、server层到innodb层读写数据是一条一条记录进行的,每次读写都会进/出一次InnoDB层(row_search_for_mysql),进入InnoDB层的时候会检查当前并发线程数目,当超过innodb_thread_concurrency时,线程将尝试spin和sleep并再次检查,如果并发数还是超过innodb_thread_concurrency,线程将进入到一个FIFO中等待被唤醒,读写记录结束后退出InnoDB层时会将当前并发线程数减1,并检查其是否低于innodb_thread_concurrency,如果是的话,从FIFO中唤醒一个等待的线程,保证并发线程不会超过innodb_thread_concurrency参数
2、当线程进入InnoDB层后,但在获取数据时由于锁请求无法得到满足而需要挂起时,线程将强制退出InnoDB层,当锁请求满足后,线程继续运行并强制进入到InnoDB层,这会导致实际并发线程数不是严格控制在innodb_thread_concurrency之内
代码调用逻辑
主要函数:
innodb_srv_conc_enter_innodb
innodb_srv_conc_exit_innodb
srv_conc_force_enter_innodb
srv_conc_force_exit_innodb
调用逻辑
进入/退出InnoDB层
ha_innobase::index_read ha_innobase::general_fetch row_check_index_for_mysql ... innodb_srv_conc_enter_innodb(prebuilt->trx); ret = row_search_for_mysql((byte*) buf, mode, prebuilt, match_mode, 0); innodb_srv_conc_exit_innodb(prebuilt->trx); ... |
锁等待逻辑:
srv_suspend_mysql_thread ... if (trx->declared_to_be_inside_innodb) { was_declared_inside_innodb = TRUE; /* We must declare this OS thread to exit InnoDB, since a possible other thread holding a lock which this thread waits for must be allowed to enter, sooner or later */ srv_conc_force_exit_innodb(trx); } /* Suspend this thread and wait for the event. */ thd_wait_begin(trx->mysql_thd, THD_WAIT_ROW_LOCK); os_event_wait(event); thd_wait_end(trx->mysql_thd); if (was_declared_inside_innodb) { /* Return back inside InnoDB */ srv_conc_force_enter_innodb(trx); } |
至于更加具体的实现细节,感兴趣的话还需阅读代码
《InnoDB并发控制模型》上有1条评论