线程池由多个线程组组成,每个线程组管理一组客户端连接。建立连接后,线程池以循环方式将它们分配给线程组。
线程池公开了可用于配置其操作的系统变量:
thread_pool_algorithm
:用于调度的并发算法。thread_pool_high_priority_connection
:如何安排会话的语句执行。thread_pool_max_unused_threads
:允许多少休眠线程。thread_pool_prio_kickup_timer
:线程池将等待执行的语句从低优先级队列移动到高优先级队列的时间。thread_pool_size
:线程池中线程组的数量。这是控制线程池性能的最重要参数。thread_pool_stall_limit
:执行语句被认为停止之前的时间。
要配置线程组的数量,请使用
thread_pool_size
系统变量。默认组数为 16。有关设置此变量的指南,请参阅第 5.5.3.4 节,“线程池调整”。
每组的最大线程数为 4096(或在某些内部使用一个线程的系统上为 4095)。
线程池将连接和线程分开,因此连接和执行从这些连接接收到的语句的线程之间没有固定的关系。这不同于默认的线程处理模型,该模型将一个线程与一个连接相关联,以便给定线程执行其连接中的所有语句。
线程池试图确保在任何时候每个组中最多执行一个线程,但有时会允许更多线程临时执行以获得最佳性能:
每个线程组都有一个侦听器线程,用于侦听来自分配给该组的连接的传入语句。当语句到达时,线程组要么立即开始执行它,要么将其排队等待稍后执行:
如果该语句是唯一收到的语句并且没有语句排队或当前正在执行,则会立即执行。
如果语句不能立即开始执行,就会出现排队。
如果立即执行,则侦听器线程执行它。(这意味着组中暂时没有线程在侦听。)如果语句很快完成,执行线程将返回以侦听语句。否则,线程池认为该语句已停止并启动另一个线程作为侦听器线程(必要时创建它)。为了确保没有线程组被停滞的语句阻塞,线程池有一个后台线程定期监视线程组状态。
通过使用侦听线程来执行可以立即开始的语句,如果语句快速完成,则无需创建额外的线程。这确保了在并发线程数较少的情况下最有效的执行。
当线程池插件启动时,它会为每组创建一个线程(侦听器线程)以及后台线程。根据需要创建额外的线程来执行语句。
系统变量的值 决定了上一项中“快速完成”
thread_pool_stall_limit
的含义。线程被认为停止之前的默认时间是 60 毫秒,但可以设置为最大值 6 秒。此参数是可配置的,使您能够针对服务器工作负载取得适当的平衡。短等待值允许线程更快地启动。短值也能更好地避免死锁情况。长时间等待值对于包含长时间运行的语句的工作负载很有用,以避免在当前语句执行时启动太多新语句。线程池侧重于限制并发短时间运行语句的数量。在执行语句到达停顿时间之前,它会阻止其他语句开始执行。如果语句执行超过了停顿时间,则允许继续执行但不再阻止其他语句启动。通过这种方式,线程池试图确保在每个线程组中永远不会有超过一个短时间运行的语句,尽管可能有多个长时间运行的语句。让长时间运行的语句阻止其他语句的执行是不可取的,因为对可能需要的等待量没有限制。例如,
如果语句遇到磁盘 I/O 操作或用户级锁(行锁或表锁),它就会被阻塞。阻塞会导致线程组变为未使用状态,所以线程池有回调,保证线程池可以立即在这个组中启动一个新的线程去执行另一条语句。当阻塞的线程返回时,线程池允许它立即重新启动。
有两个队列,一个高优先级队列和一个低优先级队列。事务中的第一条语句进入低优先级队列。如果事务正在进行(它的语句已开始执行),则事务的任何后续语句都会进入高优先级队列,否则进入低优先级队列。启用系统变量会影响队列分配
thread_pool_high_priority_connection
,这会导致会话的所有排队语句进入高优先级队列。非事务存储引擎或事务引擎(如果
autocommit
启用)的语句被视为低优先级语句,因为在这种情况下,每个语句都是一个事务。因此,给定语句InnoDB
和MyISAM
表的混合,线程池优先考虑 forInnoDB
的 语句而不是启用MyISAM
unless 的语句。autocommit
启用后,autocommit
所有语句都是低优先级的。当线程组选择一个排队的语句执行时,它首先在高优先级队列中查找,然后在低优先级队列中查找。如果找到语句,则将其从队列中删除并开始执行。
如果语句在低优先级队列中停留时间过长,线程池将移至高优先级队列。
thread_pool_prio_kickup_timer
系统变量的值 控制移动前的时间。对于每个线程组,每 10ms(每秒 100 个)最多有一个语句从低优先级队列移动到高优先级队列。线程池重用最活跃的线程以更好地利用 CPU 缓存。这是一个对性能有很大影响的小调整。
当线程从用户连接执行语句时,Performance Schema 检测将线程活动计入用户连接。否则,Performance Schema 会将活动计入线程池。
以下是线程组可能有多个线程开始执行语句的条件示例:
一个线程开始执行一条语句,但运行时间足够长以至于被视为停滞。即使第一个线程仍在执行,线程组也允许另一个线程开始执行另一个语句。
一个线程开始执行一条语句,然后被阻塞并将此报告回线程池。线程组允许另一个线程开始执行另一个语句。
一个线程开始执行一条语句,被阻塞,但不会报告它已被阻塞,因为该阻塞不会出现在已使用线程池回调进行检测的代码中。在这种情况下,线程在线程组看来似乎仍在运行。如果该块持续的时间足够长以至于语句被视为停顿,则该组允许另一个线程开始执行另一个语句。
线程池被设计为可扩展到越来越多的连接。它还旨在避免因限制主动执行语句的数量而引起的死锁。重要的是,不向线程池报告的线程不会阻止其他语句的执行,从而导致线程池陷入死锁。此类声明的示例如下:
长时间运行的语句。这些将导致所有资源仅由少数语句使用,并且它们可以阻止所有其他语句访问服务器。
读取二进制日志并将其发送到副本的二进制日志转储线程。这是一种长时间运行的 “语句”,运行时间很长,不应阻止其他语句的执行。
在行锁、表锁、睡眠或任何其他尚未由 MySQL 服务器或存储引擎报告回线程池的阻塞活动上阻塞的语句。
在每种情况下,为了防止死锁,当语句没有快速完成时,它会被移至停滞类别,以便线程组可以允许另一个语句开始执行。通过这种设计,当一个线程执行或阻塞时间过长时,线程池会将线程移至停滞类别,并且对于语句的其余执行,它不会阻止其他语句的执行。
可以出现的最大线程数是 和 的
max_connections
总和
thread_pool_size
。这可能发生在所有连接都处于执行模式并且每个组创建一个额外线程以侦听更多语句的情况下。这不一定是经常发生的状态,但理论上是可能的。