MySQL索引性能优化分析1

转载:MySQL索引性能优化分析_mysql索引和性能分析(实战)-CSDN博客

重点分享:http://t.csdnimg.cn/zFhbr

一、MySQL B+树索引回顾
(一)索引简单背景
在数据库操作中,经常需要查找特定的数据,以一条“select * from zyftest where id=10000”为例,数据库必须从第一条记录来时遍历,直到找到id为10000的数据,这样的效率显然非常低。所以,MySQL允许建立索引来加快数据表的查询和排序。

索引的目的在于提高查询效率,与我们查阅图书所用的目录是一个道理:先定位到章,然后定位到该章下的一个小节,然后找到页数。相似的例子还有:查字典,查火车车次,飞机航班等。

本质都是:通过不断地缩小想要获取数据的范围来筛选出最终想要的结果,同时把随机的事件变成顺序的事件,也就是说,有了这种索引机制,我们可以总是用同一种查找方式来锁定数据。

数据库的索引是对数据库表中一列或多列的值进行排序后的一种结构,其作用就是提高表中数据的查询速度。MySQL中的索引可以大致分为以下几类:主键索引、唯一索引、普通索引、全文索引、组合索引、空间索引。

普通索引是由KEY或INDEX定义的索引,是MySQL的基本索引类型,其值是否唯一和非空由字段本身的约束条件所决定。
唯一索引是指由UNIQUE定义的索引,该索引所在字段的值必须是唯一的。
全文索引是由FULL TEXT定义的索引,只能创建在CHAR、VARCHAR或TEXT类型的字段上,而且,现在只有MyIASM存储引擎支持全文索引。
主键索引 PRIMARY KEY,它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引。(注意:一个表只能有一个主键)
组合索引值得是在表中多个字段上创建索引,只有在查询中使用了这些字段中的第一个字段时,该索引才会被使用。
空间索引是由SPATIAL定义的索引,它只能创建在空间数据类型的字段上。MySQL中空间数据类型有四种:GEOMETRY、POINT、LINESTRING和POLYGON。注意创建空间索引的字段必须将其声明为NOT NULL,并且空间索引只能在存储引擎为MyISAM的表中创建。
索引的优缺点主要体现在:

优势:可以快速检索,减少I/O次数,加快检索速度;根据索引分组和排序,可以加快分组和排序;
劣势:索引本身也是表,因此会占用存储空间,一般来说,索引表占用的空间的数据表的1.5倍;索引表的维护和创建需要时间成本,这个成本随着数据量增大而增大;构建索引会降低数据表的修改操作(删除,添加,修改)的效率,因为在修改数据表的同时还需要修改索引表;
(二)B+树索引简单分析
MySQL中,索引是在存储引擎层实现的,不同的存储引擎支持的索引类型不同,对索引的组织实现方式也不同。我们平时最常使用的是B+树索引,B+树是为磁盘或其他存取设备设计的一种平衡查找树,所有记录节点按照键值大小顺序存放在同一层的叶节点上,各叶节点通过指针进行链接,先来看一个B+树的结构图

通过图可以看到其基本特征如下:

非叶节点只存关键字以及索引下一层节点的指针
所有叶节点在同一层,包含全部关键字和指向记录的指针,并且按照关键字从小到大顺序链接
可以看到相比一般二叉树,B+树的单个节点能存储更多信息,减少了磁盘 IO 的次数,从而提升了查找速度,而且叶节点形成有序链表,非常适合进行范围查询。

扩展:聚簇索引+普通索引
MySQL常用两种引擎InnDB和MyISAM对B+树的索引组织形式稍有不同。

InnDB的主键索引叶节点上直接存储了行记录,行记录按物理顺序存储,也叫做聚簇索引,普通索引叶节点上存储的是主键索引值,称之为辅助索引。因此如果使用普通索引查询会走两遍索引:先通过辅助索引找到主键索引值,再到主键索引中检索获取记录行,这个过程叫回表。

但是MyISAM中,普通索引和主键索引一样,叶节点存储的都是记录的物理地址,只会走一次索引。

二、索引相关基本操作
索引是数据库中一种常用的优化技术,它可以加快数据的查找速度,提高数据库的查询效率。在 MySQL 中,可以通过以下几种方式来创建和管理索引。

(一)创建索引
可以通过 CREATE INDEX 命令创建索引,语法如下:

CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name 
ON table_name (column1, column2, ...);
其中,UNIQUE 表示创建唯一索引,FULLTEXT 表示创建全文索引,SPATIAL 表示创建空间索引,index_name 是索引的名称,table_name 是要创建索引的表名,(column1, column2, ...) 是要创建索引的列名。

(二)查看索引
可以通过 SHOW INDEX 命令查看表的索引信息,语法如下:

SHOW INDEX FROM table_name;
该命令将列出表的所有索引,包括索引的名称、列名、索引类型、是否唯一等信息。

(三)删除索引
可以通过 DROP INDEX 命令删除索引,语法如下:

DROP INDEX index_name ON table_name;
其中,index_name 是要删除的索引的名称,table_name 是要删除索引的表名。

(四)优化查询
可以通过索引来优化查询语句的执行效率。MySQL 中,可以使用 EXPLAIN 命令来查看查询语句的执行计划,进而优化查询。如果查询语句没有使用索引,可以考虑添加索引或者修改查询语句的条件,使其能够利用索引来加快查询速度。

需要注意的是,虽然索引可以加快查询速度,但是过多的索引也会影响数据库的性能,因为索引需要占用存储空间,并且在修改表数据时也会增加操作的复杂度。因此,在创建索引时需要根据实际情况进行选择和权衡,避免过度使用索引。这个后面会细分析。

三、索引优化分析
索引的优化是非常必要的,因为索引可以极大地提高数据库的查询效率,特别是对于大量数据的表。在建立索引时,需要权衡利弊。一般来说,对于经常被查询、查询效率需要提高的列,可以建立索引;而对于不经常被查询的列,或者存储空间比较紧张的情况下,可以考虑不建立索引。同时,可以考虑对于一些查询频繁但数据更新较少的列建立索引,并定期进行索引维护来保证查询效率。因此,正确的创建和使用索引是实现高性能查询的基础。

(一)高效创建索引
主键索引规范
建议使用int/bitint类型自增id作为主键,避免使用uuid等无序数据作为主键。有序主键能保证顺序io提升性能,无序主键是随机io,会导致聚簇索引的插入变成完成随机和频繁页分裂。

选择合适索引列顺序
在多列的B+树索引中,索引会按照最左列进行排序,其次是第二列,因此索引的顺序对于查询是至关重要的,将选择性更高的字段放到索引的前面,可以更快地过滤出需要的行。

假设有一个学生表(students),包含了学生的ID、姓名、年龄等字段。为了加快查询效率,我们希望建立一个联合索引,包含了年龄、姓名两个字段。使用以下语句来创建该联合索引:

CREATE INDEX age_name_idx ON students (age, name);
其中,age_name_idx是索引的名称,students是表名,age和name是需要建立索引的字段名。

建立该联合索引后,就可以使用类似如下的 SQL 查询语句,来查询符合年龄、性别条件的学生数据,并使用该索引进行优化:

SELECT * FROM students WHERE age = 20 AND name = '张靓颖';
在查询数据时,MySQL 就会自动使用该联合索引,提高查询效率。

可以预先计算下哪个列的选择性更高

select count(distinct age)/count(*) as age_selectivity, 
count(distinct name)/count(*) as name_selectivity 
from T
根据计算结果,选择值更大的列作为索引列的第一项。

建立覆盖索引
假设我们有一个订单表(orders),包含了订单号、下单时间、用户ID、订单总金额等字段。为了提高查询效率,我们希望建立一个覆盖索引,包含了订单号、下单时间、订单总金额三个字段。

可以使用以下语句来创建该覆盖索引:

CREATE INDEX orders_idx ON orders (order_no, create_time, total_amount);
其中,orders_idx是索引的名称,orders是表名,order_no、create_time和total_amount是需要建立索引的字段名。

当我们需要查询订单号为某个值的订单数据时,可以使用以下 SQL 查询语句,来查询符合条件的订单数据,并使用该覆盖索引进行优化:

SELECT order_no, create_time, total_amount 
FROM orders WHERE order_no = '123456';
在查询数据时,MySQL 就会使用该覆盖索引进行优化,直接从索引中获取到需要的数据,避免了对数据表的全表扫描,提高了查询效率。这种索引被称为覆盖索引,可以帮助我们避免回表操作。

覆盖索引可以极大地提高性能,因为只需要扫描索引,这种方式能带来很多好处:

索引条目一般远小于数据行大小,只读取索引,极大减少数据访问量,而且索引更容易全部放入内存,对IO密集型应用性能提升很大

索引按照列顺序存储,范围查询会比随机从磁盘读取每一行数据的IO要少得多

InnoDB的辅助索引覆盖查询,可以避免对主键索引的二次查询

使用前缀索引
前缀索引是指对于一个列的值,只取其前几个字符建立索引。使用前缀索引的好处是可以大大减小索引的大小,提高查询效率。

举个例子,我们有一个用户表(user),包含了用户ID、用户名、邮箱等字段。假设我们需要对用户名进行索引,但是用户名过长,建立完整的索引可能会占用较多的空间,影响索引效率。这时,可以使用前缀索引来优化索引。可以使用以下 SQL 语句来创建该前缀索引:

CREATE INDEX username_prefix_idx ON user (username(10));
其中,username_prefix_idx是索引的名称,user是表名,username是需要建立索引的字段名,(10)表示该索引只对用户名的前10个字符进行建立。

需要注意的是,对于使用前缀索引的字段,查询时也需要使用该前缀才能使用索引优化。比如,以下 SQL 查询语句可以使用该前缀索引进行优化:

SELECT * FROM user WHERE username LIKE 'abc%';
而以下 SQL 查询语句无法使用该前缀索引进行优化:

SELECT * FROM user WHERE username LIKE '%abc%';
因为 %abc% 包含了用户名的后缀,无法使用前缀索引进行优化。

遇到前缀区分度不够好的情况下,比如我们国家的身份证号有18位,其中前6位是地址码,所以同一个县的人身份证号前6位一般是相同的。如果维护的是一个县的公民信息系统,对身份证号做长度为6的前缀索引区分度会很低,但索引长度选取越占用磁盘空间越大,相同数据页能放下的索引值就越少,搜索效率也就越低。

有两种方法能在达到相同的查询效率的同时占用更小的空间:

第一种方式是使用倒序存储。我们可以将身份证号倒过来存储,每次查询的时候这么写

select * from T where id_card = reverse('input_id_card')
由于身份证号后6位没有地址码这样的重复逻辑,所以能够提供足够的区分度。

第二种方式是使用hash字段。我们可以在表上再创建一个整数字段,用来保存身份证的校验码,同时在这个字段上创建索引

alter table T add id_card_crc int unsigned, add index(id_card_crc)
每次插入新记录的时候,都用crc32()这个函数得到身份证校验码填到这个字段。由于校验码可能存在冲突,所以查询语句where部分要判断id_card的值是否相同

select * from T 
where id_card_crc = crc('input_id_card') 
and id_card='input_id_card'
这样,索引的长度就变成了4个字节,比原来小了很多。

利用索引扫描做排序
在MySQL中,如果我们使用ORDER BY对查询结果进行排序,如果数据量较大,可能会导致性能下降,因为MySQL会在内存或磁盘上对所有查询结果进行排序。为了避免这种情况,我们可以利用索引扫描来进行排序。具体来说,我们可以利用覆盖索引或者索引合并的方式来实现索引扫描排序。

利用覆盖索引进行排序
我们可以建立一个包含ORDER BY字段和需要查询的字段的索引,这样MySQL可以使用索引扫描来满足ORDER BY操作,而不必再去扫描表中其他的行。

假设对上面students表需要按照age字段进行排序,可以这样建立索引:

ALTER TABLE students ADD INDEX age_index(age, id);
这样,我们在进行查询时,就可以利用age_index索引来排序了:

SELECT id, name, age FROM students ORDER BY age;
利用索引合并进行排序
当我们需要对多个字段进行排序时,我们可以建立多个单列索引,MySQL会自动选择最优的索引组合来进行排序。这个过程被称为索引合并。

例如,假设我们需要按照name和age字段进行排序,我们可以这样建立索引:

ALTER TABLE students ADD INDEX name_index(name);
 
ALTER TABLE students ADD INDEX age_index(age);
这样,在进行查询时,MySQL会自动选择最优的索引组合来满足ORDER BY操作:

SELECT id, name, age FROM students ORDER BY name, age;
需要注意的是,索引合并会增加查询的开销,因为MySQL需要扫描多个索引,将结果进行合并。因此,在建立索引时需要根据实际情况进行权衡,选择最优的索引策略。

避免创建冗余索引
在数据库中,创建过多的索引会导致查询性能下降、插入/更新/删除操作变慢等问题,而创建冗余索引则是其中一种常见的问题。冗余索引指的是已经存在一条索引可以满足查询条件,但是又创建了另一条重复的索引。这种索引不仅浪费存储空间,还会使得数据库维护索引的代价更大,影响数据库性能。下面是一个创建了students冗余索引的例子:

CREATE TABLE `students` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(50) DEFAULT NULL,
  `age` int(11) DEFAULT NULL,
   PRIMARY KEY (`id`),
   KEY `idx_name` (`name`),
   KEY `idx_age` (`age`),
   KEY `idx_name_age` (`name`,`age`)  -- 冗余索引
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
上述例子中,虽然已经在name和age字段上都创建了单独的索引,但还创建了一个覆盖了这两个字段的联合索引idx_name_age。如果查询条件只涉及name或age字段中的一个,那么使用单独的索引即可,而无需使用idx_name_age索引。

避免创建冗余索引的方法包括:

仔细分析查询需求,只创建必要的索引。
定期检查数据库中的索引,及时删除冗余的索引。
尽量避免创建覆盖索引,因为它可能包含多个不必要的字段。
需要注意的是,索引的设计并不是一成不变的,需要根据具体的业务需求和数据特征不断进行调整和优化。

(二)正确使用索引
正确使用索引可以避免因过多的无效索引造成的额外的存储空间和内存消耗,避免在大数据量和高并发的情况下出现慢查询和数据库性能下降的问题,同时也可以提高系统的安全性,减少数据损失的风险。因此,在数据库的设计和使用中,正确使用索引是非常重要的一步。

最左前缀匹配原则
对于联合索引,MySQL会一直向右匹配,直到遇到范围查询(< 、>、between、like等)就停止匹配。例如表有联合索引(a,b,c),只有a、ab、abc类型的查询会走这个索引,特别要注意对这种联合索引的使用

-- 只有a走联合索引
select * from table where a>1 and b=2 and c=3
-- 不会走联合索引
select * from table where b=2 and c=3
禁止在索引字段上做数学运算或函数运算
在索引字段上进行数学运算或函数运算会导致MySQL无法使用该索引,从而导致查询变慢。这是因为数学运算或函数运算会对字段进行计算,使得MySQL无法通过直接比较索引来确定查询结果。

select * from table where age < 23;
select * from table where age + 1 > 50;
select * from table where month(updateTime) = 7;
上面两个查询分别对索引列使用了数学运算和函数运算,通过explain查看执行计划,可以发现他们都是走的全表扫描。

常见的隐式类型转换大坑
select * from table where oplogid=123456
操作日志oplogid这个字段上有索引,但是explain的结果却显示这条语句会全表扫描。原因在于oplogid的字符类型是varchar(32),比较值却是整型,故需要做类型转换。在MySQL中字符串和数字进行比较的话是将字符串转换成数字,对于优化器来说,上面的查询语句相当于

select * from table where cast(oplogid as signed int)=123456
也就是说,它对索引字段做了函数运算,所以会出现索引失效。

常见的隐式字符编码转换大坑
两个用tradeid关联的表查询

select * from oploglog, oploglogdetail 
where 
oploglog.tradeid=oploglogdetail.tradeid and oploglog.id=1
Tradelog用tradeid关联tradedetail时,理应会走Tradedetail的tradeid索引快速定位到等值的行,实际上却走了全表扫描。如果仔细检查表结构定义的话,可以发现Tradelog字符集是utf8,Tradedetail的字符集是utf8mb4,由于utf8mb4是utf8的超集,当两个类型的字符串在做比较时,MySQL会先把utf8字符集的字符串转换成utf8mb4再做比较。所以,它也属于对索引字段做函数操作,索引会失效。

使用like时避免前缀模糊查询'%xxx%'
一般情况下不鼓励使用like,如果要使用的话避免以通配符%和_开头,即like '%xxx%',它不会走索引,而like 'xxx%'能走索引。若要提高效率,可以考虑使用全文索引。上面已经说过了。

尽量避免负向查询
负向查询指的是在查询中使用不等于(<>)或不包含(NOT IN、NOT EXISTS等)的条件,即查询不满足某些条件的记录。负向查询通常会导致数据库执行全表扫描,影响查询性能。

下面是一个简单的例子,假设我们有一个 users 表,其中包含了用户的姓名、年龄、性别、地址等信息,现在需要查询不是女性的用户信息:

SELECT * FROM users WHERE gender != 'female';
这个查询会扫描整个 users 表,并且无法利用 gender 字段上的索引,从而导致查询效率低下。为了避免负向查询,我们可以改写查询语句,如下所示:

SELECT * FROM users WHERE gender = 'male';
这个查询只需要扫描 gender 等于 male 的记录,可以充分利用 gender 字段上的索引,因此查询效率更高。

避免使用select *
查询时尽量不要使用select *,而是只查出需要的字段,因为select * 无法利用覆盖索引优化,还会为服务器带来额外的IO、内存和cpu的消耗

四、扩展优化
(一)设计优化
字段类型设计
数据类型越小越好:越小的数据类型通常在磁盘、内存和CPU缓存中都需要更少的空间,处理起来更快。
数据类型越简单越好:简单的数据类型操作代价更低,比如,字符串操作就比整型操作开销更大
尽量避免使用null:null在MySQL中不好处理,存储需要额外空间,运算也需要特殊的运算符,含有null的列很难进行查询优化。应当指定列为not null,用0、空串或其他特殊的值代替空值,比如定义为int not null default 0。
范式化
在写密集的场景,表范式化设计对性能的提升也是明显的。当数据较好范式化时,修改的数据更少,而且范式化的表通常要小,可以有更多的数据缓存在内存中,所以执行操作会更快。缺点则是查询时需要更多的关联。

第一范式:字段不可分割,数据库默认支持
第二范式:消除对主键的部分依赖,可以在表中加上一个与业务逻辑无关的字段作为主键,比如用自增id
第三范式:消除对主键的传递依赖,可以将表拆分,减少数据冗余
存储引擎选择
一般而言,选择默认的Innodb就足够了,如果要追求更好的性能,可以根据使用场景结合存储引擎的特点来选择使用最合适的存储引擎:

如果对事务安全(ACID)要求较高,需要并发控制,或者表上数据更新、删除很频繁,就要选择InnoDB引擎,InnoDB能确保事务完整提交和回滚,并且能有效降低更新、删除操作导致的锁定
如果应用主要以插入和查询操作为主,对事务和并发控制没有要求,可以选择MyISAM引擎,MyISAM提供了较高的处理效率
如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存中的Memory引擎,Memory引擎可以提供极快的访问速度。MySQL就使用Memory引擎作为临时表,存放查询的中间结果
如果只有插入和查询操作,不要求事务安全,但是对存储成本要求较高,可以选择Archive引擎,Archive支持高并发的插入操作,而且对数据的压缩比很高,适合存储归档数据,例如日志信息
适当分库分表策略
数据库设计的分库分表是为了解决大数据量、高并发的情况下数据库性能问题的一种解决方案。一般来说,采用分库分表可以有效地提升数据库的性能和可扩展性,但是需要考虑如下问题:

数据库切分的粒度:在设计分库分表方案时需要根据业务量和数据量确定切分的粒度。一般情况下,可以按照业务场景和数据访问模式进行划分,例如按照用户ID、时间、地理位置等进行划分。
数据库扩容和迁移:在分库分表的设计中需要考虑到数据库的扩容和迁移问题,需要保证分库分表的策略是可扩展的,并且在迁移时不会造成数据丢失或数据访问异常。
数据库一致性和事务管理:分库分表可能会引入分布式事务和分布式锁的问题,需要特别注意分布式环境下的一致性和事务管理问题。
数据库性能优化:在分库分表的设计中需要考虑到数据库的性能问题,需要使用合适的索引、缓存等技术来优化数据库的性能,以保证数据库的高效访问。
数据库架构的维护和管理:分库分表会带来数据库架构的复杂性,需要考虑到维护和管理的问题,包括数据库备份、监控、调优、维护等方面。
针对以上问题,一些分库分表策略建议:

根据业务场景和数据访问模式确定切分粒度,并在划分时保证数据的平衡性和访问的均衡性。
尽量采用水平切分方式,以减少数据库的复杂性和迁移难度。
在设计分库分表策略时要考虑到数据库的扩容和迁移问题,可以采用分布式数据库、数据同步等技术来实现。
尽量避免在分布式环境下使用分布式事务和锁,可以采用消息队列、异步处理等技术来避免这类问题。
在分库分表设计中,应尽量使用缓存、索引等技术来优化数据库性能,以保证高效访问。
在数据库架构维护和管理方面,可以采用自动化运维、云数据库等技术来简化维护和管理工作。
(二)查询优化
优化COUNT()查询
在MySQL中,使用COUNT(*)进行计数时,如果查询的表中有主键或非空唯一索引,则MySQL可以直接使用该索引进行计数,因此性能与使用COUNT(column)相当。而如果查询的表没有主键或非空唯一索引,则MySQL会执行全表扫描来计算行数,此时性能会比使用COUNT(column)差。因此,在查询性能方面,使用COUNT(*)和COUNT(column)并没有绝对的优劣之分,需要根据具体情况来选择使用哪种方式。

IN列表代替多个OR
MySQL会对in列表的值排序,搜索时通过二分查找来判断是否在列表中。所以in的时间复杂度是O(logn),而or的时间复杂度是O(n),in的效率更高。如果or有大量数据,建议使用in。

select * from T where name='a' or name='b' or name='c'
--改为
select * from T where name in ('a','b','c')
LIMIT分页优化
在进行分页查询时,LIMIT是常用的关键字,但是当数据量较大时,使用LIMIT会有一定的性能问题。为了优化LIMIT分页,可以考虑以下两种方案:

使用游标分页
使用游标分页的原理是,在每次查询时,只查询指定数量的数据,然后再记录下最后一条数据的位置,作为下一次查询的起始位置,以此类推。这种方式的好处是,不需要将所有的数据都查询出来,减少了查询的数据量,可以有效提高查询效率。但是,使用游标分页的缺点是,需要在程序中维护游标,增加了程序的复杂度。

使用联合查询分页
使用联合查询分页的原理是,先查询出指定数量的主键,然后再使用主键去查询对应的数据,以此来达到分页的效果。这种方式的好处是,只需要查询主键,可以大大减少查询的数据量,提高查询效率。但是,使用联合查询分页的缺点是,需要进行两次查询,增加了查询的时间。

总的来说,在进行分页查询时,要根据具体情况选择合适的优化方案,以达到较好的查询效果。

假设有一张名为students表,有10000条记录,每次查询需要分页展示10条数据,那么可以使用如下的SQL语句进行分页查询:

SELECT * FROM students LIMIT 0, 10; -- 查询第1页数据
SELECT * FROM students LIMIT 10, 10; -- 查询第2页数据
SELECT * FROM students LIMIT 20, 10; -- 查询第3页数据
这里的LIMIT语句中,第一个参数指定了查询结果的起始行数,第二个参数指定了查询结果的行数。

但是,如果数据库中有大量数据,这样的查询会非常慢。因此,可以通过优化来提高查询效率。

首先,为了避免全表扫描,应该在students表上创建一个主键索引:

ALTER TABLE students ADD PRIMARY KEY (id);
接着,可以将查询语句进行优化,将起始行数作为查询条件,这样就可以直接命中索引,提高查询效率:

SELECT * FROM students WHERE id > 0 LIMIT 10; -- 查询第1页数据
SELECT * FROM students WHERE id > 10 LIMIT 10; -- 查询第2页数据
SELECT * FROM students WHERE id > 20 LIMIT 10; -- 查询第3页数据
这里的查询语句中,WHERE子句中的id > x条件就是根据上一页最后一条数据的id值作为查询条件,查询下一页数据。这样就可以避免全表扫描,提高查询效率。

优化UNION语句
Union语句用于将两个或多个查询结果合并为一个结果集,但是在使用Union语句时也需要注意性能问题。以下是一些优化Union语句的技巧:

尽量使用Union All代替Union操作,因为Union All不会去重,可以避免大量的排序操作,从而提高查询效率。
尽量在应用程序中分页处理,而不是在Union语句中使用Limit进行分页。在Union语句中使用Limit进行分页,可能会导致整个Union语句都要执行,然后再返回前面的N行记录,这样效率很低。
尽量避免在Union语句中使用子查询。子查询会导致额外的查询操作,从而降低查询效率。
尽量将所有的查询都写成类似的格式,包括列名、列顺序、数据类型等等。这样可以避免进行额外的转换操作,从而提高查询效率。
在使用Union语句时,如果有可能,尽量使用简单的查询,避免使用复杂的联合查询,这样可以避免影响查询效率。
在使用Union语句时,尽量使用完整的列名而不是*,因为使用*会导致额外的查询操作,从而影响查询效率。
尽量将Union语句放在子查询中,从而可以避免额外的查询操作,提高查询效率。
总之,Union语句可以帮助我们将多个查询结果合并为一个结果集,但是在使用Union语句时需要注意一些性能问题,尽量避免影响查询效率的操作。

假设有两张表,一张是 table1,有字段 id 和 name,另一张是 table2,有字段 id 和 age。现在要将两张表中的记录合并,并按 id 排序。一种常见的写法是使用 UNION:

SELECT id, name FROM table1
UNION
SELECT id, NULL FROM table2
ORDER BY id;
这里第二个 SELECT 语句中使用了 NULL,是为了让 table1 和 table2 中的记录在合并后拥有同样的字段数。但是这样会导致 MySQL 在执行排序时使用文件排序算法,从而降低查询效率。

一个优化方法是使用 UNION ALL,并使用 IFNULL 函数为 table2 的 age 字段设置默认值:

SELECT id, name FROM table1
UNION ALL
SELECT id, IFNULL(age, 0) FROM table2
ORDER BY id;
这样可以避免使用文件排序算法,提高查询效率。同时,为了减少查询的数据量,可以使用 LIMIT 进行分页查询。例如:

SELECT id, name FROM table1
UNION ALL
SELECT id, IFNULL(age, 0) FROM table2
ORDER BY id
LIMIT 10, 10;
这样可以查询出第 11~20 条记录。

优化JOIN语句
优化 JOIN 语句是数据库优化的一个重要方向之一,可以有效提高查询性能。以下是一些优化 JOIN 语句的方法:

确保JOIN操作的连接字段有索引,可以使用EXPLAIN命令查看是否使用了索引,如未使用,则需要创建索引;
避免在JOIN语句中使用子查询,尽量将子查询提前执行并将结果保存在临时表中,然后在JOIN语句中使用临时表;
尽量避免使用过多的JOIN语句,JOIN语句会消耗大量的计算资源,同时也会影响查询性能,应当尽量减少JOIN的次数;
对于大表进行JOIN操作时,应当尽量使用JOIN ON 条件进行连接,而不是WHERE子句进行筛选,这样可以减少不必要的计算;
可以通过调整MySQL的连接缓存大小,以达到优化JOIN语句的效果,具体方法可以参考MySQL文档。
下面是一个使用JOIN语句进行查询的示例,对其进行优化:

-- 普通的 JOIN 语句
SELECT *
FROM orders
JOIN customers ON orders.customer_id = customers.customer_id
JOIN products ON orders.product_id = products.product_id
WHERE orders.order_date >= '2022-01-01';
-- 优化后的 JOIN 语句
SELECT *
FROM orders
JOIN (
    SELECT customer_id, customer_name
    FROM customers
) AS c ON orders.customer_id = c.customer_id
JOIN (
    SELECT product_id, product_name
    FROM products
) AS p ON orders.product_id = p.product_id
WHERE orders.order_date >= '2022-01-01';

在优化后的示例中,使用了子查询将需要JOIN的表的关键字段和名称提前查询并保存到临时表中,避免了在JOIN语句中进行大量的子查询操作,从而提高了查询性能。

参考资料:
1.传智播客教育科技股份有限公司-高教产品研发部,《MYSQL数据库入门》,清华大学出版社,2018.

2.Mysql使用索引的正确方法及索引原理详解_Mysql_脚本之家

3.深入理解MySQL索引原理和实现——为什么索引可以加速查询?_为什么查询语句会加快查询速度_tongdanping的博客-CSDN博客

4.MySQL 索引 | 菜鸟教程

5.https://www.cnblogs.com/realshijing/p/8419732.html

6.《高性能MySQL》

7.《MySQL技术内幕:InnodDB存储引擎》

8.极客时间《MySQL实战45讲

9.蔡泽胤, 《MySQL核心原理与性能优化》
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/xiaofeng10330111/article/details/105360974

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值