MYSQL速通(3/5)
九、索引
1、索引结构
2、索引分类
3、索引语法
# 1、创建索引# 创建普通索引(INDEX)CREATE INDEX index_name ON table_name (column_name);# 创建唯一索引(UNIQUE)CREATE UNIQUE INDEX unique_index_name ON table_name (column_name);# 创建全文索引(FULLTEXT)CREATE FULLTEXT INDEX fulltext_index_name ON table_name (column_name);# 创建多列索引CREATE INDEX index_name ON table_name (column1_name, column2_name);# 2、查看索引SHOW INDEX FROM table_name;# 3、删除索引DROP INDEX index_name ON table_name;
给出演示案例,不需要修改字段我使用大写,需要修改字段(如表名、索引名)我使用小写
4、性能分析
①、慢查询
# 使用语句查看当前使用频繁的操作show global status like 'Com_______'; # 七个下划线# 一般来说都是查询操作比较频繁,故我们可使用慢查询进行选择优化,记录哪些操作时间超过慢查询设置的阈值,之后对其进行性能优化即可。# 默认情况下 MySQL 的慢查询是关闭状态(OFF)# 查看当前慢查询开关SHOW VARIABLES LIKE 'slow_query_log';# 方法(临时生效,重启容器后失效)1、# 打开慢查询日志SET GLOBAL slow_query_log = 'ON';# 设置慢查询阈值为 1 秒SET GLOBAL long_query_time = 1;# 方法(重启容器后永久生效)2、# 编辑 MySQL 的配置文件(存储在/etc/my.cnf)vi /etc/my.cnf# 加入以下内容:slow_query_log = 'ON';long_query_time = 1;# 重启容器docker restart <你的MySQL容器名>-- 指定慢日志文件路径(可选,MySQL 会自动放在数据目录下)-- 示例:Linux 默认路径 /var/lib/mysql/localhost-slow.log-- Windows 默认路径 MySQL\data\hostname-slow.log-- 若想自定义路径,需要 MySQL 对该路径有写权限-- SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';# 实时查看查看慢查询日志sudo tail -f /var/lib/mysql/localhost-slow.log-- 把未使用索引的查询也记录到慢日志(可选)SET GLOBAL log_queries_not_using_indexes = 'ON';# 给出一份测试文件-- 测试表CREATE TABLE IF NOT EXISTS test_slow (id BIGINT PRIMARY KEY AUTO_INCREMENT,val VARCHAR(200),rand INT);-- 插入 100 万条随机数据(约 10~20 秒完成)DELIMITER $$CREATE PROCEDURE insert_test_rows()BEGINDECLARE i INT DEFAULT 0;WHILE i < 1000000 DOINSERT INTO test_slow(val, rand)VALUES (MD5(RAND()), FLOOR(RAND()*1000000));SET i = i + 1;END WHILE;END$$DELIMITER ;CALL insert_test_rows();
②、profile
# show profile 可以帮助我们查看执行的 SQL 语句耗费时间的去向# 查看当前 MySQL 是否支持 profile 操作SELECT @@have_profiling;# 查看当前 profile 操作是否打开# MySQL 默认 profile 是0,即关闭状态SELECT @@profiling;# 打开 profile 为1,即开启状态SET profiling = 1;# profile详情,执行一系列的业务SQL的操作,然后通过如下指令查看指令的执行耗时: # 查看每一条SQL的耗时基本情况 SHOW PROFILES;# 查看指定 query_id 的SQL语句各个阶段的耗时情况 SHOW PROFILE FOR QUERY query_id;# 查看指定 query_id 的SQL语句CPU的使用情况 SHOW PROFILE CPU FOR QUERY query_id;
③、explain
# 通过 explain 可以查看其执行计划# 在查询语句前加上 explain 或 desc 即可EXPLAIN SELECT <要搜寻的字段名> FROM <表名>;# eg:EXPLAIN SELECT name,course_name FROM student,sc,courseWHERE student.id = sc.id AND course.course_id = sc.course_id;
EXPLAIN 执行计划各字段含义:
Ⅰ、id
select 查询的序列号,表示查询中执行 select 子句或操作表的顺序。
id 相同:执行顺序从上到下;
id 不同:值越大,越先执行。
Ⅱ、select_type
SELECT 的类型,常见取值:
SIMPLE:简单表(不使用连接或子查询);
PRIMARY:主查询(最外层查询);
UNION:UNION 中第二个或后续查询语句;
SUBQUERY:SELECT/WHERE 后包含的子查询等。
Ⅲ、type
连接类型,性能由好到差:
NULL > system > const > eq_ref > ref > range > index > all。
Ⅳ、possible_keys
可能应用在该表上的索引,一个或多个。
Ⅴ、key
实际使用的索引;为 NULL 表示未使用索引。
Ⅵ、key_len
索引使用的字节数(字段最大可能长度,非实际长度),越短越好。
Ⅶ、rows
MySQL 估算需要扫描的行数(InnoDB 中为估计值)。
Ⅷ、filtered
返回结果行数占读取行数的百分比,值越大越好。
实操举例:
5、索引使用
在之前给出的插入 100 万条随机数据的测试文件中,通过id查询,速度较快,因为我们在创建表时设置了id为主键,即id有索引帮助其查询,之后我们再通过val进行查询,速度明显变慢,这是因为我们的val没有创建索引。我们可以通过添加索引(耗时30s左右,这是在创建B+树结构)来提高我们通过val进行查询的效率。
以上说明:索引可有效提高搜索效率
①、最左前缀法则
最左前缀法则(Leftmost Prefix Rule)是联合索引(复合索引)在 MySQL 中生效的铁律:
只有在查询条件中从联合索引的最左侧列开始连续匹配,索引才会被使用。
注意只要含最左侧即可,不一定最左侧要在第一个
# 联合索引示例CREATE INDEX idx_a_b_c ON table(col_a, col_b, col_c);
查询条件 | 是否命中 idx_a_b_c | 理由 |
---|---|---|
WHERE col_a = 1 | ✅生效 | 从最左列开始 |
WHERE col_b = 2 AND col_a = 1 | ✅生效 | 连续匹配前2列 |
WHERE col_c = 3 AND col_a = 1 AND col_b = 2 | ✅生效 | 连续匹配全部3列 |
WHERE col_a = 1 AND col_c = 3 | 部分生效 | 仅 col_a 部分生效(col_c 跳过 col_b) |
查询条件 | 是否命中 | 理由 |
---|---|---|
WHERE col_b = 2 | ❌不生效 | 未从最左列 col_a 开始 |
WHERE col_b = 2 AND col_c = 3 | ❌不生效 | 缺少最左列 col_a |
WHERE col_c = 3 | ❌不生效 | 缺少最左列 col_a |
②、范围查询
范围查询(如 >、<、BETWEEN)会中断后续列的索引使用
WHERE col_a = 1 AND col_b > 10 AND col_c = 3;# col_a 和 col_b 部分生效,但 col_c 无法使用索引(因 col_b 是范围查询)# 建议在业务允许范围内使用>=/<=,不会导致索引失效
③、模糊查询
使用模糊查询(%、_)如果是在前面使用则会中断后续列的索引使用,只有后面使用不会中断。
# 前面使用(示例:以轩结尾)—————— 索引失效SELECT * FROM <表名> WHERE name like '%轩'# 后面使用(示例:以张开头)—————— 索引不失效SELECT * FROM <表名> WHERE name like '张%'# 前后面均使用(示例:名字中带王)—————— 索引失效SELECT * FROM <表名> WHERE name like '%王%'
④、or查询
当使用or时,如果两边都是有索引则最终使用索引进行查询,如果有一边没有索引,则走全表扫描
⑤、数据分布
当MySQL认为使用索引不如全表扫描效率高,则即使有索引也会使用全表扫描,这是由于复合要求的数据较多,即全表大部分数据都复合搜索要求,此时认为使用索引并不能有效提高效率。
⑥、SQL提示
SQL 提示”(SQL Hint) 指的是人为干预优化器的执行计划,强制 SQL 按照我们期望的方式走索引、选择连接方式或忽略缓存等
# 建议 SQL 优化器使用 PRIMARY INDEX(可拒绝)SELECT * FROM student USE INDEX (PRIMARY) WHERE id = '1' ;# 建议 SQL 优化器忽略 PRIMARY INDEX(可拒绝)SELECT * FROM student IGNORE INDEX (PRIMARY) WHERE id = '1' ;# 强制 SQL 优化器使用 PRIMARY INDEX(不可拒绝)SELECT * FROM student FORCE INDEX (PRIMARY) WHERE id = '1' ;
⑦、覆盖索引
覆盖索引 是一种 使用状态:只要索引里列够用,就不回表;它既可以是聚集索引(主键),也可以是二级索引。
即建议创建的联合索引(二级索引的一种)包含所要查找的数据,就不回表查询,否则会查到主键后回表。
如:
# 创建联合索引 idx_a_b_cCREATE INDEX idx_a_b_c ON table(col_a, col_b, col_c);SELECT a,b,c FROM <表名> WHERE <条件>;# 此时要查询的 a,b,c 包含在联合索引内,故不回表SELECT a,b,c,d FROM <表名> WHERE <条件>;# 此时要查询的 a,b,c 包含在联合索引内,但是 d 不在,故要通过联合索引找到主键,通过主键回表查询 d
⑧、前缀索引
前缀索引(Prefix Index)是 MySQL 中一种只对字符串列的前 N 个字符(或前 N 个字节)建立索引的技术,目的是:
大幅减小索引体积
降低磁盘/内存占用
提升写入与缓存效率
# 创建前缀索引-- 普通前缀索引ALTER TABLE tbl ADD INDEX idx_col_prefix (col(N));CREATE INDEX idx_col_prefix ON TABLE tbl(col(N));-- 唯一前缀索引ALTER TABLE tbl ADD UNIQUE uk_col_prefix (col(N));CREATE UNIQUE INDEX uk_col_prefix ON TABLE tbl(col(N));# 在“足够区分度”与“最小长度”之间平衡以选择合适的前缀长度 N# 计算整体区分度SELECT COUNT(DISTINCT email) / COUNT(*) AS full_selectivity FROM user;
⑨、单列索引和联合索引
单列索引:只对 一列 建索引。
联合索引:对 多列组合 建一个索引,内部按 最左前缀法则 工作。
单列查询够用就不使用联合索引,组合查询才使用联合索引;联合索引遵循最左前缀法则,跳列就失效
6、索引设计原则
针对于数据量较大,且查询比较频繁的表建立索引。
针对于常作为查询条件(where)、排序(order by)、分组(group by)操作的字段建立索引。
尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高查询效率。
要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NULL值时,它可以更好地确定哪个索引最有效地用于查询。
十、SQL优化
1、插入数据
# 优化1、批量插入INSERT INTO tb_test VALUES(1,'Tom'),(2,'Cat'),(3,'Jerry');# 优化2、手动插入事务START TRANSACTION;INSERT INTO tb_test VALUES(1,'Tom'),(2,'Cat'),(3,'Jdny');INSERT INTO tb_test VALUES(4,'Tom'),(5,'Cat'),(6,'Jerry');INSERT INTO tb_test VALUES(7,'Tom'),(8,'Cat'),(9,'Jerry');COMMIT;# 优化3、主键顺序插入# 主键乱序插入:8 1 9 21 88 2 4 15 89 5 7 3# 主键顺序插入:1 2 3 4 5 7 8 9 15 21 88 89,效率高于乱序插入# 优化4、load插入# 适用与大批量数据(几十万,几百万)# 客户端连接服务端时,加上参数 --local-infilemysql --local-infile -u root -p# 设置全局参数 local_infile 为 1(默认是0关闭状态),开启从本地加载文件导入数据的开关set global local_infile = 1;# 将数据上传到本地,如/root/sql1.log该路径下# 执行 load 指令将准备好的数据,加载到表结构中load data local infile '/root/sql1.log' into table `<你要插入的表>` fields terminated by ',' lines terminated by '\n';
2、主键优化
①、主键设计原则
唯一性:
主键必须唯一标识表中的每一行,不能有重复值。
非空性:
主键字段不能包含 NULL 值。
稳定性:
主键的值不应频繁更改。如果业务逻辑需要更改主键(如用户ID),考虑使用唯一标识符(如 UUID)。
简单性:
尽量选择简单、固定长度的字段作为主键,以减少存储空间和提高处理速度。
相关性:
主键应与表中其他字段具有一定的相关性,以优化查询性能。
单一性:
一个表只能有一个主键。
连续性:
尽量选择连续存储的字段作为主键,以提高数据页的缓存效率。
索引覆盖:
考虑查询中经常一起使用的字段组合可以作为联合索引,覆盖索引可以减少回表次数。
前缀索引:
对于长字符串类型,考虑使用前缀索引以减少索引大小。
避免使用自然主键:
如果没有明显的业务键,考虑使用自增序列或 UUID 作为主键。
②、主键顺序和乱序插入
Ⅰ、顺序插入
前一个页满了,开辟新的页顺序插入
Ⅱ、乱序插入
前一个页满了,开辟新的页,取出前一个页的一半加入到新开辟的页中,将要插入的字段也插入新的页,再调换列表顺序即可
3、order by 优化
①. Using filesort:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区 sort buffer 中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫 FileSort 排序。
②. Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为 using index,不需要额外排序,操作效率高。
-- 没有创建索引时,根据 age, phone 进行排序# Using filesortEXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age, phone;-- 创建索引CREATE INDEX idx_user_age_phone_aa ON tb_user(age, phone);-- 创建索引后,根据 age, phone 进行升序排序# Using indexEXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age, phone;-- 创建索引后,根据 age, phone 进行降序排序# Using indexEXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age DESC, phone DESC;-- 根据 age, phone 进行降序一个升序,一个降序# Using filesort + Using indexEXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age ASC, phone DESC;-- 创建索引CREATE INDEX idx_user_age_phone_ad ON tb_user(age ASC, phone DESC);-- 根据 age, phone 进行降序一个升序,一个降序# Using indexEXPLAIN SELECT id, age, phone FROM tb_user ORDER BY age ASC, phone DESC;
根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
尽量使用覆盖索引。
多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则(ASC/DESC)。
如果不可避免的出现 filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort_buffer_size(默认256k)。
4、group by优化
group by 的优化也是通过建立联合索引(覆盖索引)来进行提高查询效率
5、limit优化
limit分页查询时,数据量越大,想查询的数据越靠后效率越低
可使用覆盖索引+子查询的方法
如:查询2000000-2000010的数据
# 获取2000000-2000010的数据的主键id# 得到表bSELECT id FROM <表A名> limit 2000000,10;# 获取对应id的数据SELECT <表A名>.* FROM <表A名> WHERE <表A名>.id = b.id# 整合SELECT a.* FROM <表A名> a, (SELECT id FROM <表A名> limit 2000000,10) b WHERE a.id = b.id;
6、count优化
count 的几种用法
null则不加,非null,count+1
①、count(主键)
InnoDB 引擎会遍历整张表,把每一行的主键 id 值都取出来,返回给服务层。服务层拿到主键后,直接按行进行累加(主键不可能为 null)。
②、count(字段)
没有 not null 约束:InnoDB 引擎会遍历整张表,把每一行的字段值都取出来,返回给服务层,服务层判断是否为 null,不为 null,计数累加。
有 not null 约束:InnoDB 引擎会遍历整张表,把每一行的字段值都取出来,返回给服务层,直接按行进行累加。
③、count(1)
InnoDB 引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数学“1”进去,直接按行进行累加。
④、count(*)
InnoDB 引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。
效率:count(*)约等于count(1)>count(主键)>count(字段)
建议多使用count(*)
7、update优化
MySQL默认适用InnoDB引擎,InnoDB的行锁是针对索引的而不是记录,且该索引不可失效,否则行锁会升级为表锁,导致性能降低,操作失败。
即更新数据时必须要有未失效的索引,否则会导致行锁升级为表锁,进而操作性能降低。