jjzjj

SQL - MySQL回表

MinggeQingchun 2023-04-24 原文

一、回表概念;现象

回表,顾名思义就是回到表中,也就是先通过普通索引(我们自己建的索引不管是单列索引还是联合索引,都称为普通索引)扫描出数据所在的行,再通过行主键ID 取出索引中未包含的数据。所以回表的产生也是需要一定条件的,如果一次索引查询就能获得所有的select 记录就不需要回表,如果select 所需获得列中有其他的非索引列,就会发生回表动作。即基于非主键索引的查询需要多扫描一棵索引树。

Mysql回表指的是在InnoDB存储引擎下,二级索引查询到的索引列,如果需要查找所有列的数据,则需要到主键索引里面去取出数据。这个过程就称为回表。因为行的数据都是存在主键B+tree的叶子节点里面,二级索引的B+树叶子节点都是存放的(索引列,主键)

简单来说,回表就是 MySQL 要先查询到主键索引,然后再用主键索引定位到数据

回表现象

举个例子:

表tbl有a,b,c三个字段,其中 a是主键,b上建了索引,然后编写sql语句SELECT * FROM tbl WHERE a=1这样不会产生回表,因为所有的数据在a的索引树中均能找到

如果是SELECT * FROM tbl WHERE b=1这样就会产生回表,因为where条件是b字段,那么会去b的索引树里查找数据,但b的索引里面只有a,b两个字段的值,没有c,那么这个查询为了取到c字段,就要取出主键a的值,然后去a的索引树去找c字段的数据。查了两个索引树,就出现了回表操作

二、存储引擎;索引结构

要弄明白回表,首先得了解MySQL的存储引擎,以及默认存储引擎 InnoDB 的两大索引,即聚簇索引 (clustered index)和 非聚簇索引/普通索引/二级索引/辅助索引(secondary index)

(一)存储引擎

MySQL中主要有2种存储引擎

1、MyISAM(不支持事物回滚)

MyIsam引擎是MySQL主流引擎之一,但它相比起InnoDB,没有提供对数据库事务的支持,不支持细粒度的锁(行锁)及外键,当表Insert与update时需要锁定整个表,因此效率会低一些,在高并发时可能会遇到瓶颈,但MyIsam引擎独立与操作系统,可以在windows及linux上使用。

可能的缺点:

不能在表损坏后恢复数据

适用场景:

1、MyIsam极度强调快速读取

2、MyIsam表中自动存储了表的行数,需要时直接获取即可

3、适用于不需要事物支持、外键功能、及需要对整个表加锁的情形

2、InnoDB(支持事物回滚)

InnoDB是一个事务型存储引擎,提供了对数据库ACID事务的支持,并实现了SQL标准的四种隔离级别,具有行级锁定(这一点说明锁的粒度小,在写数据时,不需要锁住整个表,因此适用于高并发情形)及外键支持(所有数据库引擎中独一份,仅有它支持外键)

该引擎的设计目标便是处理大容量数据的数据库系统,MySQL在运行时InnoDB会在内存中建立缓冲池,用于缓存数据及索引。

可能的缺点:

1、该引擎不支持FULLTEXT类型的索引

2、没有保存表的行数,在执行select count(*) from 表名 时,需要遍历扫描全表

适用场景:

1、经常需要更新的表,适合处理多重并发的更新请求

2、支持事务

3、外键约束

4、可以从灾难中恢复(通过bin-log日志等)

5、支持自动增加列属性auto_increment

show engines;

show engines; 查看mysql所支持的存储引擎,以及从中得到mysql默认的存储引擎 

可以看出,MySQL默认的数据库引擎是InnoDB

参数名称解释说明
Engine存储引擎名称
Support是否支持该引擎以及该引擎是否为默认存储引擎,YES表示支持,NO表示不支持
DEFAULTDEFAULT表示为默认存储引擎
Comment存储引擎的简单介绍
Transactions表示该引擎是否支持事务
XA说明该存储引擎是否支持分布事务
Savepoints说明该存储引擎是否支持部分事务回滚

(二)索引结构

MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效获取数据的数据结构。可以得到索引的本质:索引是数据结构

MySQL默认的数据库引擎是InnoDB,InnoDB 存储引擎的两大索引,即聚簇索引 (clustered index)和 非聚簇索引/普通索引/二级索引/辅助索引(secondary index)

1、聚簇索引 (clustered index)

InnoDB聚簇索引的叶子节点存储行记录,因此, InnoDB必须要有且只有一个聚簇索引。

  • 如果表定义了主键,则Primary Key 就是聚簇索引;
  • 如果表没有定义主键,则第一个非空唯一索引(Not NULL Unique)列是聚簇索引
  • 否则,InnoDB会创建一个隐藏的row-id作为聚簇索引

简单来说,聚簇索引是主键索引

2、非聚簇索引/普通索引/二级索引/辅助索引(secondary index)

主键索引之外的就是非聚簇索引,非聚簇索引又叫辅助索引或者二级索引

主键索引 和 非主键索引区别

相同点:都使用的是 B+Tree

不同点:叶子节点存储的数据不同

主键索引的叶子节点存储的是一行完整的数据

非主键索引的叶子节点存储的是主键值。叶子节点不包含记录的全部数据,非主键的叶子节点除了用来排序的 key 还包含一个书签(bookmark),其中存储了聚簇索引的 key

使用主键索引查询

# 主键索引的的叶子节点存储的是**一行完整的数据**,
# 所以只需搜索主键索引的 B+Tree 就可以轻松找到全部数据
select * from user where id = 1;

使用非主键索引查询

# 非主键索引的叶子节点存储的是**主键值**,
# 所以MySQL会先查询到 name 列的索引的 B+Tree,搜索得到对应的主键值
# 然后再去搜索该主键值查询主键索引的 B+Tree 才可以找到对应的数据
select * from user where name = 'Jack';

使用非主键索引要比主键索引多使用一次 B+Tree

二级索引查找的过程为先在二级索引找到主键索引的key,再在主键索引中查找(回表操作)

InnoDB表一定要建主键,并且最好使用int自增作为主键

这样做就是为了不用MySQL维护唯一列数据,节省资源。建立和维护索引过程中需要进行key的比较,int类型更好比较。自增使得树结构不容易产生树结构分裂,更节省算力

1、单值索引

即一个索引只包含单个列,一个表可以有多个单列索引

1

2

3

4

5

6

7

8

9

10

11

12

13

14

随表一起建索引:

CREATE TABLE customer (

id INT(10) UNSIGNED  AUTO_INCREMENT ,

customer_no VARCHAR(200),

customer_name VARCHAR(200),

PRIMARY KEY(id),

KEY (customer_name)

);

  

单独建单值索引:

CREATE  INDEX idx_customer_name ON customer(customer_name);

  

删除索引:

DROP INDEX idx_customer_name  on customer;

2、唯一索引

索引列的值必须唯一,但允许有空值

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

随表一起建索引:

CREATE TABLE customer (

id INT(10) UNSIGNED  AUTO_INCREMENT ,

customer_no VARCHAR(200),

customer_name VARCHAR(200),

  PRIMARY KEY(id),

  KEY (customer_name),

  UNIQUE (customer_no)

);

   

单独建唯一索引:

CREATE UNIQUE INDEX idx_customer_no ON customer(customer_no);

  

删除索引:

DROP INDEX idx_customer_no on customer ;

3、主键索引

设定为主键后数据库会自动建立索引,innodb为聚簇索引

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

随表一起建索引:

CREATE TABLE customer (

id INT(10) UNSIGNED  AUTO_INCREMENT ,

customer_no VARCHAR(200),

customer_name VARCHAR(200),

  PRIMARY KEY(id)

);

    

CREATE TABLE customer2 (

id INT(10) UNSIGNED   ,

customer_no VARCHAR(200),

customer_name VARCHAR(200),

  PRIMARY KEY(id)

);

  

 单独建主键索引:

ALTER TABLE customer

 add PRIMARY KEY customer(customer_no); 

  

删除建主键索引:

ALTER TABLE customer

 drop PRIMARY KEY

  

修改建主键索引:

必须先删除掉(drop)原索引,再新建(add)索引

4、复合索引

即一个索引包含多个列

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

随表一起建索引:

CREATE TABLE customer (

id INT(10) UNSIGNED  AUTO_INCREMENT ,

customer_no VARCHAR(200),

customer_name VARCHAR(200),

  PRIMARY KEY(id),

  KEY (customer_name),

  UNIQUE (customer_name),

  KEY (customer_no,customer_name)

);

  

单独建索引:

CREATE  INDEX idx_no_name ON customer(customer_no,customer_name);

  

删除索引:

DROP INDEX idx_no_name  on customer ;

(三)B-Tree 和 B+Tree

理解聚簇索引和非聚簇索引的关键在于 B+Tree 的理解

前者是 B-Tree,后者是 B+Tree,两者的区别在于:

  • B-Tree 中,所有节点都会带有指向具体记录的指针;B+Tree 中只有叶子结点会带有指向具体记录的指针。

  • B-Tree 中不同的叶子之间没有连在一起;B+Tree 中所有的叶子结点通过指针连接在一起。

  • B-Tree 中可能在非叶子结点就拿到了指向具体记录的指针,搜索效率不稳定;B+Tree 中,一定要到叶子结点中才可以获取到具体记录的指针,搜索效率稳定

基于上面两点分析,我们可以得出如下结论:

  • B+Tree 中,由于非叶子结点不带有指向具体记录的指针,所以非叶子结点中可以存储更多的索引项,这样就可以有效降低树的高度,进而提高搜索的效率。

  • B+Tree 中,叶子结点通过指针连接在一起,这样如果有范围扫描的需求,那么实现起来将非常容易,而对于 B-Tree,范围扫描则需要不停的在叶子结点和非叶子结点之间移动

三、索引创建场景

(一)需要创建索引

1、主键自动建立唯一索引

2、频繁作为查询条件的字段应该创建索引

3、查询中与其它表关联的字段,外键关系建立索引

4、单键/组合索引的选择问题, 组合索引性价比更高

5、查询中排序的字段,排序字段若通过索引去访问将大大提高排序速度

6、查询中统计或者分组字段

(二)不要创建索引

1、表记录太少

2、经常增删改的表或者字段 原因:提高了查询速度,同时却会降低更新表的速度,如对表进行INSERT、UPDATE和DELETE。因为更新表时,MySQL不仅要保存数据,还要保存一下索引文件

3、Where条件里用不到的字段不创建索引

4、过滤性不好的不适合建索引

四、覆盖索引避免回表

覆盖索引就是指索引中包含了查询中的所有字段,这种情况下就不需要再进行回表查询

一级索引:聚簇索引即主键索引
二级索引:非聚簇索引

一级B+Tree:叶子节点保存着键(id的值)和数据(全部字段的值)
二级B+Tree:叶子节点保存着键(索引字段的值)和数据(主键索引值)

查询 一级索引,根据一级B+Tree查询到数据,直接返回数据
查询 二级索引,根据二级B+Tree查询到对应的聚簇索引,再根据聚簇索引在一级B+Tree里查询到相应数据

查询 一级索引只需要扫描一次B+Tree。
查询 二级索引需要扫描两次B+Tree。根据二级B+Tree扫描的结果,再去一级B+Tree里进行扫描就叫回表操作

如果使用组合索引,就可以利用覆盖索引避免回表操作

例:表一共有五个字段:a(主键索引),b_c_d(组合索引),e(没有索引)

如果用户查询时只查 b,c,d;SELECT `b`, `c`, `d` FROM `table` WHERE `b` = 3 AND `c` = 7 AND `d` = 5;

因为查询的字段 b,c,d的值(B+Tree里的键) 已经在B+Tree里了,所以就可以直接返回,不用再拿聚簇索引去一级B+Tree里进行查询

如果查询字段为 a,b,c,d,因为a为主键索引,也保存在二级B+Tree的叶子节点里,所以也不用回表查询

【a是主键,给bcd建立联合索引】,如上几个sql,select出来的内容,和where条件字段,刚好和建立的索引一致

如果查询字段为 a,b,c,d,e,因为e没有在这个二级B+Tree里,所以需要进行回表操作,拿着主键索引再去一级B+Tree里进行查询

使用覆盖索引,我们需要select出来的列,都已经存在了索引树的叶子节点上。所以不需要回表操作,如果我们select出来的某列,不在该联合索引的叶子节点上(比如上表的e列),那就需要根据对应索引值,去聚簇索引树上回表查询对应的e列值了

参考链接

MySQL 回表 - 涛姐涛哥 - 博客园

什么是MySQL的回表?_一年春又来的博客-CSDN博客_回表

MySQL 存储引擎 - 知乎

https://www.jb51.net/article/239235.htm

有关SQL - MySQL回表的更多相关文章

  1. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

  2. Hive SQL 五大经典面试题 - 2

    目录第1题连续问题分析:解法:第2题分组问题分析:解法:第3题间隔连续问题分析:解法:第4题打折日期交叉问题分析:解法:第5题同时在线问题分析:解法:第1题连续问题如下数据为蚂蚁森林中用户领取的减少碳排放量iddtlowcarbon10012021-12-1212310022021-12-124510012021-12-134310012021-12-134510012021-12-132310022021-12-144510012021-12-1423010022021-12-154510012021-12-1523.......找出连续3天及以上减少碳排放量在100以上的用户分析:遇到这类

  3. sql - 查询忽略时间戳日期的时间范围 - 2

    我正在尝试查询我的Rails数据库(Postgres)中的购买表,我想查询时间范围。例如,我想知道在所有日期的下午2点到3点之间进行了多少次购买。此表中有一个created_at列,但我不知道如何在不搜索特定日期的情况下完成此操作。我试过:Purchases.where("created_atBETWEEN?and?",Time.now-1.hour,Time.now)但这最终只会搜索今天与那些时间的日期。 最佳答案 您需要使用PostgreSQL'sdate_part/extractfunction从created_at中提取小时

  4. ruby-on-rails - 无法安装 mysql2 0.3.14 gem - 2

    我看到其他人也遇到过类似的问题,但没有一个解决方案对我有用。0.3.14gem与其他gem文件一起存在。我已经完全按照此处指示完成了所有操作:https://github.com/brianmario/mysql2.我仍然得到以下信息。我不知道为什么安装程序指示它找不到include目录,因为我已经检查过它存在。thread.h文件存在,但不在ruby​​目录中。相反,它在这里:C:\RailsInstaller\DevKit\lib\perl5\5.8\msys\CORE\我正在运行Windows7并尝试在Aptana3中构建我的Rails项目。我的Ruby是1.9.3。$gemin

  5. ruby - 如何使用 ruby​​ mysql2 执行事务 - 2

    我已经开始使用mysql2gem。我试图弄清楚一些基本的事情——其中之一是如何明确地执行事务(对于批处理操作,比如多个INSERT/UPDATE查询)。在旧的ruby-mysql中,这是我的方法:client=Mysql.real_connect(...)inserts=["INSERTINTO...","UPDATE..WHEREid=..",#etc]client.autocommit(false)inserts.eachdo|ins|beginclient.query(ins)rescue#handleerrorsorabortentirelyendendclient.commi

  6. sql - 在 Rails Console for PostgreSQL 的表中显示数据 - 2

    我找到了这样的东西:Rails:Howtolistdatabasetables/objectsusingtheRailsconsole?这一行没问题:ActiveRecord::Base.connection.tables并返回所有表但是ActiveRecord::Base.connection.table_structure("users")产生错误:ActiveRecord::Base.connection.table_structure("projects")我认为table_structure不是Postgres方法。如何列出Postgres数据库的Rails控制台中表中的所有

  7. ruby - 防止SQL注入(inject)/好的Ruby方法 - 2

    Ruby中防止SQL注入(inject)的好方法是什么? 最佳答案 直接使用ruby?使用准备好的语句:require'mysql'db=Mysql.new('localhost','user','password','database')statement=db.prepare"SELECT*FROMtableWHEREfield=?"statement.execute'value'statement.fetchstatement.close 关于ruby-防止SQL注入(inject

  8. ruby-on-rails - 如何在 Rails 中的不同数据库上执行直接 SQL 代码 - 2

    我正在编写一个Rails应用程序,它将监视某些特定数据库的数据质量。为了做到这一点,我需要能够对这些数据库执行直接SQL查询——这当然与用于驱动Rails应用程序模型的数据库不同。简而言之,这意味着我无法使用通过ActiveRecord基础连接的技巧。我需要连接的数据库在设计时是未知的(即:我不能将它们的详细信息放在database.yaml中)。相反,我有一个模型“database_details”,用户将使用它来输入应用程序将在运行时执行查询的数据库的详细信息。因此与这些数据库的连接实际上是动态的,细节仅在运行时解析。 最佳答案

  9. sql - Rails:使用 Postgres 创建对象时重复 ActiveRecord::RecordNotUnique? - 2

    我正在使用Rails4应用程序,它需要创建大量对象以响应来自另一个系统的事件。当我调用create!时,主键列上出现非常频繁的ActiveRecord::RecordNotUnique错误(由PG::UniqueViolation引起)我的模型之一。我在SO上找到了其他答案,建议挽救异常并调用retry:beginTableName.create!(data:'here')rescueActiveRecord::RecordNotUnique=>eife.message.include?'_pkey'#Onlyretryprimarykeyviolationslog.warn"Retr

  10. ruby-on-rails - 当我通过 rvm 使用 rails3 时,如何在 ubuntu 上安装 mysql2 gem? - 2

    我正在尝试绕过rails配置这个极其复杂的迷宫。到目前为止,我设法在ubuntu上设置了rvm(出于某种原因,ruby在ubuntu存储库中已经过时了)。我设法建立了一个Rails项目。我希望我的测试项目使用mysql而不是mysqlite。当我尝试“rakedb:migrate”时,出现错误:“!!!缺少mysql2gem。将其添加到您的Gemfile:gem'mysql2'”当我尝试“geminstallmysql”时,出现错误,告诉我需要为安装命令提供参数。但是,参数列表很大,我不知道该选择哪些。如何通过在ubuntu上运行的rvm和mysql获取rails3?谢谢。

随机推荐