了解Mysql优化吗?如何优化索引?

对索引使用左或者左右模糊匹配

在MySQL中,LIKE 模糊查询可能会导致性能问题,特别是当使用通配符 % 开头时,因为这通常会导致全表扫描,也就是 like %xx 或者 like %xx% 这两种方式 (左或者左右模糊匹配的时候) 都会造成索引失效。

  • 前缀匹配:一般使用LIKE 'prefix%'的形式,这种情况MySQL能够利用索引
sql 复制代码
SELECT * FROM users WHERE username LIKE 'seven%';
  • 对于一定需要匹配后缀的情况(即LIKE '%suffix'),可以创建一个辅助列存储反转字符串,并基于此列进行前缀匹配。
sql 复制代码
ALTER TABLE users ADD reversed_username VARCHAR(255); 
UPDATE users SET reversed_username = REVERSE(username); 
CREATE INDEX idx_reversed_username ON users(reversed_username);

计算(使用函数)

因为索引保存的是索引字段的原始值,而不是经过计算后的值,自然就没办法走索引了。

函数计算或者表达式计算都没办法走索引

sql 复制代码
//函数计算
select * from t_user where length(name)=6;

//表达式计算
select * from t_user where id + 1 = 10;

不过,从 MySQL 8.0 开始,索引特性增加了函数索引,即可以针对函数计算后的值建立一个索引,也就是说该索引的值是函数计算后的值,所以就可以通过扫描索引来查询数据。

但是在索引范围之外使用函数是不影响索引的: 即函数不在索引范围(即WHERE或ON条件)使用时,索引仍然有效。例如,在 SELECT 列表、ORDER BY、GROUP BY等地方使用函数通常不影响索引的使用。

类型转换

  1. 如果索引字段是字符串类型,但是在条件查询中,输入的参数是整型的话,那么这条语句会走全表扫描。
  2. 但是如果索引字段是整型类型,查询条件中的输入参数即使字符串,是不会导致索引失效,还是可以走索引扫描。

原因在于MySQL 在遇到字符串和数字比较的时候,会自动把字符串转为数字,然后再进行比较。也就是说,如果索引字段是整型类型,查询条件中的输入参数是字符串,会自动转换成整型,所以索引不会失效。而索引字段是字符串,而输入的是整型,由于是字符串转数字,而索引不是整型类型,所以索引失效了。

因此在使用sql语句时:数值类型禁止加引号,字符串类型必须加引号

联合索引非最左匹配

联合索引要能正确使用需要遵循最左匹配原则,也就是按照最左优先的方式进行索引的匹配,否则就会导致索引失效。

原因是,在联合索引的情况下,数据是按照索引第一列排序,第一列数据相同时才会按照第二列排序。

比如,如果创建了一个 (a, b, c) 联合索引,如果查询条件是以下这几种,就可以匹配上联合索引:

  • where a=1;
  • where a=1 and b=2 and c=3;
  • where a=1 and b=2;

因为有查询优化器,所以 a 字段在 where 子句的顺序并不重要。

但是,如果查询条件是以下这几种,因为不符合最左匹配原则,所以就无法匹配上联合索引,联合索引就会失效:

  • where b=2;
  • where c=3;
  • where b=2 and c=3;

对于where a = 1 and c = 0 这个语句,前面的a = 1是会走索引的,后面的c不走索引。

where条件的顺序影响索引使用吗

在MySQL中,WHERE条件的顺序确实可能影响索引的使用,尤其是在使用复合索引(多列索引)的情况下。但是,这种影响主要取决于MySQL查询优化器的工作方式,而不是直接由WHERE子句中条件的书写顺序决定的。

  • MySQL的查询优化器通常会尝试重新排列WHERE条件以最优化索引的使用。
  • 对于复合索引,索引的使用遵循"最左前缀"原则。
  • 虽然WHERE条件的顺序通常不会影响索引的使用,但将索引列的条件放在前面可能会使查询计划更易读和理解。

不应使用 or

在 WHERE 子句中or,如果在 OR 前的条件列是索引列,而在 OR 后的条件列不是索引列,那么索引会失效。

OR 的含义就是两个只要满足一个即可,因此只有一个条件列是索引列是没有意义的,只要有条件列不是索引列,就会进行全表扫描。

in

尽量使用IN代替OR。但是IN包含的值不应过多,应少于1000个。

因为 IN 通常是走索引的,当IN后面的数据在数据表中超过30%的匹配时是全表的扫描,不会走索引

其实就是 Mysql优化器会根据当前表的情况选择最优解。 Mysql优化器认为走全表扫描 比 走索引+回表快 那就不会走索引

范围查询阻断,后续字段不能走索引

索引

sql 复制代码
KEY `idx_shopid_created_status` (`a`, `b`, `c`)

SQL语句

sql 复制代码
select * from _order where shop_id = 1 and created_at > '2021-01-01 00:00:00' and order_status = 10

所谓的停止四配指的是:a 可以用上联合索引,但是 b和c 却不行。因为 b 需要先经过范国查询,此时经过饰选得到 c 的数据是无序的。比如a为1 和a为2数据中的 b和c是无序的,因此无法利用索引查询。

如果遇到如>=、<=、BETWEEN、前缀like(xx%)的范围查询,则在等值处不会停止匹配。因为这些查询包会一个等值判断,可以直接定位到某个数据,然后往后扫描可。

相关原理可以看这篇文章 唯一索引范围查询

覆盖索引优化

覆盖索引是指 SQL 中 查询的所有字段,在这个二级索引 B+Tree 的叶子节点上都能找得到那些字段,从二级索引中查询得到记录,而不需要通过聚簇索引查询获得,就可以避免回表的操作。

asc和desc混用

sql 复制代码
select * from _t where a=1 order by b desc, c asc

desc 和asc混用时会导致索引失效

避免更新索引列值

每当索引列的值发生变化时,数据库必须更新相应的索引结构,更新索引列值可能导致这些树结构的重平衡或重新构建,增加了额外的计算和I/O开销。

不等于、不包含不能用到索引的快速搜索

sql 复制代码
select * from _order where shop_id=1 and order_status not in (1,2)
select * from _order where shop_id=1 and order_status != 1

在索引上,避免使用NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等

not in一定不走索引吗?

答案是不一定。Mysql优化器会根据当前表的情况选择最优解。

主要在于如果 MySQL 认为 全表扫描 比 走索引+回表效率高, 那么他会选择全表扫描。

重要SQL必须被索引

update、delete的where条件列、order by、group by、distinct字段、多表join字段(on后面的字段)

例如:select id from table_a where name = 'seven' order by address ; 此时建立 name + address的联合索引比较好(此处name条件必须是 = ,如果是范围则无效);如果是order by主键,则只需要在name字段建立索引即可,因为name索引表中是包含主键的,也就是所谓了避免了回表操作。

避免使用子查询

通常情况下,一般建议使用连接查询代替子查询,原因如下:

连接查询(JOIN) 子查询
在执行连接查询时,数据库会根据查询优化器的策略将多个表的数据进行合并 ,然后进行过滤和选择 子查询要先执行内部查询,然后再使用其结果进行外部查询。嵌套子查询需要多次扫描数据,并且每次子查询都可能会触发独立的扫描操作,这增加了开销。
数据库优化器在处理连接查询时有更多的优化手段,如排序合并连接、哈希连接和嵌套循环连接等。优化器可以根据统计信息和查询结构进行调整,选择最优的执行计划。 子查询有时不能充分利用优化器的优化策略,特别是在嵌套子查询的情况下,优化器可能会生成次优的执行计划。
由于连接查询一次性扫描多个表并进行合并,所以可以充分利用数据缓存,减少I/O操作。 子查询可能会导致多次扫描相同的数据,特别是在嵌套子查询和相关子查询的情况下,子查询每次执行都可能触发新的数据扫描,增加了I/O开销。
可以通过JOIN条件有效地过滤数据,减少中间结果的大小。 可能会产生较大的中间结果集,需要多次筛选和处理,增加了内存和计算的开销。

order by的坑

已知存在 custom_id 和 order_date 的联合索引

在对数据进行,custom_id 排序的情况下,再对 order_date 进行排序。

vbnet 复制代码
SELECT customer_id from orders order by customer_id, order_date

耗时 0.669 秒

当调换排序顺序,就无法走索引了,此时针对custom_id的索引排序就是失效了。

sql 复制代码
SELECT customer_id from orders order by order_date,customer_id

耗时 1.645 秒

即order by也需满足联合索引的最左匹配原则

同时默认情况下,索引是升序的,如果需要降序排序,那么索引也会失效!!!

javascript 复制代码
SELECT` `customer_id ``from` `orders ``order` `by` `customer_id ``desc``, order_date

MySQL查询 limit 1000,10 和limit 10 速度一样快吗?

两种查询方式。对应 limit offset, sizelimit size 两种方式。

而其实 limit size ,相当于 limit 0, size。也就是从0开始取size条数据。

也就是说,两种方式的区别在于offset是否为0。

先来看下limit sql的内部执行逻辑。

MySQL内部分为server层存储引擎层。一般情况下存储引擎都用innodb。

server层有很多模块,其中需要关注的是执行器是用于跟存储引擎打交道的组件。

执行器可以通过调用存储引擎提供的接口,将一行行数据取出,当这些数据完全符合要求(比如满足其他where条件),则会放到结果集 中,最后返回给调用mysql的客户端

以主键索引的limit执行过程为例:

执行select * from xxx order by id limit 0, 10;,select后面带的是星号 ,也就是要求获得行数据的所有字段信息。

server层会调用innodb的接口,在innodb里的主键索引中获取到第0到10条完整行数据,依次返回给server层,并放到server层的结果集中,返回给客户端。

把offset搞大点,比如执行的是:select * from xxx order by id limit 500000, 10;

server层会调用innodb的接口,由于这次的offset=500000,会在innodb里的主键索引中获取到第0到(500000 + 10)条完整行数据返回给server层之后根据offset的值挨个抛弃,最后只留下最后面的size条,也就是10条数据,放到server层的结果集中,返回给客户端。

可以看出,当offset非0时,server层会从引擎层获取到很多无用的数据,而获取的这些无用数据都是要耗时的。

因此,mysql查询中 limit 1000,10 会比 limit 10 更慢。原因是 limit 1000,10 会取出1000+10条数据,并抛弃前1000条,这部分耗时更大。

深分页怎么优化?

还是以上面的SQL为空:select * from xxx order by id limit 500000, 10;

方法一:延迟关联(子查询)

从上面的分析可以看出,当offset非常大时,server层会从引擎层获取到很多无用的数据,而当select后面是*号时,就需要拷贝完整的行信息,拷贝完整数据 相比只拷贝行数据里的其中一两个列字段更耗费时间。

因为前面的offset条数据最后都是不要的,没有必要拷贝完整字段,所以可以将sql语句修改成:

sql 复制代码
select * from xxx  where id >=(select id from xxx order by id limit 500000, 1) order by id limit 10;

先执行子查询 select id from xxx by id limit 500000, 1, 这个操作,其实也是将在innodb中的主键索引中获取到500000+1条数据,然后server层会抛弃前500000条,只保留最后一条数据的id。

但不同的地方在于,在返回server层的过程中,只会拷贝数据行内的id这一列,而不会拷贝数据行的所有列,当数据量较大时,这部分的耗时还是比较明显的。

在拿到了上面的id之后,假设这个id正好等于500000,那sql就变成了

sql 复制代码
select * from xxx  where id >=500000 order by id limit 10;

这样innodb再走一次主键索引,通过B+树快速定位到id=500000的行数据,时间复杂度是lg(n),然后向后取10条数据。

方法二:根据id主键进行排序

记录上次查询的最大ID(或其他唯一标识符),并以此为起点进行下一次查询。这种方法需要有连续的、唯一的列(如自增ID)以用于分页。

sql 复制代码
select * from xxx where id > start_id order by id limit 10;

通过主键索引,每次定位到start_id的位置,然后往后遍历10个数据,这样不管数据多大,查询性能都较为稳定。

大表查询慢怎么优化?

某个表有近千万数据,查询比较慢,如何优化?

当MySQL单表记录数过大时,数据库的性能会明显下降,一些常见的优化措施如下:

  • 合理建立索引。在合适的字段上建立索引,例如在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描
  • 索引优化,SQL优化。索引要符合最左匹配原则等
  • 建立分区。对关键字段建立水平分区,比如时间字段,若查询条件往往通过时间范围来进行查询,能提升不少性能
  • 利用缓存。利用Redis等缓存热点数据,提高查询效率
  • 限定数据的范围。比如:用户在查询历史信息的时候,可以控制在一个月的时间范围内
  • 读写分离。经典的数据库拆分方案,主库负责写,从库负责读
  • 通过分库分表的方式进行优化,主要有垂直拆分和水平拆分
  • 数据异构到es
  • 冷热数据分离。几个月之前不常用的数据放到冷库中,最新的数据比较新的数据放到热库中
  • 升级数据库类型,换一种能兼容MySQL的数据库(OceanBase、TiDB等)

什么时候索引失效反而提升效率

  • 小表查询: 对于非常小的表,MySQL可能会选择全表扫描(忽略索引),因为全表扫描的开销可能比通过索引逐行查找还要低。在这种情况下,索引失效不会损害性能,反而简化了查询。
  • 读取大部分或所有行: 当一个查询返回表中很大比例的行(如 30% 或更多)时,使用索引查找可能会耗时更多,因为数据库必须跳回主数据页以读取完整记录。全表扫描可能更有效,因为它可以逐行顺序读取数据。
  • 低选择性索引: 如果索引列的选择性非常低,例如一个布尔型字段,许多行有相同的值,那么依赖索引可能会产生不必要的开销。全表扫描可以避免索引的搜索和回表开销。
  • 频繁更新的表: 对于包含大量更新操作的表,索引的维护成本可能相对较高。尤其是在频繁更新索引列时,通过避免使用或减少复杂的索引可以减轻写操作的负担。
  • 复杂查询的优化选择: 对于复杂的多表联接查询,优化器有时可以选择执行计划中不使用某个索引(或部分失效)以提高整体联接和计算效率。
  • 数据分布与优化器误判: 在某些特定情况下,如果索引导致MySQL错误地估计数据分布或行数,手动禁用索引或提示优化器使用不同策略可能提升性能。
相关推荐
代码的余温36 分钟前
SQL性能优化全攻略
数据库·mysql·性能优化
回家路上绕了弯3 小时前
MySQL 详细使用指南:从入门到精通
java·mysql
MaxHua3 小时前
SQL语法大全指南:从基础到进阶的关键字与用法解析
后端·mysql
回家路上绕了弯3 小时前
MySQL 索引详解:从原理到最佳实践
后端·mysql
手把手入门3 小时前
★CentOS:MySQL数据备份
数据库·mysql·adb
小卓笔记4 小时前
MySQL库表操作
mysql
iVictor4 小时前
MySQL 密码防暴力破解插件:Connection Control
mysql
rannn_1115 小时前
【MySQL学习|黑马笔记|Day7】触发器和锁(全局锁、表级锁、行级锁、)
笔记·后端·学习·mysql
杰克尼5 小时前
MYSQL-175. 组合两个表
数据库·mysql