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