本节提供有关确定线程池性能最佳配置的指南,性能使用每秒事务数等指标衡量。
最重要的因素是线程池中的线程组数量,可以在服务器启动时使用 --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 服务器的调用都报告给线程池,它将始终知道何时执行线程被阻塞,但这并不总是真的。例如,阻塞可能发生在未经线程池回调调用的代码中。对于此类情况,线程池必须能够识别似乎被阻塞的线程。这是通过由 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% 的语句在 100 毫秒内完成,即使服务器处于负载状态,其余语句在 100 毫秒到 2 小时之间均匀分布。在这种情况下,将 thread_pool_stall_limit
设置为 10 (10 × 10 毫秒 = 100 毫秒) 是有意义的。默认值为 6 (60 毫秒),适用于主要执行非常简单的语句的服务器。
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 秒才能开始执行。
此示例显示了应用程序的最坏情况。如何处理它取决于应用程序。如果应用程序对响应时间有很高的要求,那么它很可能应该在更高级别上自行限制用户。否则,它可以使用线程池配置参数来设置某种最大等待时间。