5.5.3.3 线程池操作

线程池由多个线程组组成,每个线程组 Management 一组 Client 端连接。构建连接后,线程池将以循环方式将它们分配给线程组。

线程组的数量可以使用thread_pool_size系统变量进行配置。组的默认数目为 16.有关设置此变量的准则,请参见第 5.5.3.4 节“线程池调整”

每个组的最大线程数为 4096(在内部使用一个线程的某些系统上为 4095)。

线程池将连接和线程分开,因此连接与执行从那些连接接收的语句的线程之间没有固定的关系。这不同于默认的线程处理模型,该模型将一个线程与一个连接相关联,以使给定线程执行其 Connecting 的所有语句。

线程池试图确保在任何时候在每个组中最多执行一个线程,但是有时允许更多线程临时执行以达到最佳性能。该算法以以下方式工作:

  • 每个线程组都有一个侦听器线程,该线程侦听分配给该组的 Connecting 的传入语句。当一条语句到达时,线程组要么立即开始执行它,要么将其排队 await 以后执行:

  • 如果该语句是唯一收到的语句,并且没有语句排队或当前正在执行,则立即执行。

    • 如果语句不能立即开始执行,则会发生排队。
  • 如果立即执行,则执行由侦听器线程执行。 (这意味着该组中暂时没有线程正在侦听.)如果该语句快速完成,执行线程将返回侦听语句。否则,线程池将认为该语句已停止,并启动另一个线程作为侦听器线程(如有必要,请创建该线程)。为确保没有任何线程组被停止的语句阻塞,线程池具有一个后台线程,该线程定期监视线程组状态。

通过使用侦听线程执行可以立即开始的语句,如果该语句快速完成,则无需创建其他线程。这样可以确保在并发线程数较少的情况下尽可能最有效的执行。

启动线程池插件时,它将为每个组创建一个线程(侦听器线程),再创建一个后台线程。根据需要创建其他线程来执行语句。

  • thread_pool_stall_limit系统变量的值确定上一项中“快速完成”的含义。线程被认为停止之前的默认时间为 60ms,但可以设置为最多 6s。此参数是可配置的,以使您能够达到适合服务器工作负载的平衡。较短的 await 值使线程可以更快地启动。较短的值也可以更好地避免死锁情况。长 await 值对于包含长时间运行的语句的工作负载很有用,以避免在当前语句执行时启动太多新语句。

  • 线程池的重点是限制并发短运行语句的数量。在执行语句到达停顿时间之前,它会阻止其他语句开始执行。如果该语句执行超过停顿时间,则可以 continue 执行该语句,但不再阻止其他语句启动。通过这种方式,线程池将尝试确保每个线程组中的短运行语句不会超过一个,尽管可能会有多个长运行语句。让长时间运行的语句阻止其他语句执行是不可取的,因为对 await 的数量没有限制。例如,在复制主服务器上,将二进制日志事件发送到从属服务器的线程将永远有效地运行。

  • 如果语句遇到磁盘 I/O 操作或用户级别的锁(行锁或 table 锁),则该语句将被阻塞。该块将导致线程组变得未使用,因此对线程池进行了回调,以确保线程池可以立即启动该组中的新线程来执行另一条语句。当阻塞的线程返回时,线程池允许它立即重新启动。

  • 有两个队列,一个高优先级队列和一个低优先级队列。事务中的第一条语句进入低优先级队列。如果事务正在进行(事务的语句已开始执行),则该事务的以下任何语句都将进入高优先级队列,否则,将进入低优先级队列。启用thread_pool_high_priority_connection系统变量会影响队列分配,该变量会导致会话的所有排队语句进入高优先级队列。

非事务性存储引擎或事务引擎(如果启用了autocommit)的语句被视为低优先级语句,因为在这种情况下,每个语句都是一个事务。因此,给定InnoDBMyISAMtable 的混合语句,除非启用autocommit,否则线程池将InnoDB的语句优先于MyISAM的语句。启用autocommit后,所有语句将具有低优先级。

  • 当线程组选择一个排队的语句来执行时,它首先在高优先级队列中查找,然后在低优先级队列中查找。如果找到一条语句,则将该语句从其队列中删除并开始执行。

  • 如果一条语句在低优先级队列中停留的时间过长,则线程池将移至高优先级队列。 thread_pool_prio_kickup_timer系统变量的值控制移动之前的时间。对于每个线程组,每 10ms 最多有一个语句或每秒 100 个语句将从低优先级队列移到高优先级队列。

  • 线程池重用最活跃的线程,以更好地利用 CPU 缓存。这是一个很小的调整,会对性能产生重大影响。

  • 当线程从用户连接执行语句时,性能模式检测会将线程活动记入用户连接。否则,性能架构会将活动计入线程池。

这是一个线程组可能有多个线程开始执行语句的条件示例:

  • 一个线程开始执行一条语句,但是运行了足够长的时间才被认为已停止。即使第一个线程仍在执行,线程组也允许另一个线程开始执行另一个语句。

  • 一个线程开始执行一条语句,然后被阻塞,并将其报告回线程池。线程组允许另一个线程开始执行另一个语句。

  • 一个线程开始执行一条语句,被阻塞,但是没有报告该语句已被阻塞,因为在使用线程池回调进行检测的代码中该阻塞没有发生。在这种情况下,线程在线程组中似乎仍在运行。如果该块的持续时间足够长,以至于该语句被认为已停止,则该组将允许另一个线程开始执行另一个语句。

线程池被设计为可在越来越多的连接上进行扩展。它还旨在避免因限制正在执行的语句数而引起的死锁。重要的是,不向线程池报告的线程不会阻止其他语句的执行,从而导致线程池死锁。此类声明的示例如下:

  • 长时间运行的语句。这些将导致所有资源仅由少数几个语句使用,并且它们可能阻止所有其他语句访问服务器。

  • 二进制日志转储线程读取二进制日志并将其发送到从属服务器。这是一种长期运行的“语句”,它会运行很长时间,并且不应阻止其他语句的执行。

  • 对行锁,table 锁,睡眠或其他任何未由 MySQL Server 或存储引擎报告回线程池的阻塞活动而阻塞的语句。

在每种情况下,为防止死锁,该语句在未快速完成时将其移至停顿类别,以便线程组可以允许另一个语句开始执行。通过这种设计,当线程执行或长时间阻塞时,线程池将线程移至停滞类别,并且在语句执行的其余部分,它不会阻止其他语句执行。

可以出现的最大线程数是max_connectionsthread_pool_size之和。在所有连接都处于执行模式并且为每个组创建一个额外的线程以侦听更多语句的情况下,可能会发生这种情况。这不一定是经常发生的状态,但理论上是可能的。