MySQL · 源码分析 · Performance Schema 初始化过程

​ Performance Schema可以用于监控MySQL server在运行过程中的资源消耗、资源等待等情况。在开启该功能后,MySQL会收集服务器的运行状态,并将信息记录在performance_schema库中对应的Table内。用户可通过查看对应的Table,了解数据库的运行状态。例如,借助performance_schema.metadata_locks表,可以获知当前MySQL中MDL锁的持有情况。借助performance_schema.events_stages_current表,我们可以获知当前DDL语句的执行进度。在MySQL中,开启/关闭Performance Schema均需要重启。该功能由如下参数控制:

参数 取值范围 说明
performance_schema [ON OFF]

本文主要分析实例启动时,Performance Schema模块的初始化过程。以下分析均基于MySQL-8.0.13版本。

一、初始化过程分析

概述

MySQL实例的总入口函数为mysqld_main()。该函数中,与Performance Schema模块初始化相关的函数简化记录如下:


1. int mysqld_main()
2. {
3. ...
4. 1. pre_initialize_performance_schema();
5. ...
6. 2. init_pfs_instrument_array();
7. ...
8. 3. ho_error = handle_early_options();
9. ...
10. 4. initialize_performance_schema();
11. ...
12. 5. init_server_psi_keys();
13. 6. my_thread_global_reinit();
14. ...
15. 7. initialize_performance_schema_acl();
16. ...
17. }

下面对各个函数进行详细分析说明。

1. pre_initialize_performance_schema()


1. void pre_initialize_performance_schema() {
2. pfs_initialized = false;
3. // 初始化内存监控相关的instrument;
4. init_all_builtin_memory_class();
5. // 重置统计信息;
6. PFS_table_stat::g_reset_template.reset();
7. global_idle_stat.reset();
8. global_table_io_stat.reset();
9. global_table_lock_stat.reset();
10. g_histogram_pico_timers.init();
11. global_statements_histogram.reset();

13. THR_PFS = nullptr;
14. for (int i = 0; i < THR_PFS_NUM_KEYS; ++i) {
15. THR_PFS_contexts[i] = nullptr;
16. }
17. }

​ 其中,init_all_builtin_memory_class() 函数负责注册与pfs模块内存监控相关的instrument。该监控无法被用户修改。只要打开了PFS,就会默认开启对PFS模块内存消耗的监控。内存监控的结果可以通过表memory_summary_global_by_event_name进行查看。在实际应用中,由于pfs的表都是内存表,且PFS模块的的内存消耗具有如下特点:

  • 启动时会预分配一定的空间;
  • 运行时,若空间不足,则会申请更多空间
  • 已申请的空间不会进行释放,但会进行复用。

​ 因此,如果负载过大,PFS模块可能会消耗大量内存。以MDL锁监控为例进行说明。如果MDL最大检测数目设置为-1,表示自适应调整:


1. performance_schema_max_metadata_locks = -1;

​ 通过查阅源码可知,此时最大监控的数目为1024*1024,每个mdl锁监控消耗的内存大小为:


1. sizeof(PFS_metadata_lock) = 512bytes

​ 若业务峰值mdl锁到达理论上限,则此时,仅mdl锁监控部分,消耗的内存为:


1. 1024*1024*sizeof(PFS_metadata_lock)=512Mb

​ 由于PFS模块申请的内存只会在重启后释放,因此这样的消耗对对低规格实例可能产生较大影响。实际引用中,用户可借助memory_summary_global_by_event_name表,及时了解具体的内存消耗,以便及时调整对应的参数。

2. init_pfs_instrument_array()

​ 本函数对全局数组Pfs_instr_config_array进行初始化。该数组用于记录用户在配置文件中关于表 setup_instrument 的配置。在配置文件中,用户可以使用如下参数对instrument进行配置:


1. performance-schema-instrument='instrument_name = value'

​ 其中,用户可以通过performance_schema.setup_instruments表中的NAME列,查看支持的instrument。instrument_name支持正则匹配。例如,如下配置可以开启stage/innodb/下所有以alter开头的instrument。


1. performance_schema_instrument='stage/innodb/alter%=YES'

​ 在Pfs_instr_config_array数组中可以记录对同一个instrument的多个配置。生效规则如下:

​ 1、匹配最精确的配置项:

​ 如果通过不同的的正则表达式可以匹配到同一个instrument, 则匹配越精确的,优先级越高。例如,当配置文件中进行如下配置时,wait/lock/metadata/sql/mdl会被设置为打开状态。


1. performance_schema_instrument='%=OFF'
2. performance_schema_instrument='wait/lock/metadata/sql/mdl = ON'

​ 2、相同匹配长度,越靠后优先级越高。

​ 当同一个instrument具有完全相反的配置,而且匹配长度一致时,越靠后者,优先级越高。例如,当配置文件中进行如下配置时,wait/lock/metadata/sql/mdl会被设置为打开状态。


1. performance_schema_instrument='wait/lock/metadata/sql/mdl = OFF'
2. performance_schema_instrument='wait/lock/metadata/sql/mdl = ON'

3. handle_early_options()

​ 在MySQL进行初始化时,部分参数需要被用于初始化过程。本函数用于载入这一类参数。具体的,在sql/sys_vars.cc文件中,所有parse_flag= PARSE_EARLY 的变量,均会在此处被读取。这些参数可以被分为三类:

  • Performance Schema 模块相关参数
  • help 相关参数
  • bootstrap 相关参数

对于Performance Schema模块的参数,可以进一步细分为如下三类:

  • Performance Schema 控制开关

​ 如 performance_schema - pfs内存分配&监控的相关参数

​ 控制监控项数目参数,如:performance_schema_max_stage_classes

​ 控制监控实体参数:如 performance_schema_max_thread_instances

​ 控制记录数目参数 如:performance_schema_events_stages_history_size - 配置参数

​ 对consumers和instrument的配置等。如performance_schema_consumer_thread_instrumentation等

上文提到的Pfs_instr_config_array数组,也是在此处被填充。

4. initialize_performance_schema()

​ 此处为PFS核心的初始化函数,在本函数中,完成了绝大多数的初始化工作。该函数简化后的伪代码如下:


1. int initialize_performance_schema() {
2. ...
3. pfs_enabled = param->m_enabled;//pfs全局状态变量
4. // 1. 配置默认参数
5. pfs_automated_sizing(param);
6. init_timers();// 配置定时器
7. init_event_name_sizing(param);
8. // 2. 注册全局instrument.
9. register_global_classes();
10. // 3. pfs初始化内存分配
11. if (init_sync_class(param->m_mutex_class_sizing, param->m_rwlock_class_sizing,
12. param->m_cond_class_sizing) ||
13. init_thread_class(param->m_thread_class_sizing) ||
14. init_table_share(param->m_table_share_sizing) ||
15. init_table_share_lock_stat(param->m_table_lock_stat_sizing) ||
16. init_table_share_index_stat(param->m_index_stat_sizing) ||
17. init_file_class(param->m_file_class_sizing) ||
18. init_stage_class(param->m_stage_class_sizing) ||
19. init_statement_class(param->m_statement_class_sizing) ||
20. init_socket_class(param->m_socket_class_sizing) ||
21. init_memory_class(param->m_memory_class_sizing) ||
22. init_instruments(param) ||
23. init_events_waits_history_long(
24. param->m_events_waits_history_long_sizing) ||
25. init_events_stages_history_long(
26. param->m_events_stages_history_long_sizing) ||
27. init_events_statements_history_long(
28. param->m_events_statements_history_long_sizing) ||
29. init_events_transactions_history_long(
30. param->m_events_transactions_history_long_sizing) ||
31. init_file_hash(param) || init_table_share_hash(param) ||
32. init_setup_actor(param) || init_setup_actor_hash(param) ||
33. init_setup_object(param) || init_setup_object_hash(param) ||
34. init_host(param) || init_host_hash(param) || init_user(param) ||
35. init_user_hash(param) || init_account(param) ||
36. init_account_hash(param) || init_digest(param) ||
37. init_digest_hash(param) || init_program(param) ||
38. init_program_hash(param) || init_prepared_stmt(param) ||
39. init_error(param)) {
40. /*
41. The performance schema initialization failed.
42. Free the memory used, and disable the instrumentation.
43. */
44. cleanup_performance_schema();
45. return 2;
46. }
47. // 3. setup_consumer表配置
48. if (param->m_enabled) {
49. /** Default values for SETUP_CONSUMERS */
50. flag_events_stages_current =
51. param->m_consumer_events_stages_current_enabled;
52. flag_events_stages_history =
53. param->m_consumer_events_stages_history_enabled;
54. flag_events_stages_history_long =
55. param->m_consumer_events_stages_history_long_enabled;
56. flag_events_statements_current =
57. param->m_consumer_events_statements_current_enabled;
58. flag_events_statements_history =
59. param->m_consumer_events_statements_history_enabled;
60. flag_events_statements_history_long =
61. param->m_consumer_events_statements_history_long_enabled;
62. flag_events_transactions_current =
63. param->m_consumer_events_transactions_current_enabled;
64. flag_events_transactions_history =
65. param->m_consumer_events_transactions_history_enabled;
66. flag_events_transactions_history_long =
67. param->m_consumer_events_transactions_history_long_enabled;
68. flag_events_waits_current = param->m_consumer_events_waits_current_enabled;
69. flag_events_waits_history = param->m_consumer_events_waits_history_enabled;
70. flag_events_waits_history_long =
71. param->m_consumer_events_waits_history_long_enabled;
72. flag_global_instrumentation =
73. param->m_consumer_global_instrumentation_enabled;
74. flag_thread_instrumentation =
75. param->m_consumer_thread_instrumentation_enabled;
76. flag_statements_digest = param->m_consumer_statement_digest_enabled;
77. } else {
78. flag_events_stages_current = false;
79. flag_events_stages_history = false;
80. flag_events_stages_history_long = false;
81. flag_events_statements_current = false;
82. flag_events_statements_history = false;
83. flag_events_statements_history_long = false;
84. flag_events_transactions_current = false;
85. flag_events_transactions_history = false;
86. flag_events_transactions_history_long = false;
87. flag_events_waits_current = false;
88. flag_events_waits_history = false;
89. flag_events_waits_history_long = false;
90. flag_global_instrumentation = false;
91. flag_thread_instrumentation = false;
92. flag_statements_digest = false;
93. }

95. pfs_initialized = true;
96. // 4. 获取相关接口的实现函数
97. if (param->m_enabled) {
98. install_default_setup(&pfs_thread_bootstrap);
99. *thread_bootstrap = &pfs_thread_bootstrap;
100. *mutex_bootstrap = &pfs_mutex_bootstrap;
101. *rwlock_bootstrap = &pfs_rwlock_bootstrap;
102. *cond_bootstrap = &pfs_cond_bootstrap;
103. *file_bootstrap = &pfs_file_bootstrap;
104. *socket_bootstrap = &pfs_socket_bootstrap;
105. *table_bootstrap = &pfs_table_bootstrap;
106. *mdl_bootstrap = &pfs_mdl_bootstrap;
107. *idle_bootstrap = &pfs_idle_bootstrap;
108. *stage_bootstrap = &pfs_stage_bootstrap;
109. *statement_bootstrap = &pfs_statement_bootstrap;
110. *transaction_bootstrap = &pfs_transaction_bootstrap;
111. *memory_bootstrap = &pfs_memory_bootstrap;
112. *error_bootstrap = &pfs_error_bootstrap;
113. *data_lock_bootstrap = &pfs_data_lock_bootstrap;
114. *system_bootstrap = &pfs_system_bootstrap;
115. }

117. /* Initialize plugin table services */
118. init_pfs_plugin_table();

120. return 0;
121. }

部分重点函数详细介绍如下

4.1 pfs_automated_sizing(param)

​ 在大多数情况下,用户只会对某些参数下进行设置。对于其他参数不作调整。因此,PFS会根据当前服务器的table_def_size、table_cache_size、max_connections三个参数,来自动选择合适的配置。判断条件如下:


1. /**
2. MAX_CONNECTIONS_DEFAULT = 151
3. TABLE_DEF_CACHE_DEFAULT = 400
4. TABLE_DEF_CACHE_DEFAULT = 4000
5. */
6. static PFS_sizing_data *estimate_hints(PFS_global_param *param) {
7. if (max_connections <= MAX_CONNECTIONS_DEFAULT) &&
8. (table_def_size <= TABLE_DEF_CACHE_DEFAULT) &&
9. (table_cache_size <= TABLE_OPEN_CACHE_DEFAULT)) {
10. return &small_data;
11. }
12. if ((max_connections <= MAX_CONNECTIONS_DEFAULT * 2) &&
13. (table_def_size <= TABLE_DEF_CACHE_DEFAULT * 2) &&
14. (table_cache_size <= TABLE_OPEN_CACHE_DEFAULT * 2)) {
15. return &medium_data;
16. }
17. return &large_data;
18. }

​ small、medium和large对应的参数如下,若用户未指定的某参数的具体值,则根据上述的判断条件,此处会将其设置为如下默认值。


1. PFS_sizing_data small_data = {
2. /* History sizes */
3. 5, 100, 5, 100, 5, 100, 5, 100,
4. /* Digests */
5. 1000,
6. /* Session connect attrs. */
7. 512};

9. PFS_sizing_data medium_data = {
10. /* History sizes */
11. 10, 1000, 10, 1000, 10, 1000, 10, 1000,
12. /* Digests */
13. 5000,
14. /* Session connect attrs. */
15. 512};

17. PFS_sizing_data large_data = {
18. /* History sizes */
19. 10, 10000, 10, 10000, 10, 10000, 10, 10000,
20. /* Digests */
21. 10000,
22. /* Session connect attrs. */
23. 512};

​ 映射关系为:


1. struct PFS_sizing_data {
2. /** Default value for @c PFS_param.m_events_waits_history_sizing. */
3. ulong m_events_waits_history_sizing;
4. /** Default value for @c PFS_param.m_events_waits_history_long_sizing. */
5. ulong m_events_waits_history_long_sizing;
6. /** Default value for @c PFS_param.m_events_stages_history_sizing. */
7. ulong m_events_stages_history_sizing;
8. /** Default value for @c PFS_param.m_events_stages_history_long_sizing. */
9. ulong m_events_stages_history_long_sizing;
10. /** Default value for @c PFS_param.m_events_statements_history_sizing. */
11. ulong m_events_statements_history_sizing;
12. /** Default value for @c PFS_param.m_events_statements_history_long_sizing. */
13. ulong m_events_statements_history_long_sizing;
14. /** Default value for @c PFS_param.m_events_transactions_history_sizing. */
15. ulong m_events_transactions_history_sizing;
16. /** Default value for @c PFS_param.m_events_transactions_history_long_sizing.
17. */
18. ulong m_events_transactions_history_long_sizing;
19. /** Default value for @c PFS_param.m_digest_sizing. */
20. ulong m_digest_sizing;
21. /** Default value for @c PFS_param.m_session_connect_attrs_sizing. */
22. ulong m_session_connect_attrs_sizing;
23. };

4.2 register_global_classes();

​ 注册全局的instrument监控项。在MySQL中,全局控制的instrument项有如下六种,分别解释如下:

NAME 解释
global_table_io_class TABLE IO相关的instrument
global_table_lock_class 与TABLE 锁相关的instrument
global_idle_class 空闲事件相关的instrument
global_metadata_class 与metadata_lock相关的instrument
global_error_class 错误信息相关的instrument
global_transaction_class 事务相关的instrument

​ 在对上述六类全局instrument进行注册后,会根据Pfs_instr_config_array数组对相应的instrument进行配置。

4.3 内存初始化

​ 此处完成对PFS所需要的内存进行初始化。内存分配可以分为三大类:

​ 1、init_xxx_class()

​ 该内存用于存储xxx类的instrument的配置项。这一类内存不会随着负载的增加而增大。内存大小与xxx类的 instruments的最大数目有关。此类内存在memory_summary_global_by_event_name表中记录为xxx_class类型。

​ 2、init_events_xxx_history_long()

​ 该内存用于记录全局的xxx类的历史信息。该信息记录在表EVENTS_xxx_HISTORY_LONG中。该类型的内存在表memory_summary_global_by_event_name中记录为:events_xxx_history_long。此类型的内存在pfs启动时分配,不会随着负载增加而增大。

​ 3、instrument相关的内存

​ 用于记录instrument所收集的信息。此类型内存多使用container内存模型,具体可以参见扩展阅读文章。此类型的内存大小会随着负载增加而增加。这也是PFS模块内存消耗的主要来源。

4.4 SETUP_CONSUMERS表配置

​ 对表SETUP_CONSUMERS中的配置项进行配置。PFS采用生产-消费模型。当关闭对应的CONSUMERS时,对应的instrument也不会再收集相关的信息。当用户未进行人为配置时,默认的配置为:


1. mysql> select * from performance_schema.setup_consumers;
2. +----------------------------------+---------+
3. | NAME                             | ENABLED |
4. +----------------------------------+---------+
5. | events_stages_current            | NO      |
6. | events_stages_history            | NO      |
7. | events_stages_history_long       | NO      |
8. | events_statements_current        | YES     |
9. | events_statements_history        | YES     |
10. | events_statements_history_long   | NO      |
11. | events_transactions_current      | YES     |
12. | events_transactions_history      | YES     |
13. | events_transactions_history_long | NO      |
14. | events_waits_current             | NO      |
15. | events_waits_history             | NO      |
16. | events_waits_history_long        | NO      |
17. | events_parallel_query_current    | YES     |
18. | events_parallel_query_history    | YES     |
19. | global_instrumentation           | YES     |
20. | thread_instrumentation           | YES     |
21. | statements_digest                | YES     |
22. +----------------------------------+---------+
23. 17 rows in set (0.04 sec)

​ 用户可通过在配置文件或启动参数中,设置对应的配置项。


1. performance_schema_consumer_xxx = ON/OFF

​ 启动后,用户也可通过update操作对表performance_schema.setup_consumers进行更新,来修改对应配置。


1. UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME IN ('global_instrumentation');

4.5 注册PFS相关的接口到PSI中。

​ 在MySQL中定义了诸多PFS相关的接口。在peferformance模块对这些接口进行了实现。此处将pfs中对应接口的实现函数进行返回。

5. init_server_psi_keys()

​ 此函数注册所有除4.2中全局instrument以外的所有监控项。在具体实现上,通过调用mysql_xxx_register()函数实现对xxx类instrement的注册。以thread类为例,其注册instrument的函数调用链路为:


1. init_server_psi_keys()
2. -- mysql_thread_register()
3. -- inline_mysql_thread_register()
4. -- PSI_THREAD_CALL(register_thread)
5. -- pfs_register_thread_v1()
6. -- register_thread_class()

6. my_thread_global_reinit()

​ 至此已经完成了对PFS模块的初始化过程。由于当前线程中也存在一些需要检测的线程相关的锁对象,这里使用重新创建的方式,来监测这些锁对象。由于目前为止都是单个线程在运行,没有新的线程被创建,所以这里是线程安全的。

7. initialize_performance_schema_acl

​ 初始化performance_schema库的权限。由于无论是否启用Performance Schema功能,该库下面的表始终是可见的。因此需要进行权限控制。

二、功能拓展

​ 在了解Performance Schema的初始化过程后,便可以通过修改源码的形式,实现一些有趣的功能。

1、修改pfs默认配置

​ 当我们在默认状态下开启Performance Schema功能时,pfs会运行在一套默认参数下。此处可以通过修改源码的形式,配置多套默认参数。以方便不同规格实例进行切换。具体方法如下:

​ 在文件sql/sys_vars.cc中,新增变量performance_schema_params_control;


1. static Sys_var_long Sys_pfs_params_control(
2. "performance_schema_params_control,
3. "control which params grop will be used."
4. " Use 0,1,2 to choose different param_group.",
5. READ_ONLY GLOBAL_VAR(pfs_param.m_params_control),
6. CMD_LINE(REQUIRED_ARG), VALID_RANGE(0, 2),
7. DEFAULT(0), BLOCK_SIZE(1), PFS_TRAILING_PROPERTIES);

​ 在文件storage/perfschema/pfs_server.h的PFS_global_param结构体中,新增变量如下变量:


1. struct PFS_global_param {
2. ...
3. /* control which param_grop will be used.*/
4. long m_params_control;
5. ...
6. }

​ 结合上述分析,在PFS初始化过程中,参数配置主要是由pfs_automated_sizing()函数完成的。因此,我们仅需要在该函数之后,对默认参数进行覆盖即可。伪代码如下:


1. int initialize_performance_schema() {
2. ...
3. pfs_automated_sizing(param);
4. // 覆盖默认参数
5. switch (param->m_params_control)
6. {
7. case 0:
8. param_group_1;
9. break;
10. case 1:
11. param_group_2;
12. break;
13. case 2:
14. param_group_3;
15. break;
16. default:
17. default_param_group;
18. break;
19. }
20. ...
21. return 0;
22. }

​ 通过这种方式,可以通过该参数,控制pfs启动时选择不同组的配置参数,方便灵活配置。

2、修改默认监控项

​ 在PFS启动时,若用户未进行指定,会默认开启部分监控。在了解了PFS初始化的过程后,就可以通过修改源码的形式,修改setup_instrument表中的默认状态。

​ 根据上文介绍,instrument的配置项是记录在Pfs_instr_config_array数组中的。而该数组是由函数handle_early_options()进行填充的。因此,可以通过在handle_early_options()之后,修改Pfs_instr_config_array的值,便可以对instrument的状态进行配置。例如,如果希望关闭所有监控项,仅开启mdl锁的监控,可以进行如下修改:


1. int mysqld_main()
2. {
3. ...
4. ho_error = handle_early_options();
5. add_pfs_instr_to_array("%", "OFF");
6. add_pfs_instr_to_array("wait/lock/metadata/sql/mdl", "ON");
7. ...
8. }

扩展阅读

  1. MySQL · 资源管理 · PFS内存管理分析
  2. MySQL · 引擎特性 · Performance_schema 内存分配
  3. MySQL 8.0 Reference Manual · Performance Schema

原文:http://mysql.taobao.org/monthly/2021/09/03/