线程池调整
本节提供了一些指导原则,可用于根据每秒事务数等指标确定线程池性能的最佳配置。
最重要的是线程池中的线程组数量,可在服务器启动时使用--thread-pool-size
选项设置;运行时不能更改此选项。此选项的推荐值取决于使用的主要存储引擎是 InnoDB
还是 MyISAM
:
-
如果主要存储引擎是
InnoDB
,则线程池大小的推荐值为主机上可用的物理核数,最多为 512。 -
如果主要存储引擎是
MyISAM
,则线程池大小应适中。经常可以看到从 4 到 8 的最佳性能。更高的值往往对性能有轻微但不显著的负面影响。
线程池插件可以处理的并发事务的数量上限由thread_pool_max_transactions_limit
的值确定。该系统变量的推荐初始设置是物理核数乘以 32。根据给定的工作负载,可能需要调整此值;此值的合理上限是预期的最大并发连接数;Max_used_connections
状态变量的值可作为确定此值的指南。一个好的方法是将 thread_pool_max_transactions_limit
设置为该值,然后在观察吞吐量的影响时将其向下调整。
线程组中允许的查询线程的最大数量由 thread_pool_query_threads_per_group
的值确定,该值可以在运行时进行调整。该值和线程池大小的乘积约等于可用于处理查询的线程总数。对于您的应用程序,在 thread_pool_query_threads_per_group
和线程池大小之间取得最佳性能通常意味着要适当地平衡两者之间的关系。对于较长运行时间的查询,thread_pool_query_threads_per_group
的值越大,在工作量包括长运行时间和短运行时间查询时,该线程组中同时执行的所有线程同时阻止较短运行时间查询的可能性就越小。请注意,随着对每个线程组的连接轮询操作的开销增加,当为 thread_pool_query_threads_per_group
使用较小的线程池大小时,也会增大线程池的大小。因此,我们建议将 thread_pool_query_threads_per_group
的起始值设置为 2;将此变量设置为较低的值通常不会带来任何性能优势。
在正常情况下,为了获得最佳性能,我们还建议您将 thread_pool_algorithm
设置为 1 以实现高并发性。
此外,thread_pool_stall_limit
系统变量的值确定阻塞和长时间运行的语句的处理方式。如果将所有阻塞 MySQL Server 的调用都报告给线程池,它将始终知道执行线程是否被阻塞,但情况并非总是如此。例如,阻塞可能发生在未用线程池回调进行instrumented 的代码中。对于这种情况,线程池必须能够识别似乎被阻塞的线程。这是通过由 thread_pool_stall_limit
确定的超时值来实现的,该值可确保服务器不会完全阻塞。thread_pool_stall_limit
的值表示 10 毫秒间隔的数量,因此 600
(最大值)表示 6 秒。
thread_pool_stall_limit
还使线程池能够处理长时间运行的语句。如果允许长时间运行的语句阻塞线程组,则将阻塞与该组关联的所有其他连接,并且直到长时间运行的语句完成,这些连接才无法开始执行。在最坏的情况下,这可能需要几个小时甚至几天时间。否则,长时间运行的语句会产生大量额外开销,因为它们涉及额外的上下文切换,在某些情况下甚至涉及额外的线程创建。
thread_pool_stall_limit
的值应选择为以下值:较长时间运行的语句被视为阻塞的语句。阻塞语句的执行需要很长时间,因为它们涉及额外的上下文切换,甚至在某些情况下还涉及额外的线程创建。另一方面,将 thread_pool_stall_limit
参数设置得太高意味着长时间运行的语句会阻止许多短时间运行的语句执行。等待值较短可使线程更快地启动。较短的值也可避免死锁情况。等待值较长对包含长时间运行语句的工作负载很有用,以免在当前语句执行时启动太多新语句。
假设服务器执行的工作量中,有 99.9%的语句即使在加载时也会在 100ms 内完成,其余语句则均匀地分布在 100ms 和 2 小时之间。在这种情况下,将 thread_pool_stall_limit
设置为 10(10 × 10ms = 100ms)是有意义的。默认值 6(60ms)适用于主要执行非常简单语句的服务器。
可以在运行时更改 thread_pool_stall_limit
参数,以使其适用于服务器工作负载。假设 tp_thread_group_stats
表已启用,则可以使用以下查询来确定执行时阻塞的语句的比例:
SELECT SUM(STALLED_QUERIES_EXECUTED) / SUM(QUERIES_EXECUTED)
FROM performance_schema.tp_thread_group_stats;
此数字应尽可能低。要降低语句阻塞的可能性,请增加thread_pool_stall_limit
的值。
当语句到达时,它可以延迟多长时间才能开始实际执行?假设以下条件适用:
-
低优先级队列中有 200 条语句排队。
-
高优先级队列中有 10 条语句排队。
-
thread_pool_prio_kickup_timer
设置为 10000(10 秒)。 -
thread_pool_stall_limit
设置为 100(1 秒)。
在最坏的情况下,10 条高优先级语句代表 10 个长时间运行的事务。因此,在最坏的情况下,由于始终有正在等待执行的语句,因此不能将任何语句移至高优先级队列。10 秒钟后,新语句有资格移至高优先级队列。但是,在可以将其移动之前,还必须将其前面的所有语句都移动。这可能需要再花费 2 秒钟,因为每秒最多可将 100 条语句移至高优先级队列。现在,当该语句到达高优先级队列时,在它之前可能有许多长时间运行的语句,在最坏的情况下,这些语句中的每一个都可能变得停顿,并且在下一条语句从高优先级队列中取出之前,每一条语句需要 1 秒钟。因此,在这种情况下,新语句开始执行需要 222 秒。
此示例显示了应用程序的最坏情况。如何处理它取决于应用程序。如果应用程序对响应时间有较高要求,则最有可能在更高级别自身限制用户。否则,它可以使用线程池配置参数设置某种最大等待时间。