MYSQL INNODB中表数据的返回顺序问题

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 接上一篇: http://blog.itpub.net/7728585/viewspace-2126344/ 如何证明INNODB辅助索引叶子结点KEY值相同的按照PRIMARY KEY排序  我们在上一篇中建立了表 mysql> create table t...
接上一篇:
http://blog.itpub.net/7728585/viewspace-2126344/
如何证明INNODB辅助索引叶子结点KEY值相同的按照PRIMARY KEY排序 


我们在上一篇中建立了表
mysql> create table test (a int,b int,primary key(a),key(b));
Query OK, 0 rows affected (0.08 sec)
并且插入了数据
mysql> insert into test values(1,1);
Query OK, 1 row affected (0.08 sec)
mysql> insert into test values(5,1);
Query OK, 1 row affected (0.03 sec)
mysql> insert into test values(3,1);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(4,2);
Query OK, 1 row affected (0.59 sec)
mysql> insert into test values(10,4);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(7,4);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(8,5);
Query OK, 1 row affected (0.01 sec)
mysql> insert into test values(11,5);
Query OK, 1 row affected (0.01 sec)
mysql> insert into test values(20,6);
Query OK, 1 row affected (0.01 sec)
mysql> insert into test values(21,6);
Query OK, 1 row affected (0.00 sec)
mysql> insert into test values(19,7);
Query OK, 1 row affected (0.03 sec)
mysql> insert into test values(16,7);
Query OK, 1 row affected (0.01 sec)

通过分析和程序跑出了在辅助索引列b中的存储顺序如下:
[root@ora12ctest test]# ./a.out test.ibd 4
Index_no is:42
find first one record!
B:1,A:1-->
B:1,A:3-->
B:1,A:5-->
B:2,A:4-->
B:4,A:7-->
B:4,A:10-->
B:5,A:8-->
B:5,A:11-->
B:6,A:20-->
B:6,A:21-->
B:7,A:16-->
B:7,A:19-->


这里我们讨论一下SELECT * FROM 使用 USING INDEX 索引覆盖扫描B列的情况下和不使用索引使用索引而使用表本生的聚族索引的情况下数据
返回的顺序及性能比较。
首先给出猜测的结论:
1、在使用USING INDEX B列索引的时候,返回的顺序应该是和B列上辅助索引的返回顺序一致,也就是程序跑出的结果,在这里需要注意一点
   熟悉ORACLE的朋友如果DUMP过索引块,会看到索引的数据实际上INDEX KEY+ROWID,那么这种情况下肯定不能使用索引覆盖扫描(INDEX FAST FULL SCAN),
   因为索引中压根不包含A值,但是INNODB却不同,他包含是PRIMARY KEY,所以使用到了USING INDEX.
2、在不使用任何索引,仅仅使用全表扫描,其实全表扫描也是按链表顺序扫描聚族索引B+树的叶子结点,所以我们可以推断他的顺序是和A列
   主键的排序一致的。
下面来证明这两点:
1、
mysql> explain select * from test force index(b);
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | test  | NULL       | index | NULL          | b    | 5       | NULL |   12 |   100.00 | Using index |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
明显是Using index B索引
看看结果:
mysql> select * from test force index(b);
+----+------+
| a  | b    |
+----+------+
|  1 |    1 |
|  3 |    1 |
|  5 |    1 |
|  4 |    2 |
|  7 |    4 |
| 10 |    4 |
|  8 |    5 |
| 11 |    5 |
| 20 |    6 |
| 21 |    6 |
| 16 |    7 |
| 19 |    7 |
+----+------+
是不是和程序按照链表结构跑出来的一模一样
B:1,A:1-->
B:1,A:3-->
B:1,A:5-->
B:2,A:4-->
B:4,A:7-->
B:4,A:10-->
B:5,A:8-->
B:5,A:11-->
B:6,A:20-->
B:6,A:21-->
B:7,A:16-->
B:7,A:19-->
这样结论1得到了验证

2、
mysql> explain  select * from test force index(primary);
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
|  1 | SIMPLE      | test  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   12 |   100.00 | NULL  |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
明显没有使用索引,那么我们可以断定他是使用了表本生也就是聚集索引的,按照聚集索引本生的链表进行返回,也就是按照主键
列A的顺序返回,因为是主键这个顺序也就自然固定了不用看B列的值了。来看看
mysql>  select * from test force index(primary);
+----+------+
| a  | b    |
+----+------+
|  1 |    1 |
|  3 |    1 |
|  4 |    2 |
|  5 |    1 |
|  7 |    4 |
|  8 |    5 |
| 10 |    4 |
| 11 |    5 |
| 16 |    7 |
| 19 |    7 |
| 20 |    6 |
| 21 |    6 |
+----+------+

可以看到确实如果结论2得到验证。


当然这个结论不光适合SELECT 全索引扫描的情况,为了证明这一点我增加了一列
C

mysql> alter table test add column c int;
Query OK, 0 rows affected (1.13 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> update test set c=100;
Query OK, 12 rows affected (0.11 sec)
Rows matched: 12  Changed: 12  Warnings: 0
mysql> commit;
Query OK, 0 rows affected (0.00 sec)

目的在于不然MYSQL使用Using index这个索引覆盖扫描的方式:
1、
mysql> explain select * from test force index(b) where b in(4,5,7);
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
|  1 | SIMPLE      | test  | NULL       | range | b             | b    | 5       | NULL |    6 |   100.00 | Using index condition |
+----+-------------+-------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
1 row in set, 1 warning (0.00 sec)


mysql> select * from test force index(b) where b in(4,5,7);
+----+------+------+
| a  | b    | c    |
+----+------+------+
|  7 |    4 |  100 |
| 10 |    4 |  100 |
|  8 |    5 |  100 |
| 11 |    5 |  100 |
| 16 |    7 |  100 |
| 19 |    7 |  100 |
+----+------+------+
6 rows in set (0.01 sec)


2、
mysql> explain select * from test force index(primary) where b in(4,5,7);
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows | filtered | Extra       |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
|  1 | SIMPLE      | test  | NULL       | ALL  | NULL          | NULL | NULL    | NULL |   12 |    30.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)


mysql>  select * from test force index(primary) where b in(4,5,7);
+----+------+------+
| a  | b    | c    |
+----+------+------+
|  7 |    4 |  100 |
|  8 |    5 |  100 |
| 10 |    4 |  100 |
| 11 |    5 |  100 |
| 16 |    7 |  100 |
| 19 |    7 |  100 |
+----+------+------+
6 rows in set (0.00 sec)

可以清楚的看到他们的区别,也就是查询1是通过B列辅助索引的叶子结点查询出然后进行书签试查找主键回到的聚集索引,得出的
顺序当然是辅助索引B中B列的排序方式。而查询2当然也就是直接访问聚集索引过滤的条件,当然也就是主键的顺序。

然后我们讨论一下性能问题,虽然都是按照B+树的叶子结点进行顺序返回,但是聚集索引却要比辅助索引上的信息多,
也许要说这里聚集索引也是A,B列的值,辅助索引也是A,B列的值,
但是从前文看出:
./bcview  test.ibd 16 126 30|more
current block:00000003--Offset:00126--cnt bytes:21--data is:80000001000000000707a70000011b011080000001
current block:00000004--Offset:00126--cnt bytes:21--data is:8000000180000001
在聚集索引中有
000000000707a70000011b0110这样的信息实际上就是transaction id 和roll pointer
那么我们可以直观的判断出在同样的数据量下辅助索引的叶子PAGE会少于聚集索引的PAGE,
那么性能应该也会更好。

结论:
1、如果发现使用不同索引返回数据的顺序不一样,不要吃惊,不一样是正常,如果一样才要吃惊,INNODB全表扫描
   能够保证返回数据的顺序是主键的排序(虽然我们只验证单叶子结点情况,但是B+树的叶子结点是有PAGE和PAGE之间
   的指针的),这一点ORACLE中却不行,我曾经在ORACLE的书上看到,如果要保证排序只能用ORDER BY,但是这一点视乎
   在INNODB中并不适用,当然如果保险加上ORDER BY也是可以的,因为SORT的操作会被优化器忽略,这样以防万一。
   其实索引在INNODB和ORACLE中的另外一个功能就是避免排序。
2、create table test (a int,b int,primary key(a),key(b));这种方式如果where b= 在INNODB中可以使用索引覆盖扫描
   但是在ORACLE中不行,原因前面给出了。
3、在性能方面INNODB unsing index的性能在大多数情况下都要优于全表扫描(聚集索引),原因也已经给出。
相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
打赏
0
0
0
0
91
分享
相关文章
MySQL底层概述—10.InnoDB锁机制
本文介绍了:锁概述、锁分类、全局锁实战、表级锁(偏读)实战、行级锁升级表级锁实战、间隙锁实战、临键锁实战、幻读演示和解决、行级锁(偏写)优化建议、乐观锁实战、行锁原理分析、死锁与解决方案
MySQL底层概述—10.InnoDB锁机制
MySQL底层概述—5.InnoDB参数优化
本文介绍了MySQL数据库中与内存、日志和IO线程相关的参数优化,旨在提升数据库性能。主要内容包括: 1. 内存相关参数优化:缓冲池内存大小配置、配置多个Buffer Pool实例、Chunk大小配置、InnoDB缓存性能评估、Page管理相关参数、Change Buffer相关参数优化。 2. 日志相关参数优化:日志缓冲区配置、日志文件参数优化。 3. IO线程相关参数优化: 查询缓存参数、脏页刷盘参数、LRU链表参数、脏页刷盘相关参数。
MySQL底层概述—5.InnoDB参数优化
MySQL底层概述—4.InnoDB数据文件
本文介绍了InnoDB表空间文件结构及其组成部分,包括表空间、段、区、页和行。表空间是最高逻辑层,包含多个段;段由若干个区组成,每个区包含64个连续的页,页用于存储多条行记录。文章还详细解析了Page结构,分为通用部分(文件头与文件尾)、数据记录部分和页目录部分。此外,文中探讨了行记录格式,包括四种行格式(Redundant、Compact、Dynamic和Compressed),重点介绍了Compact行记录格式及其溢出机制。最后,文章解释了不同行格式的特点及应用场景,帮助理解InnoDB存储引擎的工作原理。
MySQL底层概述—4.InnoDB数据文件
MySQL和SQLSugar百万条数据查询分页优化
在面对百万条数据的查询时,优化MySQL和SQLSugar的分页性能是非常重要的。通过合理使用索引、调整查询语句、使用缓存以及采用高效的分页策略,可以显著提高查询效率。本文介绍的技巧和方法,可以为开发人员在数据处理和查询优化中提供有效的指导,提升系统的性能和用户体验。掌握这些技巧后,您可以在处理海量数据时更加游刃有余。
41 9
基于SQL Server / MySQL进行百万条数据过滤优化方案
对百万级别数据进行高效过滤查询,需要综合使用索引、查询优化、表分区、统计信息和视图等技术手段。通过合理的数据库设计和查询优化,可以显著提升查询性能,确保系统的高效稳定运行。
18 2
MySQL进阶突击系列(09)数据磁盘存储模型 | 一行数据怎么存?
文中详细介绍了MySQL数据库中一行数据在磁盘上的存储机制,包括表空间、段、区、页和行的具体结构,以及如何设计和优化行数据存储以提高性能。
【YashanDB 知识库】MySQL 迁移至崖山 char 类型数据自动补空格问题
问题分类】功能使用 【关键字】char,char(1) 【问题描述】MySQL 迁移至崖山环境,字段类型源端和目标端都为 char(2),但应用存储的数据为'0'、'1',此时崖山查询该表字段时会自动补充空格 【问题原因分析】mysql 有 sql_mode 控制,检查是否启用了 PAD_CHAR_TO_FULL_LENGTH SQL 模式。如果启用了这个模式,MySQL 才会保留 CHAR 类型字段的尾随空格,默认没有启动。 #查看sql_mode mysql> SHOW VARIABLES LIKE 'sql_mode'; 【解决/规避方法】与应用确认存储的数据,正确定义数据
Docker Compose V2 安装常用数据库MySQL+Mongo
以上内容涵盖了使用 Docker Compose 安装和管理 MySQL 和 MongoDB 的详细步骤,希望对您有所帮助。
151 42
如何排查和解决PHP连接数据库MYSQL失败写锁的问题
通过本文的介绍,您可以系统地了解如何排查和解决PHP连接MySQL数据库失败及写锁问题。通过检查配置、确保服务启动、调整防火墙设置和用户权限,以及识别和解决长时间运行的事务和死锁问题,可以有效地保障应用的稳定运行。
96 25