本节提供了确定线程池性能的最佳配置的指南,以每秒事务数等指标衡量。
线程池中线程组的数量是最重要的,使用 --thread-pool-size
选项在服务器启动时设置;这不能在运行时更改。该选项的推荐值取决于主要存储引擎是 InnoDB
还是 MyISAM
:
-
如果主要存储引擎是
InnoDB
,则线程池大小的推荐值是主机机器上的物理核心数,最高为 512。 -
如果主要存储引擎是
MyISAM
,则线程池大小应该较小。通常情况下,性能最佳的值在 4 到 8 之间。较高的值对性能的影响微弱但不明显。
线程池插件可以处理的并发事务的上限是由 thread_pool_max_transactions_limit
的值确定的。该系统变量的初始设置建议为物理核心数乘以 32。你可能需要根据工作负载调整该值;合理的上限是预期的最大并发连接数;Max_used_connections
状态变量的值可以作为确定该值的指南。
每个线程组中允许的查询线程的最大数量是由 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
的值确定的超时来实现的,该值表示 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秒才能开始执行新语句。
这个示例展示了应用程序的最坏情况。如何处理它取决于应用程序。如果应用程序对响应时间有高要求,那么它应该在更高级别上限制用户。否则,它可以使用线程池配置参数来设置某种最大等待时间。