MySQL · 参数故事 · max_prepared_stmt_count

Author: zhongbei

前情提要

SysBench压测过程经常遇见max_prepared_stmt_count过小的问题,本文分析SysBench压测中的prepare语句数量,给SysBench压测过程max_prepared_stmt_count参数的设置提供依据。

参数背景

SysBench压测过程会产生prepare语句,主要由db-ps-mode选项控制,该选项取值为{auto, disable},默认值为auto。取值为auto时,允许使用prepare语句。取值为disable时,表示禁用prepare语句。SysBench压测过程可能会产生大量的prepare语句,并且可能会超出max_prepared_stmt_count参数限制,出现报错:

  1. FATAL: MySQL error: 1461 "Can't create more than max_prepared_stmt_count statements (current value: 16382)"

为此,在SysBench压测过程中,我们需要调整max_prepared_stmt_count参数以满足测试需求。

max_prepared_stmt_count参数限制了MySQL中prepare语句的数量,超过max_prepared_stmt_count数量后不能准备新的prepare语句,需要等之前的prepare语句被释放。该参数是一个全局动态参数,官方默认值为16382,取值范围为[0, 4194304]。关于参数的详细描述,可参见文档

那么调整这个参数会产生什么风险?

  • 调大的风险:一个prepare语句最少需要占用8k的内存,prepare语句太多会占用过多的实例内存资源,增加MySQL OOM的风险。

  • 调小的风险:风险较小,现有prepare语句不受影响,超过max_prepared_stmt_count数量后不能准备新的prepare语句,需要等之前的prepare语句被释放。

SysBench源码分析

在SysBench测试场景下,如何根据需要确定max_prepared_stmt_count参数取值?

下面以SysBench 1.0.20为例(阿里云RDS官网推荐的测试版本,不同版本会有差异),分析了SysBench的代码。在SysBench测试场景下,prepare语句的数量主要与SysBench的测试参数相关,涉及三个参数:表数量、线程数、测试模型。以oltp_read_only测试模型为例,分析prepare语句的数量,最后给出prepare语句的计算公式,给max_prepared_stmt_count参数设置提供依据。

  1. -- ----------------------------------------------------------------------
  2. -- Read-Only OLTP benchmark
  3. -- ----------------------------------------------------------------------
  4. require("oltp_common")
  5. function prepare_statements()
  6. prepare_point_selects() -- 每张表执行一次
  7. if not sysbench.opt.skip_trx then
  8. prepare_begin() -- 每个线程执行一次
  9. prepare_commit() -- 每个线程执行一次
  10. end
  11. if sysbench.opt.range_selects then
  12. prepare_simple_ranges() -- 每张表执行一次
  13. prepare_sum_ranges() -- 每张表执行一次
  14. prepare_order_ranges() -- 每张表执行一次
  15. prepare_distinct_ranges() -- 每张表执行一次
  16. end
  17. end

SysBench中prepare语句在prepare_statements()函数中,其中prepare_begin 、prepare_commit每个线程执行一次:

  1. function prepare_begin()
  2. stmt.begin = con:prepare("BEGIN")
  3. end
  4. function prepare_commit()
  5. stmt.commit = con:prepare("COMMIT")
  6. end

prepare_point_selects、prepare_simple_ranges、prepare_sum_ranges、prepare_order_ranges、prepare_distinct_ranges每个线程每张表都需要执行一次:

  1. function prepare_point_selects()
  2. prepare_for_each_table("point_selects")
  3. end
  4. function prepare_simple_ranges()
  5. prepare_for_each_table("simple_ranges")
  6. end
  7. function prepare_sum_ranges()
  8. prepare_for_each_table("sum_ranges")
  9. end
  10. function prepare_order_ranges()
  11. prepare_for_each_table("order_ranges")
  12. end
  13. function prepare_distinct_ranges()
  14. prepare_for_each_table("distinct_ranges")
  15. end

那么每个线程对每个表就需要执行5个prepare语句,再加上线程本身需要执行begin、commit的2个prepare语句,可以得出oltp_read_only测试模型需要的prepare语句总数计算公式为:

  1. read_only_ps_total = 线程数 * 表数量 * 5 + 线程数 * 2

然而,需要注意的是,MySQL官方并不支持begin类型的prepare语句!见文档。在执行begin类型的prepare语句时会失败,所以需要从上述公式中减去一个“线程数”,于是oltp_read_only测试模型正确的prepare语句总数公式为:

  1. read_only_ps_total = 线程数 * 表数量 * 5 + 线程数

SysBench prepare语句计算公式

类似的可以分析oltp_write_only、oltp_read_write、oltp_insert的源码,在此总结了不同的测试模型prepare语句计算公式,计算汇总在如下表格:

测试模型prepare语句数量计算公式
oltp_read_only线程数  表数量  5 + 线程数
oltp_write_only线程数  表数量  4 + 线程数
oltp_read_write线程数  表数量  9 + 线程数
oltp_insert0. (oltp_insert场景没有prepare语句)

公式正确性验证

利用show global status like ’Prepared_stmt_count’;命令,可以获取SysBench测试过程中实际的prepare语句数量。注意:Prepared_stmt_count变量表示当前MySQL总的prepare语句数量,这是一个准确值,当SysBench测试结束,prepare语句结束,该值会变成0。

  1. mysql> show global status like 'prepared_stmt_count';
  2. +---------------------+-------+
  3. | Variable_name | Value |
  4. +---------------------+-------+
  5. | Prepared_stmt_count | 0 |
  6. +---------------------+-------+

测试了几组数据,结果如下:

测试模型表数量线程数prepare语句实际值根据公式计算的prepare语句数量
oltp_read_only503280328032
 100321603216032
 100643206432064
oltp_write_only503264326432
 100321283212832
 100642566425664
oltp_read_write50321443214432
 100322883228832
 100645766457664
oltp_insert503200
 1003200
 1006400

可以看到,prepare语句实际值和理论值完全吻合,证明了公式的正确性。

利用show global status like ’com_stmt%’;命令,可以获取prepare语句累计数量。

  1. mysql> show global status like 'com_stmt%';
  2. +-------------------------+-------+
  3. | Variable_name | Value |
  4. +-------------------------+-------+
  5. | Com_stmt_execute | 0 |
  6. | Com_stmt_close | 0 |
  7. | Com_stmt_fetch | 0 |
  8. | Com_stmt_prepare | 0 |
  9. | Com_stmt_reset | 0 |
  10. | Com_stmt_send_long_data | 0 |
  11. | Com_stmt_reprepare | 0 |
  12. +-------------------------+-------+

其中,Com_stmt_prepare表示累计prepare语句数量,Com_stmt_close表示累计关闭的prepare语句数量。Com_stmt_prepare中包含了prepare语句执行失败的数量,感兴趣的读者可以利用Com_stmt_prepare - Com_stmt_close来验证SysBench测试过程中,begin语句是否prepare失败。笔者已经对此进行了验证。

参数设置建议

根据上述分析,SysBench压测中prepare语句数量与SysBench的测试参数相关,涉及三个参数:表数量、线程数、测试模型。只要max_prepared_stmt_count参数大于等于SysBench测试的prepare语句理论值,就可以保证满足测试要求。

不同的测试参数配置会对prepare语句数量有着较大的影响,同时max_prepared_stmt_count参数不应该影响用户正常业务,对于有SysBench测试需求的用户,根据本文计算公式来提供max_prepared_stmt_count参数的设置依据,由用户自行根据测试需求来设置这个参数。同时建议客户在测试完毕后,调小max_prepared_stmt_count参数。对于业务中的prepare语句数量,由用户自己评估来设置max_prepared_stmt_count参数。

原文:http://mysql.taobao.org/monthly/2023/03/04/