事务并发控制
事务并发控制
在MySQL5.7中,由于消除了大量临界资源的竞争,InnoDB只读查询的性能非常优化,几乎可以随着CPU线性扩展。但如果进入到读写混合的场景,就不可避免的使用到一些临界资源,例如事务、锁、日志等子系统。当竞争越激烈,就可能导致性能的下降。通常系统会有个吞吐量和响应时间最优的性能拐点。
InnoDB本身提供了并发控制机制,一种是语句级别的并发控制,另外一种是事务提交阶段的并发控制。
语句级别的并发通过参数innodb_thread_concurrency
来控制,表示允许同时在InnoDB层活跃的并发SQL数。
每条SQL在进入InnoDB层进行操作之前都需要先递增全局计数,并为当前SQL分配innodb_concurrency_tickets
个ticket。也就是说,如果当前SQL需要进出InnoDB层很多次(例如一个大查询需要扫描很多行数据时),innodb_concurrency_tickets
次都可以自由进入InnoDB,无需判断innodb_thread_concurrency
。当ticket用完时,就需要重新进入,当SQL执行完成后,会将ticket重置为0。
如果当前InnoDB层的并发度已满,用户线程就需要等待,目前的实现使用sleep一段时间的方式,sleep的时间是自适应的,但你可以通过参数innodb_adaptive_max_sleep_delay
来设置一个最大sleep事件,具体的算法参阅函数srv_conc_enter_innodb_with_atomics
。
提到并发控制,另外一个不得不提的问题就是热点更新问题。事务在进入InnoDB层,准备更新一条数据,但发现行记录被其他线程锁住,这时候该线程会强制退出InnoDB并发控制,同时将自己suspend住,进入睡眠等待。如果有大量并发的更新同一条记录,就意味着大量线程进入InnoDB层,访问热点竞争资源锁系统,然后再退出。最终会呈现出大量线程在InnoDB中suspend住,相当于并发控制并没有达到降低临界资源争用的效果。早期我们对该问题的优化就是将线程从堵在InnoDB层,转移到堵在进入InnoDB层时的外部排队中,这样就不涉及到InnoDB的资源争用了。具体的实现是将statement级别的并发控制提升为事务级别的并发控制,因此这个方案的缺陷是对长事务不友好。
另外还有一些并发控制方案,例如线程池、水位限流、按pk排队等策略,我们的RDS MySQL也很早就支持了。如果你存在热点争用(例如秒杀场景),并且正在使用RDS MySQL,你可以去咨询售后如何使用这些特性。
除了语句级别的并发外,InnoDB也提供了提交阶段的并发控制,主要通过参数innodb_commit_concurrency
来控制。该参数的默认值为0,表示不控制commit阶段的并发。在进入函数innobase_commit
时,如果该参数被设置,且当前并发度超过,就需要等待。然而由于当前在默认配置下所有事务都走组提交(ordered_commit
),InnoDB层的提交大多数情况下只会有一个活跃线程。你只有关闭binlog或者关闭参数binlog_order_commits
,这个参数设置才有意义。