8. 解读命令输出

128 阅读15分钟

8. 用命令解读.png

1. explain

列名含义取值范围及含义
id查询的序列号,标识查询中各个 SELECT 子句的顺序整数,值越大越先执行;相同值时,按从上到下顺序执行;为 NULL 时表示该查询是一个派生表(如子查询)的一部分
select_type查询的类型SIMPLE:简单查询,不包含子查询或 UNION
PRIMARY:最外层的查询;
SUBQUERY:子查询;
DERIVED:派生表,即 FROM 子句中的子查询;
UNIONUNION 中的第二个及后续查询;
UNION RESULTUNION 结果集
table查询涉及的表名实际的表名,如果是子查询或派生表,可能显示为 <derivedN>N 为派生表的编号)或 <unionM,N>(表示 UNION 操作的结果)
partitions查询可能涉及的分区如果表是分区表,显示查询可能访问的分区;如果不是分区表,则为 NULL
type表示表的连接类型,反映了查询的效率,从好到差依次排列system:表中只有一行记录,是 const 类型的特例;
const:通过索引一次就找到记录,用于 PRIMARY KEYUNIQUE 索引;
eq_ref:多表连接时,对于前面的每一行,当前表只通过索引匹配一行;
ref:使用非唯一索引或唯一索引的前缀查找;
range:使用索引进行范围扫描;
index:全索引扫描;
ALL:全表扫描
possible_keys可能使用的索引列出查询可能使用的索引名,如果为空表示没有可用的索引
key实际使用的索引显示查询实际使用的索引名,如果为空表示没有使用索引
key_len实际使用的索引长度索引使用的字节数,该值越短越好,它能帮助判断索引的使用情况和效率
ref显示哪些列或常量被用于和索引一起从表中查找记录显示与索引进行比较的列或常量,如果为 NULL 表示没有使用索引进行比较
rowsMySQL 估计为了找到所需的行而要读取的行数一个估计值,该值越小说明查询效率越高
filtered表示通过条件过滤后,剩余记录的百分比取值范围是 0.00 - 100.00,值越高表示过滤后剩余的记录越多
Extra额外的信息,提供了关于查询执行的其他重要细节Using filesort:需要额外的排序操作,通常表示查询效率较低;
Using temporary:使用了临时表来处理查询结果;
Using index:使用了覆盖索引;
Using where:使用了 WHERE 子句进行过滤;
Using join buffer:使用了连接缓存;
Impossible WHEREWHERE 子句的条件永远为 FALSE

下面举例进行说明

表结构:

Create Table: CREATE TABLE `tt` (
  `id` int NOT NULL AUTO_INCREMENT,
  `a` int DEFAULT NULL,
  `b` int DEFAULT NULL,
  `c` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `a` (`a`),
  KEY `bc` (`b`,`c`)
) ENGINE=InnoDB AUTO_INCREMENT=131056 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

表数据:

create table tt(id int primary key auto_increment, a int , b int, index (a), index bc(b,c))engine=innodb;
insert into tt(a,b,c) values(1,1,1),(2,2,2),(3,3,3),(4,4,4);
insert into tt(a,b,c) select a,b,c from tt;//重复插入到16384insert into tt(a,b,c) values(5,5,5);
insert into tt(a,b,c) select a,b,c from tt;
insert into tt(a,b,c) select a,b,c from tt; //总共65540create table t2 like tt;
insert into t2 select * from tt;

1.1 主键上的查询

image.png 主键上的等值查询,type 为 const,用了 PRIMARY 索引

image.png 主键上的范围查询,type 为 range,范围查询用了 where 条件,因此 extra 中为 Using where

1.2 二级索引上的查询

image.png 二级索引上等值查询,type 为 ref,且能用上覆盖索引,Extra 为 Using index

image.png 二级索引上的范围查询,type 为 range,能用上索引且是范围查询,所以 Extra 为 Using where,Using index

1.3 非索引或者非前缀索引查询

image.png 非前缀索引等值和范围查询,type 都为 all,由于是非索引,所以就算是等值,也需要用 where 进行判断,因此 Extra 为 Using where,key 唯 null

如果查询结果能用上覆盖索引,由于一般二级索引都比主键索引要小,因此这种情况下会遍历二级索引,如下: image.png type 为 index 表示全索引扫描(二级索引,主键索引为 all),对应的 analyze 结果如下:

image.png

1.4 连表查询

image.png 先用 a 进行等值查询,由于 a 有索引,因此 type 为 ref,ref 为 const 表示是常数

然后拿到查询的结果跟 b 连表,这里的 type 为 eq_ref 就可以理解为 const,表示连表查询时,使用唯一键进行等值查询,然后 ref 为 test.a.id,表示使用 test 库 a 表的 id 做等值查询

1.5 order by

image.png

需要使用索引 a 进行等值查询,因此 type 为 ref,有 order by,需要排序,因此 Extra 中有 Using filesort

Using filesort 强调的是排序操作不能直接通过索引有序性来实现,需要额外的排序步骤。“filesort” 是 MySQL 内部的一种排序机制的统称,它并不特指在文件(磁盘)上排序,也可能在内存中进行,只是表示无法单纯依赖索引来完成结果集的排序。

1.6 group by

image.png MySQL 从 where 条件和 group by 语句选择的待选 key 为 a,bc,但是计算后发现单纯使用其中某一个,都需要回表,还不如直接全表查询来得快,因此最终选择了全表查询,type 为 all

由于 group by 算法会创建只有两列的临时表用来处理数据,因此 Extra 中有 Using temporary

1.7 distinct

image.png 可以看到,distinct 和 group by 算法很像,也用了临时表

image.png 如果 distinct 的列本来就能保证唯一性,比如主键,唯一索引等,那么是不用到临时表去聚合的

image.png 如果结果集中需要计算,尽管 id+0 跟 id 是一致的,但是 MySQL 可没这么智能,还是会老老实实的用临时表来进行聚合,然后讲结果返回,因此这里有 Using temporary,同时,判断能否使用覆盖索引,只要查询和条件字段在一个索引中都包含,那么就能用上覆盖索引,所以这里有 Using index

1.8 是否使用临时表

当我们想查看一个语句是否有用临时表,以及是否有用到磁盘临时表时,可以通过 optimizer_trace 来查看

set optimizer_trace= 1;
explain analyze select distinct id+0 from tt where a > 1;
select * from information_schema.optimizer_trace\G;

这里能看到使用了临时表,但是没有使用磁盘临时表

2. show engine innodb status

2.1 事务相关结果

show engine innodb status 结果很长,但我们只用关注事务状态即可,即关注下面内容:

2.2 读事务(读锁)不计入

这里只显示写锁相关的事务,读锁不计入,比如

begin;
select * from tt where id = 12 for share;

结果为:

2.3 写事务(写锁)计入

如果是写锁:

begin;
select * from tt where id = 2 for update;

结果为:

由于我是直接在上面的会话中加了一个新的写锁,因此这里 lock struct 是 4,包含 IX、IS、上一个行读锁和这个行写锁,如果我完全新起一个事务,那么结果如下:

这也从侧面说明了 innodb 在分配事务 id 时,只有写事务才会分配事务 id,读事务不分配,id 为一个很长的数字

2.4 history 和 undo

两个线程如下执行:

begin;
select * from tt where id = 12;
                                        update t set c = c + 1 where id = 2;
update t set c = c + 1 where id = 3

结果为:

可以看到,history 表示已经提交但未 purge 的 undo 数

undo log entries 表示未提交的 undo 数量

2.5 锁冲突

begin;
select * from tt where id = 2;
select * from tt where id = 2 for update;
                                           update t set c = c + 1 where id = 2;

image.png

可以看到事务正在等待锁,等的是lock_mode X locks rec but not gap waiting行锁,非间隙锁,非 Next-Key lock

表只有 4 列,为什么上图中有 6 列呢?多余两列为隐式的 roll_ptr 和 trx_id,0 是 id 列,1 是 trx_id,2 是 roll_ptr,后面就分别每个列以及值

3. mysqlbinlog

3.1 binlog 开头文件解析

mysqlbinlog 是 MySQL 自带的查看 binlog 的工具,binlog 文件开头为

# The proper term is pseudo_replica_mode, but we use this compatibility alias
# to make the statement usable on server versions 8.0.24 and older.
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;
/*!50003 SET @OLD_COMPLETION_TYPE=@@COMPLETION_TYPE,COMPLETION_TYPE=0*/;
DELIMITER /*!*/;

带 '#' 的都是注释,/**/ 也是注释,但是如果是 /*!……*/,那就不是注释了,比如/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=1*/;,表示如果版本大于等于 5.530,这句话就执行,每个中间的 0 都是 '.'

3.2 binlog 内容解析

# at 3231894
#250215 15:10:11 server id 1  end_log_pos 3231978 CRC32 0x3494bc57 	Query	thread_id=25	exec_time=0	error_code=0
SET TIMESTAMP=1739603411/*!*/;
BEGIN
/*!*/;
# at 3231978
#250215 15:10:11 server id 1  end_log_pos 3232029 CRC32 0xf978a687 	Table_map: `test`.`tt` mapped to number 99
# has_generated_invisible_primary_key=0
# at 3232029
#250215 15:10:11 server id 1  end_log_pos 3232099 CRC32 0x89001328 	Update_rows: table id 99 flags: STMT_END_F

BINLOG '
0z2wZxMBAAAAMwAAAB1RMQAAAGMAAAAAAAEABHRlc3QAAnR0AAQDAwMDAA4BAQCHpnj5
0z2wZx8BAAAARgAAAGNRMQAAAGMAAAAAAAEAAgAE//8AAQAAAAEAAAABAAAAAQAAAAABAAAAAQAA
AAEAAAACAAAAKBMAiQ==
'/*!*/;
# at 3232099
#250215 15:10:11 server id 1  end_log_pos 3232130 CRC32 0x361b0f71 	Xid = 498
COMMIT/*!*/;
SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;
DELIMITER ;
# End of log file
/*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;
/*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

CRC32 表示主库开了 binlog_checksum,每次记录 binlog 都会计算 checksum,从库应用时也会校验,但是从库也能配置汇率校验 checksum

BINLOG '……' 这里面记录的就是 binlog 具体内容

original_committed_timestamp 表示主库生成 binlog 时间,单位微秒

immediate_commit_timestamp 表示从库应用 binlog 的时间,用这两个能计算主从延迟,但是如果有级联情况,比如 A->B->C,C 上 original_committed_timestamp 是 A 的值而不是 B 的

SET @@SESSION.GTID_NEXT= 'ANONYMOUS' 表示匿名事务。当将 @@SESSION.GTID_NEXT 设置为 'ANONYMOUS' 时,意味着接下来的事务将不会使用 GTID 进行标识,而是作为一个匿名事务来执行。在某些情况下,你可能需要执行一些不希望被 GTID 管理的特殊事务,例如恢复备份数据、执行一些测试性的事务等。通过设置 @@SESSION.GTID_NEXT = 'ANONYMOUS',可以确保这些事务不会干扰正常的 GTID 序列,避免对主从复制等基于 GTID 的机制产生影响。

SET @@SESSION.GTID_NEXT= 'AUTOMATIC'表示接下来的事务会由 MySQL 系统按照既定规则自动分配一个唯一的 GTID

Xid 事务 id,用于崩溃恢复,当 redo 中有 prepare,但是没有 commit 时,看有没有 Xid,有的话就提交,没有就回滚

4. mysqldump

备份整个库

mysqldump -uroot -pxxx database --flush-logs --single-transaction > backup.sql

结果为:

-- MySQL dump 10.13  Distrib 9.2.0, for macos15 (x86_64)
--
-- Host: localhost    Database: test // 这里为 test 具体值表示只备份 test 库
-- ------------------------------------------------------
-- Server version       9.2.0

// 这里是开头,现将部分变量保存起来,同时设置为需要的值,等完成后再恢复
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!50503 SET NAMES utf8mb4 */;    // 设置字符集
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; // 设置为 0 作用是先关闭唯一索引和外键影响
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

// 创建表
--
-- Table structure for table `t`
--

DROP TABLE IF EXISTS `t`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!50503 SET character_set_client = utf8mb4 */;
CREATE TABLE `t` (
`id` int NOT NULL AUTO_INCREMENT,
`c` int DEFAULT NULL,
`d` int DEFAULT NULL,
`e` int DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c_idx` (`c`),
KEY `d_e_idx` (`d`,`e`)
) ENGINE=InnoDB AUTO_INCREMENT=26 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `t`
--

// 插入数据,此处注意插入数据是需要锁表的
LOCK TABLES `t` WRITE;
/*!40000 ALTER TABLE `t` DISABLE KEYS */;   //  只对 myisam 有效,临时禁用指定表 `t` 上的非唯一索引
INSERT INTO `t` VALUES (1,0,0,0),(5,5,5,5),(10,10,10,10),(15,15,15,15),(20,20,20,20),(25,25,25,25);
/*!40000 ALTER TABLE `t` ENABLE KEYS */;
UNLOCK TABLES;

// 恢复变量
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2025-02-15 18:15:20

创建视图,如果视图依赖的表还未创建,怎么办?mysqldump 会先创建一个假的视图,如下:
然后在库备份最后重建视图,如下:

5. innodb_trx

语句 select * from information_schema.innodb_trx\G; 作用是查询 MySQL 数据库中 InnoDB 存储引擎当前正在运行的事务信息,结果如下:

                    trx_id: 2041         // 事务 id,只有更新或者写锁事务 id 是正常数据,读或者读锁事务 id 是一个很大数字
                 trx_state: LOCK WAIT    // 事务状态
               trx_started: 2025-02-16 13:20:58    // 事务开始时间
     trx_requested_lock_id: 140562234655416:428:6:7:2:140562695098912   // 事务正在请求的锁的唯一标识符
          trx_wait_started: 2025-02-16 13:20:58   // 锁等待开始时间
                trx_weight: 2    // 事务权重,回滚时可用(锁+回滚段)
       trx_mysql_thread_id: 31   // show processlist 中的 id
                 trx_query: select * from tt where id = 1 for update   // 正在执行的语句
       trx_operation_state: starting index read   // 事务执行状态,正在索引上查找时被堵住了
         trx_tables_in_use: 1   // 事务当前正在执行的语句用了几张表
         trx_tables_locked: 1   // 意向锁数量
          trx_lock_structs: 2   // 持有锁数量
     trx_lock_memory_bytes: 1128
           trx_rows_locked: 1
         trx_rows_modified: 0   // 事务修改的行数量
   trx_concurrency_tickets: 0   // InnoDB 用来控制并发事务数量的一种手段,当事务需要执行某些操作(如加锁、访问数据页等)时,会消耗一定数量的票据。当事务完成操作或者释放锁时,会将相应的票据释放回系统,供其他事务使用。避免小事务饿死
       trx_isolation_level: REPEATABLE READ   // 隔离级别
         trx_unique_checks: 1    // 唯一键检查
    trx_foreign_key_checks: 1    // 外键检查
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
       trx_schedule_weight: 1    // 事务调度权重,值越大表示越容易拿到锁资源解锁执行

问答

  1. binlog 单个文件设置大点好还是小点好?

    建议设置大些,因为 binlog 文件每次切的时候,都会将上面关联的 redo 数据刷盘,如果设置太小,就会频繁触发切文件刷盘的操作,导致系统抖动,但是也不能设置太大,太大的话崩溃恢复时间会拉长

  2. mysqldump --quick 默认参数是 ON,应该保持不变?

    是的,这里的 quick 的意思是客户端不缓存结果,直接落盘,在执行 mysqldump 时这样设置是合理的,并且也应该这样做,因为如果要缓存,可能客户端内存会 OOM,但是这个参数为 ON 对服务端来说就不是 quick 了,因为如果有缓存,服务端数据发送后,客户端缓存后可以直接接着发送,但是现在需要等待客户端落盘后才能继续发送,因此服务端来讲是慢的。

    同时,客户端默认值为 OFF,一般也不建议修改,因为这样能加快服务端速度

  3. mysqldump 如果指定 --single-transaction,会在输出文件里输出start transaction with consistent snapshot 吗?

    不会,因为这个参数是为了备份线程获取一致性视图加的,从库应用时并不需要

  4. mysqldump 如果同时指定 --flush-logs --single-transaction 的时候,每备份一个库,都会执行一次 flush logs 吗?

    不是,flush logs 作用是切所有 log 文件,包括 binlog,如果在事务中执行该语句,那么会隐式的提交当前事务,如果每备份一个库都 flush 一次 logs,那么会切 log,事务就会被提交,一致性视图就被摧毁了,这个语句仅会在备份第一个库时,才执行,后面不执行

  5. 单独备份一个库,需要指定 --routines 才能备份存储过程?

    是的,如果不加该参数,那么只会备份这个库的数据,不备份存储过程,只有加上 --routines 才会去系统库中将这个库相关的所有存储过程备份