MySQL中Performance Schema库的详解(上)

使用Performance Schema

将通过一些示例来演示如何使用Performance Schema解决常见的故障案例。

检查SQL语句

要启用语句检测,需要启用statement类型的插桩,如表1所述

表1:statement类型的插桩及其描述

插装类 描述
statement/sp SQL语句,如SELECT,或者CREATE TABLE
statement/scheduler 存储过程控制事件调度器命令
statement/sql 事件调度器
statement/com 命令,如quit、kill、DROP DATABASE,或者 Binlog Dump,有些命令是用户不可用的,只能由mysqld进程调用。
statement/abstract 包括四类命令:clone、Query、new packet和relay log

常规SQL语句

Performance Schema将语句指标存储在events_statements_currentevents_statements_historyevents_statements_history_long表中。这三个表具有相同的结构

接下来我用events_statements_history表来举例,下列注释由AI生成,大家可以参考一下。

复制代码
查询语句# 
select * from events_statements_history \G

			   THREAD_ID: 47  # 执行该语句的MySQL线程的ID。
               EVENT_ID: 351  # 该事件的唯一标识符。
               END_EVENT_ID: 351  # 对于此事件,开始和结束事件ID相同,表示这是一个单一事件。
               EVENT_NAME: statement/sql/select  # 事件类型,这里是SQL SELECT语句。
               SOURCE: init_net_server_extension.cc:94  # 事件源代码的位置,可能不是直接相关,因为通常这指向MySQL内部代码位置。
            TIMER_START: 327261072671000  # 事件开始的时间戳(纳秒)。
              TIMER_END: 327261372405000  # 事件结束的时间戳(纳秒)。
             TIMER_WAIT: 299734000  # 事件等待的总时间(纳秒)。
              LOCK_TIME: 116000000  # 语句获取锁所需的时间(纳秒)。
               SQL_TEXT: select * from events_transactions_history  where EVENT_ID=22  # 实际执行的SQL语句。
                 DIGEST: 54f0cbe2356984985c1af5f13f557284349025f640ed3c8b4b959f2b62a6e60a  # SQL语句的摘要(一种哈希值)。
            DIGEST_TEXT: SELECT * FROM `events_transactions_history` WHERE `EVENT_ID` = ?  # 摘要的文本表示,使用了占位符。
         CURRENT_SCHEMA: performance_schema  # 当前使用的数据库模式(schema)。
            OBJECT_TYPE: NULL  # 事件关联的对象类型(如果有的话),这里为空。
          OBJECT_SCHEMA: NULL  # 事件关联对象的模式(schema),这里为空。
            OBJECT_NAME: NULL  # 事件关联对象的名称,这里为空。
  OBJECT_INSTANCE_BEGIN: NULL  # 事件关联对象的实例ID(如果有的话),这里为空。
            MYSQL_ERRNO: 0  # MySQL错误代码,0表示没有错误。
      RETURNED_SQLSTATE: NULL  # SQLSTATE错误代码,NULL表示没有错误。
           MESSAGE_TEXT: NULL  # 错误或警告的消息文本,这里为空。
                 ERRORS: 0  # 发生的错误数量。
               WARNINGS: 0  # 发生的警告数量。
          ROWS_AFFECTED: 0  # 被语句影响的行数(对于SELECT语句,通常为0)。
              ROWS_SENT: 0  # 发送给客户端的行数(对于SELECT语句,这通常与返回的行数相关)。
          ROWS_EXAMINED: 22  # 语句执行过程中检查(读取)的行数。
CREATED_TMP_DISK_TABLES: 0  # 创建的临时磁盘表数量。
     CREATED_TMP_TABLES: 0  # 创建的临时表数量(包括内存表和磁盘表)。
       SELECT_FULL_JOIN: 0  # 执行全连接操作的次数。
 SELECT_FULL_RANGE_JOIN: 0  # 使用全范围扫描的连接次数。
           SELECT_RANGE: 0  # 使用范围扫描的次数。
     SELECT_RANGE_CHECK: 0  # 范围检查的次数。
            SELECT_SCAN: 1  # 全表扫描的次数。
      SORT_MERGE_PASSES: 0  # 排序合并传递的次数。
             SORT_RANGE: 0  # 使用范围排序的次数。
              SORT_ROWS: 0  # 排序的行数。
              SORT_SCAN: 0  # 使用扫描进行排序的次数。
          NO_INDEX_USED: 1  # 没有使用索引的情况。
     NO_GOOD_INDEX_USED: 0  # 没有使用良好索引的情况(基于优化器的判断)。
       NESTING_EVENT_ID: NULL  # 嵌套事件的ID,这里为空表示没有嵌套事件。
     NESTING_EVENT_TYPE: NULL  # 嵌套事件的类型,这里为空。
    NESTING_EVENT_LEVEL: 0  # 嵌套事件的级别,0表示没有嵌套。
           STATEMENT_ID: 342  # 语句的ID,用于标识该语句。

表2列出了可以用于标识需要优化查询的指标的列。用了4列只是为了查询结果排序。重要性这一列表示该指标的重要程度

表2:event statement history表中可以作为优化指标的列

描述 重要性
CREATED_TMP_DISK_TABLES 查询创建的磁盘临时表的数量。有两个选项可以解决这个问题:优化查询或增加内存临时表的最大大小
CREATED_TMP_TABLES 查询创建的内存临时表的数量。使用内存中临时表本身并不是坏事。但是,如果基础表数量增加,超过了内存临时表的空间,可能会转换为磁盘临时表。最好能提前为这种情况做好准备
SELECT_FULL_ JOIN 因为没有合适的索引,所以导致JOIN执高行了全表扫描。除非表非常小,否则需要重新考虑索引的设计
SELECT_FULL_RANGE_JOIN JOIN 操作是否使用了被引用表的范围搜索
SELECT_RANGE JOIN操作是否使用了范围搜索来解析第一个表中的行
SELECT_RANGE_CHECK 如果JOIN操作没有索引,则会检查每行之后的键。如果该值大于零,则需要重新考虑表中的索引设计
SELECT_SCAN JOIN操作是否对第一个表执行了全表扫描,如果第一个表很大则会是一个问题
SORT_MERGE_PASSES 排序必须执行的合并过程数。如果该值大于零且查询性能较低,则可能需要增加sort_buffer_size的值。
SORT_RANGE 是否使用的是范围排序。
SORT_ROWS 排序的行数。如果排序的行数比返回的行数多,则可能需要优化查询。
SORT_SCAN 排序是否是通过扫描表完成的。除非有意选择表中的所有行而不使用索引。
NO_INDEX_USED 查询没有使用索引。高值表示可能存在问题,除非表非常小。
NO_GOOD_INDEX_USED 查询所用的索引不是最合适的。如果该值大于零,则需要重新考虑表的索引设计。

要找出哪些语句需要优化,可以选择上述列中的任何一列,并将其与0进行比较。

单个查询语句

复制代码
SELECT THREAD_ID, SQL_TEXT, ROWS_SENT, ROWS_EXAMINED, CREATED_TMP_TABLES, 
NO_INDEX_USED, NO_GOOD_INDEX_USED  FROM performance_schema.events_statements_history_long  
WHERE NO_INDEX_USED > 0 OR NO_GOOD_INDEX_USED > 0;

具体解释如下:

复制代码
-- 从performance_schema数据库的events_statements_history_long表中查询数据
SELECT 
    THREAD_ID,          -- 线程ID,代表执行SQL语句的数据库线程
    SQL_TEXT,           -- SQL语句的文本内容,即实际执行的SQL查询
    ROWS_SENT,          -- 查询返回给客户端的行数
    ROWS_EXAMINED,      -- 查询执行过程中检查(读取)的行数,可能包括未返回给客户端的行
    CREATED_TMP_TABLES, -- 查询过程中创建的临时表的数量
    NO_INDEX_USED,      -- 指示查询是否未使用索引(1表示是,0表示否)
    NO_GOOD_INDEX_USED  -- 指示查询是否未使用合适的索引(1表示是,0表示否,或者查询使用了索引但不是最佳选择)
FROM 
    performance_schema.events_statements_history_long  -- 数据源表,存储历史SQL语句执行事件的信息
WHERE 
    NO_INDEX_USED > 0    -- 筛选条件,查询未使用索引的事件
    OR NO_GOOD_INDEX_USED > 0;  -- 或者查询未使用合适索引的事件

查询所有有问题的语句

复制代码
mysql> SELECT 
    ->     THREAD_ID,
    ->     SQL_TEXT,
    ->     ROWS_EXAMINED,
    ->     ROWS_SENT,
    ->     ROWS_AFFECTED,
    ->     ERRORS,
    ->     CREATED_TMP_DISK_TABLES,
    ->     CREATED_TMP_TABLES,
    ->     SELECT_FULL_JOIN,
    ->     SELECT_FULL_RANGE_JOIN,
    ->     SELECT_RANGE,
    ->     SELECT_RANGE_CHECK,
    ->     SELECT_SCAN,
    ->     SORT_MERGE_PASSES,
    ->     SORT_RANGE,
    ->     SORT_ROWS,
    ->     NO_INDEX_USED,
    ->     NO_GOOD_INDEX_USED 
    -> FROM 
    ->     performance_schema.events_statements_history_long
    -> WHERE 
    ->     (ROWS_EXAMINED > ROWS_SENT) OR
    ->     (ROWS_EXAMINED > ROWS_AFFECTED) OR
    ->     (ERRORS > 0) OR
    ->     (CREATED_TMP_DISK_TABLES > 0) OR
    ->     (CREATED_TMP_TABLES > 0) OR
    ->     (SELECT_FULL_JOIN > 0) OR
    ->     (SELECT_FULL_RANGE_JOIN > 0) OR
    ->     (SELECT_RANGE > 0) OR
    ->     (SELECT_RANGE_CHECK > 0) OR
    ->     (SELECT_SCAN IS NOT NULL AND SELECT_SCAN > 0) OR
    ->     (SORT_MERGE_PASSES > 0) OR
    ->     (SORT_RANGE > 0) OR
    ->     (SORT_ROWS > 0) OR
    ->     (NO_INDEX_USED > 0) OR
    ->     (NO_GOOD_INDEX_USED > 0);

使用sys schema查询

sys schema提供了可用于查找有问题语句的视图

  • statements_with_errors_or_warnings列出了带有错误和警告的所有语句
  • statements_with_full_table_scans列出了需要全表扫描的所有语句

例如:

复制代码
use sys
select * from statements_with_errors_or_warnings \G

更多查询优化语句的视图表如表3所示

表3:可用于查找需要优化的语句的视图

视图 描述
statement_analysis 具有聚合统计信息的规范化语句视图,按每个规范化语句的总执行时间排序。类似于events_statements,但summary_by_digest表没有那么详细
statements_with_errors_or_warnings 所有引起错误或警告的规范化语句
statements_with_full_table_scans 所有执行了全表扫描的规范化语句
statements_with_runtimes_in_95th_percentile 所有平均执行时间在前95%的规范化语句(注意:此条在图片文字文本中略有出入,但根据上下文理解为此意)
statements_with_sorting 所有执行了排序的规范化语句。该视图包括各种类型的排序
statements_with_temp_tables 所有使用了临时表的规范化语句

预处理语句

这一段引用的高性能MySQL第4版

prepared_statements_instances表包含服务器中存在的所有预处理语句。它和events_statements_[current|history|history_long]表有相同的统计数据,此外还有关于预处理语句所属的线程以及该语句被执行了多少次的信息。和events_statements_[current|history|history_long]表不同的是,统计数据是累加的,这个表包含所有语句执行的总量。

要启用预处理语句检测,需要启用表3-6所示的插桩。

​ 表4:启用预处理语句检测的插桩

一旦启用预处理语句功能,一个预处理好的语句就可以多次执行:

然后查看诊断结果:

注意,只有在服务器中存在的预处理语句才能在prepared_statements_instances表中查到统计信息。一旦预处理语句被删除,就不能再访问这些统计信息了:

存储例程

使用performance_schema可以检索有关存储例程如何执行的信息:例如,IF...ELSE流控制语句的哪个分支被选择了,或者是否调用了错误处理程序。

要启用存储例程检测,需要启用匹配'statement/sp/%'模式的插桩。statement/sp/stmt插桩负责例程内部调用的语句,而其他插桩则负责跟踪事件,例如进入或离开过程、循环或任何其他控制指令。

例如:

复制代码
-- 定义存储过程的创建者为 'root'@'localhost'
CREATE DEFINER='root'@'localhost' PROCEDURE `sp_test`(val int)
BEGIN
    -- 声明一个继续处理程序,用于处理特定的错误代码
    DECLARE CONTINUE HANDLER FOR 1364, 1048, 1366
    BEGIN
        -- 尝试向表 t1 插入一个字符串值,如果违反唯一性约束则忽略错误
        INSERT IGNORE INTO t1 VALUES('Some string');
        
        -- 获取堆栈诊断信息,将返回的SQL状态码存储在变量 @stacked_state 中
        GET STACKED DIAGNOSTICS CONDITION 1 @stacked_state = RETURNED_SQLSTATE;
        
        -- 获取堆栈诊断信息,将错误消息文本存储在变量 @stacked_msg 中
        GET STACKED DIAGNOSTICS CONDITION 1 @stacked_msg = MESSAGE_TEXT;
    END;
    
    -- 向表 t1 插入传入的参数 val 的值
    INSERT INTO t1 VALUES(val);
END

接下来使用不同的值来调用以上存储过程

复制代码
mysql> CALL sp_test(1);

mysql> SELECT 
    ->     THREAD_ID,         
    ->     EVENT_NAME,    
    ->     SQL_TEXT              
    -> FROM events_statements_history WHERE 
    -> EVENT_NAME LIKE 'statement/sp%';

在本例中,没有调用错误处理程序,过程将参数值(1)插入表中

复制代码
mysql> CALL sp_test(null);

mysql> SELECT 
    ->     THREAD_ID,         
    ->     EVENT_NAME,    
    ->     SQL_TEXT              
    -> FROM events_statements_history WHERE 
    -> EVENT_NAME LIKE 'statement/sp%';

语句剖析

events_stages_[current|history|history_long]表包含剖析信息,例如MySQL在创建临时表、更新或等待锁时花费了多少时间。要启用剖析,需要启用上述消费者表以及匹配'stage/%'模式的插桩。

例如:

复制代码
mysql> SELECT 
    ->     eshl.event_name,                  
    ->     esthl.sql_text,                   
    ->     eshl.timer_wait/1000000000 AS w_s 
    -> FROM 
    ->     performance_schema.events_stages_history_long eshl 
    -> JOIN 
    ->     performance_schema.events_statements_history_long esthl 
    -> ON 
    ->     eshl.nesting_event_id = esthl.event_id 
    -> WHERE 
    ->     eshl.timer_wait > 1*1000000000; 

使用events_stages_[current|history|history_long]表的另一种技术是注意那些在已知阶段中花费了超过某个阈值的语句,这些语句会导致性能问题。表4列出了这些阶段。

​ 表4:代表不同性能问题的阶段

阶段类 描述
stage/sql/%tmp% 所有与临时表相关的内容
stage/sql/%lock% 所有与锁相关的内容
stage/%/waitingfor% 所有与等待资源相关的内容
stage/sql/Sendingdata 这个阶段应该与语句统计中的ROWSSENT进行比较。如果ROWSSENT很小,那么在这个阶段花费大量时间的语句可能意味着必须创建一个临时文件或表来解析中间结果。在向客户端发送数据之前,通常会对行进行过滤。这通常是查询优化不良的症状
stage/sql/freeingitems 这些是释放资源的阶段。不幸的是,它们不够详细
stage/sql/cleaningup 阶段都包含不只一个任务。如果发现查询在这些阶段花费了很长时间,那么很可能由于高并发性而遇到资源争用
stage/sql/closingtables 你需要检查CPU、IO和内存使用情况,以及硬件和MySQL选项设置是否足够处理应用程序创建的并发性
stage/sql/end 结束阶段是确保正确处理应用程序和数据库之间的并发性

由于时间原因,等晚上或者是明天再把剩余的解析完,谢谢!

相关推荐
桥Dopey18 分钟前
关系型数据库PostgreSQL for Mac 保姆级使用教程
数据库·postgresql
@_猿来如此23 分钟前
Django 实现电影推荐系统:从搭建到功能完善(附源码)
数据库·后端·python·django
nbsaas-boot23 分钟前
SQL Server 存储过程开发手册
数据库
uncofish26 分钟前
springboot不连接数据库启动(原先连接了mysql数据库)
数据库·spring boot·mysql
宛如昨晚没早睡35 分钟前
MVCC(多版本并发控制)
mysql
鱼儿也有烦恼1 小时前
MySQL最新安装、连接、卸载教程(Windows下)
mysql·navicat
Databend2 小时前
大数据是不是凉了?
数据库
学也不会2 小时前
雪花算法
java·数据库·oracle
瀚高PG实验室3 小时前
数据库未正常关闭后,再次启动时只有主进程,数据库日志无输出
数据库
异常君4 小时前
MySQL 查询优化:JOIN 操作背后的性能代价与更优选择
后端·mysql·性能优化