MySQL内部实现和优化的原理

MySQL服务器架构

MySQL逻辑架构图

上层是服务器层的服务和查询执行引擎,下层则是存储引擎。
在这里插入图片描述

优化与执行

MySQL解析查询,创建内部数据结构(解析树),对其重写查询,决定表的读取顺序,以及选择合适的索引等优化。(可以使用hint提示自己抉择优化)
针对select语句,在解析前,服务器会先检查缓存(Query Cache),如果找到对应查询,服务器不会再执行查询解析、优化和执行的整个过程,而是直接返回缓存结果集。

并发控制

读写锁

共享锁和排他锁,也叫读锁和写锁。
读锁是共享的,多个客户在同一时刻读取同一个资源互不干扰,写锁则是排他的,会阻塞其他的写锁和读锁。

锁粒度

表锁是开销最小的锁,但这会阻塞其他用户对该表的所有读写操作,另外写锁比读锁优先级更高,会被插入到读锁的队列前面。
行级锁,只在存储引擎层实现,最大程度的支持并发处理(同时带来最大的锁开销)

事务管理

事务就是一组原子性的SQL语句查询,具有ACID特性。
A(atomicity)— 原子性:全做或者都不做。
C(consistency)— 一致性:数据库总是从一个一致性状态到另外一个一致性状态。
I(isolation)— 隔离性:一个事务所做的修改在最终提交前,对其他事务是不可见的。
D(durability)— 持久性:所做的修改永久保存到数据库。

隔离级别

READ UNCOMMITTED(读未提交):事务中修改的,没有提交,对其他事务可见,脏读
READ COMMITTED(读已提交/不可重复读):一个事务开始,只能看见已经提交的事务所做的修改,会出现两次读取的数据不一致。
REPEATABLE READ(可重复读):解决了脏读,保证同一个事务的多次读取同样记录结果一致。但事务A在读取某个范围内的记录时,事务B又在该范围内插入了新记录,事务A再次读取该范围数据,会产生幻行。InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC)解决幻读。(MySQL默认事务隔离级别)
SERIALIZABLE(可串行化):串行执行事务,会在读取的每一行数据上加锁。

死锁

死锁是指多个事务在同一资源上相互占用,并请求锁定对方占用的资源。
事务A:
START TRANSACTION;
UPDATE user SET name = ‘上官’ WHERE user_id = 1;
UPDATE user SET name = ‘帝文’ WHERE user_id = 2;
COMMIT;
事务B:
START TRANSACTION;
UPDATE user SET name = ‘东华’ WHERE user_id = 2;
UPDATE user SET name = ‘帝君’ WHERE user_id = 1;
COMMIT;
假如两个事务都执行了第一句,同时也锁定了改行,俩事务都尝试执行第二条语句,都等待对方释放,同时又持有对方需要的锁,陷入死循环。
MySQL解决方案:
死锁检测和死锁超时机制。
InnoDB处理死锁:将持有最少行级排他锁的事务进行回滚。

事务日志

存储引擎修改内存拷贝,再把改修改行为记录到持久在磁盘上的事务日志中,而不是每次都将数据本身持久化到磁盘。事务日志采用追加方式记录,因此是磁盘一小块区域的顺序I/O,不像随机I/O需要再磁盘的多个地方移动磁头。内存中修改的数据,利用事务日志,在后台可以慢慢地刷回到磁盘。

MySQL中的事务

自动提交

MySQL提供两种事务型的存储引擎:InnoDB和NDB Cluster。
MySQL默认采用自动提交,每一个CURD动作都会被当作一个事务执行提交操作(AUTOCOMMIT:1/on 启用,0/off 禁用)。
查看语句:SHOW VARIABLES LIKE ‘AUTOCOMMIT’;
设置语句:SET AUTOCOMMIT = 1;

隐式和显式锁定

事务执行锁定为隐式锁定。
InnoDB支持显式锁定:
SELECT … LOCK IN SHARE MODE
SELECT … FOR UPDATE
MySQL也支持LOCK TABLES UNLOCK TABLES语句,这是在服务器层实现的,和存储引擎无关。

MVCC

实现机制:保存数据在某个时间点的快照,根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样。
实现种类:乐观和悲观两种并发控制
实现原理:
在每行记录后面保存“创建时间”、行的“过期时间”(删除时间)这两个隐藏列。时间指的是系统版本号。每开启一个事务,系统版本号都会自动自增,事务开始时刻的版本号作为事务的版本号,和查询到的每行记录的版本号进行比较。
工作流程:
SELECT:
InnoDB会根据以下两个条件检查每行的记录:

  1. InnoDB只会找版本早于当前事版本的数据行。(确保事务读取的行,要么在事务开始前存在,要么是事务自身插入或者修改的)。
  2. 行的删除版本要么未定义,要么大于当前事务版本号。(确保事务读取到的行,在事务开始之前为被删除)。

INSERT:
InnoDB为新插入的每一行保存当前系统版本号作为行版本号。
DELETE:
InnoDB为删除的每一行保存当前系统版本号作为行删除标识。
UPDATE:
InnoDB为插入一行新的记录,保存当前系统版本号作为行版本号,同时保存当前系统版本号到原来的行作为行删除标识。(更新操作为:插入新+删除旧
备注:MVCC只在REPEATABLE READ和 READ COMMITTED两种隔离级别下工作。

MySQL存储引擎

mysql会给每个表创建一个表定义文件:表名.frm,里面是一些表的定义信息。

InnoDB

支持事务和行级锁,数据和索引在一个文件,文件扩展为.ibd。
特性:

  1. 采用MVCC实现高并发,并且实现了四个标准的隔离级别。默认隔离级别为REPEATABLE READ,并且通过间隙锁策略防止幻读,间隙锁不仅仅锁定查询涉及到的行,还会对索引中的间隙进行锁定,以防止幻影行的插入。
  2. 基于聚簇索引建立表,二级索引/非主键索引中必须包含主键列,所以如果主键列很大的话,其他的所有索引都会很大。

MyISAM

MyISAM不支持事务和行级锁。数据文件和索引文件,分别为.MYD和.MYI为扩展名。

  1. 支持全文索引,基于分词创建的索引。
  2. 延迟更新索引建,每次写到内存中键缓冲区,在清理缓冲区或者关闭表的时候,才将对应的索引块写入磁盘。
  3. 支持压缩表,针对不再修改的表,可以采用MyISAM压缩。
    适用场景:
    只读或者大部分情况的只读表。

Archive:

特性:

  1. 只支持INSERT和SELECT操作。
  2. 会缓存所有的写并利用zlib对插入行进行压缩,每次SELECT需要全表扫描。
  3. 支持夯机所和专用的缓冲区,可以实现高并发插入
    适用场景:
    日志和数据的采集类应用。

Blackhole:

特性:
数据不做保存,会记录Blackhole表的日志。
适用场景:
可以用于复制数据到备库。

CSV

特性:
可以将csv格式的文件作为MySQL表来处理,但不支持索引。可以将excel数据转为csv存入数据库。
适用场景:
可以作为一种数据交换的机制。

Federated

特性:
访问其他MySQL的代理,创建一个到远程MySQL服务器的客户端连接,将查询传输到远程服务器执行,提取或者发送需要的数据。

Memory

特性:

  1. 所有的数据保存在内存中,表结构在服务器重启后还会保留,但是数据会丢失。
  2. 支持Hash索引,查找速度很快。
  3. 支持表级锁,因此并发写入的性能较低。
  4. 不支持BLOB/TEXT,每行的长度固定。

适用场景:

  1. 用于查找或者映射表,如:邮编和地域的映射。
  2. 用于缓存周期性的聚合数据。
  3. 用于保存数据分析中产生的中间数据。
    备注:
    MySQL查询的时候,需要使用临时表的时,内部使用的是Memory表,如果结果太大,超出Memory表限制或者含有BLOB/TEXT字段,则临时表会转成MyISAM表。

如何选择存储引擎?

事务、备份、崩溃恢复、特有的特性这四个方面考虑。

MySQL数据类型

选择合适的数据类型

选择数据类型原则:

  1. 更小的通常更好,尽量使用可以正确存储数据的最小数据类型
  2. 简单数据类型,如:整型比字符串操作代价更低,因为字符集和校对规则(排序规则)使字符比较比整型更加复杂。
  3. 尽量避免NULL,因为可为NULL的列使得索引、索引统计和值比较都复杂,会使用更多的存储空间,被索引时,每个索引记录需要一个额外的字节。

整数类型

TINYINT,SMALLINT,MEDIUMINT,INT,BIGINT,分别使用8,16,24,32,64位存储空间,它们可以存储的值范围从-2(N-1) 到2(N-1) -1,其中N是存储空间的位数。
注意: int(1),int(10), 整数类型定义的1和10只是指定显示宽度,但是实际的值还是存储在数据库中,只是限制终端交互显示的字符宽度。

实数类型

实数是带有小数部分的数字,MySQL支持数据类型,也支持精度。

  1. DECIMAL列,将数字打包保存到一个二进制字符串中,每4个字节存9个数字,如:DECIMAL(15,9),小数点左边存储6个数字,占4字节,小数点后9个数字占4个字节,小数点占一个字节,一共需要9字节。
  2. FLOAT使用4字节,DOUBLE占用8字节。
  3. 在计算金钱的时候,可以使用BIGINT代替,货币单位根据小数乘相应的倍数。

字符串类型

VARCHAR和CHAR是主要的字符串类型。
VARCHAR:
存储变长字符串,需要使用1个或2个额外的字节记录字符串的长度,如果列的最大长度小于等于255字节,使用一个字节,否则使用2个字节,由于是变长的,可能导致UPDATE时,比原来长,页没有更多的空间,假设存储引擎是MyISAM会将行拆成不同的片段存储,InnoDB需要分裂页来使行可以放进页内。(InnoDB会将过长的VARCHAR存储为BLOB)
适用场景:

  1. 字符串的最大长度比平均长度大很多。
  2. 列的更新很少。
    CHAR:
    它是定长的数据类型,会截断字符串后面的空格。
    适用场景:
  3. 存储定长的字符串
  4. 经常变更的数据比VARCVHAR更好。

BLOB和TEXT类型

BLOB采用二进制,TEXT采用字符存储。当这俩类型的值太大时,InnoDB会使用专门的“外部”存储区域进行存储,此时每个值在行内需要1-4个字节出粗一个指针,然后在外部存储区域存储实际的值。
注意: Memory 引擎不支持BLOB和TEXT,如果查询使用了它们并且需要使用隐式临时表,将使用到MyISAM磁盘临时表。如果无法避免,可以再用到BLOB字段的地方使用SUBSTRING(column, length)将列值转换为字符串。

枚举类型(ENUM)

MySQL会将每个值在列表中的位置保存为整数,并且在表的.frm文件中保存“数字—字符串”映射关系的“查找表”
EX:
CREATE TABLE enum_t(
  em ENUM(‘apple’, ‘dog’) NOT NUL
);
INSERT INTO enum_t(em) VALUES(‘dog’),(‘apple’);
这两行数据存储为整数,而不是字符串
SELECT em + 0 FROM enum_t;
查询结果如下:
在这里插入图片描述

枚举字段是按照内部存储的整数而不是定义的字符串进行排序
SELECT em FROM enum_t ORDER BY em;
在这里插入图片描述
可以再查询中使用FIELD()函数显示地指定排序顺序,但为导致MySQL无法利用索引消除排序。
SELECT em FROM enum_t ORDER BY FIELD(em, ‘apple’, ‘dog’);
缺点: 字符串列表固定,添加或者删除字符串必须使用ALTER TABLE。

日期和时间类型

DATETIME: 存储范围从1001年到9999年,精度为秒。它把时间封装成YYYYMMDDHHMMSS的整数中,与时区无关,使用8字节的存储空间。
TIMESTAMP: 存储从1970年1月1日午夜以来的秒数,它和UNIX时间戳相同,使用4字节的存储空间,只能表示1970到2038年,显示依赖时区。

位数据类型

BIT位是字符串类型,在数字上下文的场景中检索时,结果将是位字符串转化为数字。如果存储值为b’00111001’(二进制值等于57)到BIT(8)的列并且检索它,得到的内容是字符码为57的字符串,即ASCII码为57的字符’9’ ('9’的ASCII码是十进制57,十六进制为0x39),但在数字上下文场景中,得到数字57:
CREATE TABLE bites(a bit(8));
INSERT INTO bites VALUES(b’00111001’);
SELECT a, a + 0 FROM bites;
在这里插入图片描述

如何选择标识符

  1. 整数类型通常是标识列最好的选择,因为它们很快并且可以使用AUTO_INCREMENT
  2. ENUM和SET列适合存储固定信息,如:状态、产品类型、人的性别
  3. 尽量避免字符串类型作为标识列,太消耗空间,并且比数字类型慢,MyISAM默认对字符串使用压缩索引,这会导致查询慢很多。
  4. 特殊类型,如IPv4,它实际上是一个32位无符号整数,不是字符串,用小数点分成四段表示法只是阅读容易,应该用无符号整数存储IP地址,并且MySQL提供INET_ATON()和INET_NTOA函数进行转换。

注意事项

  1. 一个表不要有太多的列,MySQL存储引擎API工作时需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,在服务器层将缓冲内容解码成各个列,这个转换过程很消耗性能
  2. 不要做太多的关联。
  3. 防止过度使用枚举(ENUM),因为每修改一次,就是一次ALTER TABLE

范式和反范式

范式

优点:

  1. 更新操作快。
  2. 范式表节省空间
    缺点:
  3. 通常需要关联。

反范式

上面相反的结果

缓存表和汇总表

缓存表:表示存储可以简单地从schema其他表获取(但是每次获取的的速度比较慢)数据的表。
汇总表/累积表:保存的是使用GROUP BY 语句聚合数据的表。
注意:当重建汇总表和缓存表时,要使用“影子表”来实现,“影子表”指的是一张在真实的表“背后”创建的表。当完成建表操作后,可以通过一个原子的重命名操作切换影子表和原表。如下:
DROP TABLE IF EXISTS my_new, my_old;
CREATE TABLE my_new LIKE my;
RENAME TABLE my TO my_old, my_new TO my;
将my 这个名字分配给新建的表之前将原始的my表重命名为my_old,就可以在下一次重建之前保留旧版本的数据,如果新表有问题,可以很容易回滚。

计数器表

可以用这种表缓存一个用户的朋友数、文件下载次数等。
CREATE TABLE counter (
cnt int unsigned not null
)ENGINE=InnoDB;
UPDATE counter SET cnt = cnt + 1;
对于任何想要更新这一行的事务来说,这条记录都有一个全局的互斥锁,导致事务只能串行。我们可以将计数器存在多行中,每次随机选择一个进行更新,需要对表结构修改
CREATE TABLE counter (
slot tinyint unsigned not null primary key,
cnt int unsigned not null
)ENGINE=InnoDB;
然后预先在这张表增加100行数据,现在选择一个随机的槽(slot)进行更新:
UPDATE counter SET cnt = cnt + 1 WHERE slot = RAND() * 100;
获取最终统计数:
SELECT SUM(cnt) FROM counter;
备注:

  1. 如果需要天级别统计维度,只需要加一个日期字段就好。
  2. 如果觉得表数量过大,可以定时周期性的执行任务,合并数据到某一个槽位,并删除其他槽位。

总结

  1. 使用小而简单的数据类型,应该尽可能避免使用NULL值。
  2. 尽量使用相同的数据类型存储相似或相关的值,尤其是在关联条件中使用的列。
  3. 注意可变长字符串,在临时表和排序时可能导致悲观的按最大长度分配内存。
  4. 尽量使用整型标识列。
  5. 避免使用MySQL已经遗弃的特性,如:指定浮点数精度或整数的显示宽度,
  6. 小心使用ENUM和SET。

MySQL索引

索引存储方式

MyISAM使用的是非聚簇索引,InnoDB使用的是聚簇索引。

聚簇索引

概念: 聚簇索引并不是一种索引类型,而是一种数据的存储方式。InnoDB在同一个结构中保存B-Tree索引和数据行,数据行和相邻的键值紧凑的存储在一起。如果没有定义主键,InnoDB会选择一个唯一的非空索引代替,如果没有这样的索引,会隐式定义一个主键作为聚簇索引。
缺点:

  1. 插入速度严重依赖于插入顺序。
  2. 更新聚簇索引的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置。
  3. 插入新行或者主键更新导致需要移动行的时,可能面临页分裂,当行的主键值要求必须将这一行插入到某个已满的页中时,存储引擎将页分裂成两个页面,容纳行,会占用更多的磁盘。
  4. 聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致的数据存储不连续。
  5. 二级索引可能比想象的要更大,因为在二级索引的叶子节点包含了引用行的主键列。

非聚簇索引/二级索引

索引类型

主索引

  1. 特性:主索引唯一,不能有空值
  2. 语句:ALTER TABLE test_index ADD PRIMARY KEY (‘test’);

唯一索引

  1. 特性:唯一,可以有空值
  2. 语句:CREATE UNIQUE INDEX test_unique_index ON `test_table(‘test’);

普通索引

  1. 语句:CREATE INDEX test_index ON `test_table(‘test’);

复合索引

  1. 特性:必须满足最左前缀匹配原则
  2. 语句:CREATE INDEX test_unique_index ON `test_table(‘test1’,‘test2’);

全文索引

  1. 特性:只能放在CHAR、VARCHAR、TEXT类型字段
  2. 语句:CREATE FULLTEXT INDEX test_fulltext_index ON `test_table(‘test1’);

索引名扩展

  1. 覆盖索引:使用到索引并且查询的值为主键,此时不需要回表。
  2. 后缀索引:前缀索引,只是将字符串反转,应用:找某个域名下的所有电子邮箱。

索引实现方式

B-Tree索引

查询类型:
全值匹配、匹配最左前缀、匹配列前缀、匹配范围值、精确匹配某一列并范围匹配另外一列、只访问索引的查询。
在这里插入图片描述

哈希索引

基于哈希表实现
缺点:

  1. 哈希索引只包含哈希值和行指针,而不是存储字段值,所以不能使用索引中的值来避免读取行。
  2. 无法进行排序操作。
  3. 不支持部分索引列的匹配。
  4. 只支持等值查询。
  5. 如果哈希冲突很多,更操作代价很大,需要遍历对应的哈希值的链表中的每一行。

注意:
InnoDB引擎有一个特殊的功能叫做“自适应哈希索引”。当InnoDB注意到某些索引值被使用得非常频繁,它会在内存中基于B-Tree索引之上再创建一个哈希索引,进行快速查找。
自己也可以创建自适应哈希,如:
匹配url,https://www.baidu.com
SELECT id FROM url WHERE url = ‘http://www.mysql.com’;
可以新增一个url_crc列,使用CRC32函数做哈希,查询方式为:
SELECT id FROM url WHERE url_crc = CRC32(‘http://www.mysql.com’) AND url = ‘http://www.mysql.com’;
MySQL优化器会使用这个选择性很高而体积很小的基于url_crc列的索引来完成查找。
可以使用MySQL的触发器,同步更新哈希索引值。
CREATE TRIGGER self_hash_crc BEFORE INSERT ON self_hash_table FOR EACH ROW BEGIN SET NEW.url_crc=crc32(‘NEW.url’);

索引优点

  1. 减少服务器需要扫描的数据量
  2. 帮助服务器避免排序和临时表
  3. 将随机I/O变为顺序I/O

高效使用索引

  1. 查询方式必须是独立的,即查询条件不能有函数或表达式。
  2. 选择合适的索引列顺序,如:
    SELECT SUM(test1 = 1), SUM(test2 = 2) FROM test;
    如果是 SUM(test1 = 1): 1000, SUM(test2 = 2) = 100,我们应该=将索引列test2放到前面,因为对应条件值的test2数量更小(这样方法依赖于具体的数值)。
  3. 使用索引扫描排序,只有当ORDER BY 子句引用的字段全部为第一个表时,才能使用索引做排序。
  4. 当ORDER BY不满足最左前缀,但是第一列如果是常数,同样可以走复合索引。
  5. 尽可能的将需要做范围查询的列放到索引的后面,以便优化器能使用尽可能多的索引列。

名词解释

  1. 行碎片:数据行被存储为多个地方的多个片段中。
  2. 行间碎片:指逻辑上顺序的页,或者行在磁盘上不是顺序存储的。
  3. 剩余空间碎片:指数据页中有大量的空余空间。这会导致服务器读取大量不需要的数据。

查询性能优化

查询优化、索引优化、库表结构优化

为什么查询会慢?

查询执行流程:

Created with Raphaël 2.3.0 Start 服务器解析,生成执行计划 执行 客户端 end

上面的执行包含检索数据到存储引擎的调用以及调用后的数据处理,如:排序、分组。
在网络、CPU计算、生成统计信息和执行计划、锁(互斥等待)等操作花费时间,尤其是向底层存储引擎检索数据的调用,需要在内存上操作,CPU操作和内存不足时导致I/O操作上消耗时间。根据存储引擎不同,还会产生大量的上下文切换及系统调用。

优化数据访问

  1. 确认程序是否在检索大量超过需要的数据
  2. 多表关联时返回全部列
  3. 总是取出全部列
  4. 重复查询相同的数据

重构查询的方式

  1. 分解查询:一个复杂的拆分多个简单的
  2. 切分查询:如果用一个大的语句一次性的完成的话,则可能需要一次锁住很多数据,占满整个事务日志,耗尽系统资源,阻塞很多小的但很重要的查询,将一个大的DELETE语句切分多个小的查询可以尽可能的小地影响MySQL性能,同时还可以减少MySQL复制的延迟。(一次性的删除一万行数据,一般来说是一个比较高效且对服务器影响较小的)
  3. 分解关联查询:对每一个表进行一次单表查询,将结果在应用程序中进行关联
    优势:
    (1)让缓存的效率更高
    (2)查询分解后,执行单个查询减少锁的竞争
    (3)在应用层做关联,可以更容易的对数据库进行拆分,更容易做高性能和可扩展性
    (4)减少冗余记录查询,在应用层关联,某条记录只需要查询一次
    (5)在应用层做,相当与实现了哈希关联,而不是使用MySQL的嵌套循环关联

查询执行的基础

在这里插入图片描述

  1. 客户端发送一条查询给服务器
  2. 服务器先检查查询缓存,如果命中缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段
  3. 服务器进行SQL解析,预处理,再由优化器生成对应的执行计划
  4. MySQL根据优化器生成的执行计划,调用存储引擎的API来执行查询
  5. 将结果返回给客户端

MySQL客户端/服务器通信

  1. 采用半双工通信协议,同一时刻只能一方向另一方发送数据。
  2. 查询状态
    (1)Sleep:线程正在等待客户端发送新的请求
    (2)Query:线程正在执行或者正在将结果发送给客户端
    (3)Locked:在MySQL服务层,该线程正在等待表锁
    (4)Analyzing and statistics:线程正在收集存储引擎的统计信息,并生成查询的执行计划。
    (5)Copying to tmp table [on disk]:线程正在执行查询,并且将结果都复制到一个临时表中(一般为GROUP BY、文件排序、UNION操作,如果还有on disk 标记,表示MySQL将内存临时表放到磁盘上)
    (6)Sorting result:线程正在对结果集进行排序
    (7)Sending data:线程可能在多个状态之间传送数据,或者在生成结果集,或者在向客户端返回数据

查询缓存

如果查询缓存打开,MySQL会优先检查查询是否命中缓存,这个检查是通过一个大小写敏感的哈希查询实现的

查询优化处理

语法解析器和预处理
MySQL通过关键字将SQL语句进行解析,并生成一颗对应的“解析树”。解析器将使用MySQL语法规则验证和解析查询。预处理器则根据规则进一步检查解析树是否合法,如:数据表和数据列是否存在,还会解析名字和别名,看看是否有歧义。
查询优化器
MySQL可以优化的数据类型

  1. 重新定义关联表的顺序
  2. 将外连接转化成内连接
  3. 使用等价的变换规则
  4. 优化COUNT()、MIN()、MAX():查某一列最小值,只需要查B-Tree索引最左端的记录
  5. 预估并转化为常数表达式:一个表达式可以转化为常数
  6. 覆盖索引扫描
  7. 子查询优化
  8. 提前终止查询:当发现一个不成立的条件,立马返回空。
  9. 等值传播:如果两列的值通过等式关联,MySQL会把其中一个列的where条件传递到另一列。如:select film.film_id from sakila.film inner join sakila.film_actor using(film_id) where film.film_id > 500;
    film_id不仅适用于film表,对film_actor同样适用。
    MySQL会自动转换为 :select film.film_id from sakila.film inner join sakila.film_actor using(film_id) where film.film_id > 500 and film_actor.film_id > 500;
  10. 列表IN()的比较:MySQL会将IN()列表数据先排序,然后通过二分查找的方式确定列表的值是否满足条件,这是一个O(logn)复杂度的操作

执行计划

MySQL生成查询的一颗指令树,然后通过存储引擎执行完成这颗指令树并返回结果。总是从一个表开始一直嵌套循环、回溯完成所有表关联。是一颗左侧深度优先的数。

排序优化

  1. 两次传输排序(旧版本)
    读取行指针和需要排序的字段,对其进行排序,然后将排序结果读取所需的数据行。这需要进行两次数据传输,先从数据表读取两次数据。第二次读取数据的时候,因为是读取排序列进行排序后的所有记录,这会产生大量的随机I/O,所以两次传输的成本非常高。
  2. 单次传输排序(新版本)
    先读取查询所需要的所有列,然后再根据给定的列进行排序,最后返回排序结果。缺点:数据量非常大,会额外占用大量的空间。

猜你喜欢

转载自blog.csdn.net/weixin_43885417/article/details/118268534