线程池由多个线程组组成,每个线程组管理一组客户端连接。当建立连接时,线程池以轮询的方式将它们分配给线程组。
线程池公开了可用于配置其操作的系统变量
thread_pool_algorithm
:用于调度的并发算法。thread_pool_dedicated_listeners
:在每个线程组中指定一个侦听器线程,以侦听分配给该组的连接的传入语句。thread_pool_high_priority_connection
:如何调度会话的语句执行。thread_pool_longrun_trx_limit
:在使用所有thread_pool_max_transactions_limit
的线程执行之前,等待多长时间才能暂停该组的限制。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 instrumentation 会将线程活动计入用户连接。 否则,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 行(每个线程组一行),则将在第 17 行报告 Admin
线程组统计信息。