1、sql没加索引
很多时候,我们的慢查询,都是因为没有加索引,如果没有加索引的话,会导致全表扫描,因此在where的条件列建立索引,尽量避免全表扫描
反例:
正例:
2、sql索引不生效
有时候明明添加了索引却不生效,在以下场景不生效,如下图
2.1 隐式的类型转换,索引失效
CREATE TABLE USER (
id INT ( 11 ) NOT NULL AUTO_INCREMENT,
userId VARCHAR ( 32 ) NOT NULL,
age VARCHAR ( 16 ) NOT NULL,
NAME VARCHAR ( 255 ) NOT NULL,
PRIMARY KEY ( id ),
KEY idx_userid ( userId ) USING BTREE
) ENGINE = INNODB DEFAULT CHARSET = utf8
userId是字符串类型,是B+树普通索引, 而这是传一个数字类型,就会导致索引失效
为什么第一条语句未加单引号就不走索引
了呢?
这是因为不加单引号时,是字符串跟数字的比较,它们类型不匹配,MySQL会做隐式的类型转换
,把它们转换为浮点数再做比较。隐式的类型转换,索引会失效。
2.2 查询条件包含or,可能导致索引失效
对于or + 没有索引的age这种情况,假设它走了userId的索引,但是走到age查询条件时,还得全表扫描,也就是需要三步过程:全表扫描+索引扫描+合并,如果他一开始就走全表扫描,直接一遍扫描就完事
,Mysql优化器出于效率与成本考虑,遇到or条件,让索引失效,但是倮or条件列都加了索引,索引可能走可能不走,可以考虑拆成两条sql
2.3 like通配符可能导致索引失效
这里指like查询以%开头,才会导致索引失效
将%放后面,还是正常走索引
2.3.1 避免索引失效
1.使用覆盖索引(查询方式
)
主键索引:叶子节点存储数据、辅助索引(非主键索引):叶子节点存储主键=回表
2.将%放后面
2.4 查询条件不满足联合索引的最左匹配原则
mysql建立联合索引时,会遵循最左匹配规则,即最左优先,如 建立一个(userId,name)联合索引,相当于建立(userId)、(userId ,name)三个索引
因为name列不满足mysql最左原则匹配(不是联合索引的第一个列),导致索引失效
2.5在索引上使用mysql内置函数
DROP TABLE USER
CREATE TABLE `user` (
`id` INT ( 11 ) NOT NULL AUTO_INCREMENT,
`userId` VARCHAR ( 32 ) NOT NULL,
`login_time` datetime NOT NULL,
PRIMARY KEY ( `id` ),
KEY `idx_userId` ( `userId` ) USING BTREE,
KEY `idx_login_time` ( `login_Time` ) USING BTREE
) ENGINE = INNODB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8
虽然login_time加了索引,但是使用了mysql内置函数:DATE_ADD()
,直接凉凉,如图
可以将内置函数的逻辑转移到右边索引就生效了,如下:
2.6 对索引进行列运算(如,+、-、*、/)
,索引不生效
虽然age加索引了,但是对他进行运算,导致索引失效
, 所以不可以对索引列进行运算,可以在代码处理好,再传参进去
2.7 索引字段上使用(!= 或者 < >),索引可能失效
避免拿索引列作为条件查询使用 != 、<> 、not in
,这和mysql优化器有关,如果优化器觉得即使走了索引,还是需要扫描很多很多行的话,不如直接不走索引
2.8 索引字段上使用is null, is not null
,索引可能失效
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`card` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`) USING BTREE,
KEY `idx_card` (`card`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
单个name和card字段加上索引,加上is not null 或is null
其实是会走索引的,但是用or连接索引就会失效
explain select * from user where name is not null
explain select * from user where card is not null
explain select * from user where name is null
explain select * from user where card is null
explain select * from user where name is not null or card is not null
explain select * from user where name is null or card is null
很多时候,也是因为数据量问题,导致mysql优化器放弃走索引,同时我们用explain分析sql时,如果type=range时这个可能因为数据量问题导致索引无效
2.9 左右连接,关联的字段编码格式不一样
DROP table user
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 DEFAULT NULL,
`age` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
CREATE TABLE `user_job` (
`id` int(11) NOT NULL,
`userId` int(11) NOT NULL,
`job` varchar(255) DEFAULT NULL,
`name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_name` (`name`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8
user
表的name
字段编码是utf8mb4
,而user_job
表的name
字段编码为utf8
。
执行左外连接查询,user_job
表还是走全表扫描,如下:
如果把它们的name
字段改为编码一致,相同的SQL,还是会走索引
所以大家在做表关联时,注意一下关联字段的编码问题哈。
2.10 优化器选错了索引
MySQL 中一张表是可以支持多个索引的。你写SQL
语句的时候,没有主动指定使用哪个索引的话,用哪个索引是由MySQL
来确定的。
我们日常开发中,不断地删除历史数据和新增数据的场景,有可能会导致MySQL选错索引。那么有哪些解决方案呢?
- 使用
force index
强行选择某个索引 - 修改你的SQl,引导它使用我们期望的索引
- 优化你的业务逻辑
- 优化你的索引,新建一个更合适的索引,或者删除误用的索引。
3、limit深分页
limit深分页问题,会导致慢查询,应该大家都司空见惯
了吧。
3.1 limit深分页为什么会变慢
limit深分页为什么会导致SQL变慢
呢?假设我们有表结构如下:
CREATE TABLE account (
id int(11) NOT NULL AUTO_INCREMENT COMMENT '主键Id',
name varchar(255) DEFAULT NULL COMMENT '账户名',
balance int(11) DEFAULT NULL COMMENT '余额',
create_time datetime NOT NULL COMMENT '创建时间',
update_time datetime NOT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (id),
KEY idx_name (name),
KEY idx_create_time (create_time) //索引
) ENGINE=InnoDB AUTO_INCREMENT=1570068 DEFAULT CHARSET=utf8 ROW_FORMAT=REDUNDANT COMMENT='账户表'
你知道以下SQL
,执行过程是怎样的嘛?
select id,name,balance from account where create_time > '2020-09-19' limit 100000,10
这个SQL的执行流程:
1.通过普通二级索引树idx_create_time
,过滤create_time
条件,找到满足条件的主键id
。
2.通过主键id
,回到id主键
索引树,找到满足记录的行,然后取出需要展示的列(回表过程)
3.扫描满足条件的100010
行,然后扔掉前100000
行,返回。
limit
深分页,导致SQL
变慢原因有两个:
limit
语句会先扫描offset+n
行,然后再丢弃掉前offset
行,返回后n
行数据。也就是说limit 100000,10
,就会扫描100010
行,而limit 0,10,
只扫描10
行。limit 100000,10
扫描更多的行数,也意味着回表更多的次数。
评论区