《MySQL高级篇》六、索引的创建与设计原则(三)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 《MySQL高级篇》六、索引的创建与设计原则

3. 索引的设计原则

3.1 数据准备

第1步:创建数据库、创建表

CREATE DATABASE testdb1;
USE atguigudb1;
#1.创建学生表和课程表
CREATE TABLE `student_info` (
     `id` INT(11) NOT NULL AUTO_INCREMENT,
     `student_id` INT NOT NULL ,
     `name` VARCHAR(20) DEFAULT NULL,
     `course_id` INT NOT NULL ,
     `class_id` INT(11) DEFAULT NULL,
     `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
CREATE TABLE `course` (
    `id` INT(11) NOT NULL AUTO_INCREMENT,
    `course_id` INT NOT NULL ,
    `course_name` VARCHAR(40) DEFAULT NULL,
    PRIMARY KEY (`id`)
) ENGINE=INNODB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

第2步:创建模拟数据必需的存储函数

#函数1:创建随机产生字符串函数
DELIMITER //
CREATE FUNCTION rand_string(n INT)
    RETURNS VARCHAR(255) #该函数会返回一个字符串 
BEGIN
    DECLARE chars_str VARCHAR(100) DEFAULT
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ';
    DECLARE return_str VARCHAR(255) DEFAULT '';
        DECLARE i INT DEFAULT 0;
    WHILE i < n DO
       SET return_str =CONCAT(return_str,SUBSTRING(chars_str,FLOOR(1+RAND()*52),1));
       SET i = i + 1;
    END WHILE;
    RETURN return_str;
END //
DELIMITER ;
#函数2:创建随机数函数
DELIMITER //
CREATE FUNCTION rand_num (from_num INT ,to_num INT) RETURNS INT(11) BEGIN
DECLARE i INT DEFAULT 0;
SET i = FLOOR(from_num +RAND()*(to_num - from_num+1)) ;
RETURN i;
END //
DELIMITER ;

创建函数,假如报错:

This function has none of DETERMINISTIC......


由于开启过慢查询日志 bin-log, 我们就必须为我们的 function 指定一个参数。


主从复制,主机会将写操作记录在 bin-log 日志中。从机读取 bin-log 日志,执行语句来同步数据。如果使用函数来操作数据,会导致从机和主机操作时间不一致。所以,默认情况下,mysql 不开启创建函数设置。

查看 mysql 是否允许创建函数:

show variables like 'log_bin_trust_function_creators';

命令开启:允许创建函数设置:

set global log_bin_trust_function_creators=1;

mysqld 重启,上述参数又会消失。永久方法:

  • windows下:my.ini[mysqld]加上:
log_bin_trust_function_creators=1

linux下:/etc/my.cnf 下 my.cnf[mysqld] 加上:

log_bin_trust_function_creators=1

第3步:创建插入模拟数据的存储过程

 # 存储过程1:创建插入课程表存储过程
DELIMITER //
CREATE PROCEDURE insert_course( max_num INT ) BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i=i+1; #赋值
INSERT INTO course(course_id, course_name)VALUES(rand_num(10000,10100),rand_string(6));
UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务 
END //
DELIMITER ;
# 存储过程2:创建插入学生信息表存储过程
DELIMITER //
CREATE PROCEDURE insert_stu( max_num INT ) BEGIN
DECLARE i INT DEFAULT 0;
SET autocommit = 0; #设置手动提交事务
REPEAT #循环
SET i=i+1; #赋值
INSERT INTO student_info (course_id, class_id ,student_id ,NAME ) VALUES
(rand_num(10000,10100),rand_num(10000,10200),rand_num(1,200000),rand_string(6)); UNTIL i = max_num
END REPEAT;
COMMIT; #提交事务
END //
DELIMITER ;

第4步:调用存储过程

CALL insert_course(100); # 课程表中添加100条数据
CALL insert_stu(1000000);# 学生表中插入1000000条数据

3.2 哪些情况适合创建索引

1、字段的数值有唯一性的限制

4fec9837ea3ccb56e9824c08835dde98.png

2、频繁作为 WHERE 查询条件的字段

某个字段在 SELECT 语句的 WHERE 条件中经常被使用到,那么就需要给这个字段创建索引了。尤其是在数据量大的情况下,创建普通索引就可以大幅提升数据查询的效率。


比如 student_info 数据表(含 100 万条数据),假设我们想要查询 student_id=123110 的用户信息。

①查看student_info表中的索引

effa441582413c4a4212d3769a3793f8.png

可以看出,我们没有对student_id字段创建索引。

②进行如下查询,耗时220ms


a08776adc9dc792ac19cedc6bd2a1993.png


③添加索引

alter table student_info add index idx_sid(student_id);

④再查询。耗时0ms。性能提升杠杠的~


ece05e244fbbc9d3827c225fd2213095.png


3、经常 GROUP BY 和 ORDER BY 的列


索引其实就是让数据按照某种顺序进行存储或检索。当我们使用 GROUP BY 对数据进行分组查询,或者使用 ORDER BY 对数据进行排序的时候,如果 对分组或者排序的字段建立索引,本身索引的数据就已经排好序了,进行分组查询和排序操作性能不是很nice吗?另外,如果待排序的列有多个,那么可以在这些列上建立 组合索引 。


①下面在有student_id索引的情况下,查询:

mysql> SELECT student_id,COUNT(*) AS num
    -> FROM student_info
    -> GROUP BY student_id
    -> LIMIT 100;
+------------+-----+
| student_id | num |
+------------+-----+
|          1 |   5 |
.....此处省略n行......
|          3 |   4 |
|        101 |   7 |
+------------+-----+
100 rows in set (0.00 sec)

②删除索引

#删除idx_sid索引
DROP INDEX idx_sid ON student_info;

③再次查询 ,慢的像蜗牛~

mysql> SELECT student_id,COUNT(*) AS num
    -> FROM student_info
    -> GROUP BY student_id
    -> LIMIT 100;
+------------+-----+
| student_id | num |
+------------+-----+
|      95666 |   9 |
.....此处省略n行......
|     173440 |  14 |
|      67234 |   9 |
+------------+-----+
100 rows in set (0.78 sec)

同样,如果是ORDER BY,也需要对字段创建索引

④如果同时使用GROUP BYORDER BY,先看看不加索引的情况

mysql> SELECT student_id,COUNT(*) AS num FROM student_info
    -> GROUP BY student_id
    -> ORDER BY create_time DESC
    -> LIMIT 100;
ERROR 1055 (42000): Expression #1 of ORDER BY clause is not in GROUP BY clause and contains nonaggregated column 'atguigudb1.student_info.create_time' which is not functionally dependent on columns in GROUP BY clause; this is incompatible with sql_mode=only_full_group_by

⑤出现了一个异常信息,这是因为我们使用的sql_modeonly_full_group_by。修改下再来查询,时间代价是6.61s

mysql> SELECT @@sql_mode;
+-----------------------------------------------------------------------------------------------------------------------+
| @@sql_mode                                                                                                            |
+-----------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION |
+-----------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> SET @@sql_mode = 'STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';
Query OK, 0 rows affected (0.00 sec); # 去掉ONLY_FULL_GROUP_BY
mysql> SELECT student_id,COUNT(*) AS num FROM student_info
    -> GROUP BY student_id
    -> ORDER BY create_time DESC
    -> LIMIT 100;
+------------+-----+
| student_id | num |
+------------+-----+
|      21497 |   1 |
|      17311 |   1 |
.....此处省略n行......
|     183509 |   1 |
+------------+-----+
100 rows in set (6.61 sec)

⑥再看看两个字段分别建立单列索引的情况,耗时5.26 s,快了一点点

mysql> ALTER TABLE student_info ADD INDEX idx_sid(student_id);
Query OK, 0 rows affected (1.77 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> ALTER TABLE student_info ADD INDEX idx_cre_time(create_time);
Query OK, 0 rows affected (1.49 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> SELECT student_id,COUNT(*) AS num FROM student_info
    -> GROUP BY student_id
    -> ORDER BY create_time DESC
    -> LIMIT 100;
+------------+-----+
| student_id | num |
+------------+-----+
|      64044 |   1 |
.....此处省略n行......
|     101052 |   1 |
|     152620 |   1 |
+------------+-----+
100 rows in set (5.26 sec)

**注意:**建立多个单列索引,并不会都走,像刚才这个例子,只会走idx_sid索引

⑦分析下它的查询过程,原来我们只用了一个索引,由于我们是先GROUP BY student_id,后ORDER BY create_time,我们实际上只使用了索引idx_sid

mysql> EXPLAIN SELECT student_id,COUNT(*) AS num FROM student_info
    -> GROUP BY student_id
    -> ORDER BY create_time DESC
    -> LIMIT 100;
+----+-------------+--------------+------------+-------+---------------+---------+---------+------+--------+----------+---------------------------------+
| id | select_type | table        | partitions | type  | possible_keys | key     | key_len | ref  | rows   | filtered | Extra                           |
+----+-------------+--------------+------------+-------+---------------+---------+---------+------+--------+----------+---------------------------------+
|  1 | SIMPLE      | student_info | NULL       | index | idx_sid       | idx_sid | 4       | NULL | 997130 |   100.00 | Using temporary; Using filesort |
+----+-------------+--------------+------------+-------+---------------+---------+---------+------+--------+----------+---------------------------------+
1 row in set, 1 warning (0.00 sec)

⑧建立联合索引的情况,芜湖起飞,直接0.25s。此时我们用EXPLAIN查看命中的也是 联合索引

mysql>  ALTER TABLE student_info ADD INDEX idx_sid_cre_time(student_id,create_time DESC);
Query OK, 0 rows affected (2.09 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> SELECT student_id,COUNT(*) AS num FROM student_info
    -> GROUP BY student_id
    -> ORDER BY create_time DESC
    -> LIMIT 100;
+------------+-----+
| student_id | num |
+------------+-----+
|       1226 |   8 |
.....此处省略n行......
|       1400 |   2 |
+------------+-----+
100 rows in set (0.25 sec)
mysql> EXPLAIN SELECT student_id,COUNT(*) AS num FROM student_info
    -> GROUP BY student_id
    -> ORDER BY create_time DESC
    -> LIMIT 100;
+----+-------------+--------------+------------+-------+--------------------------+------------------+---------+------+--------+----------+----------------------------------------------+
| id | select_type | table        | partitions | type  | possible_keys            | key              | key_len | ref  | rows   | filtered | Extra                                        |
+----+-------------+--------------+------------+-------+--------------------------+------------------+---------+------+--------+----------+----------------------------------------------+
|  1 | SIMPLE      | student_info | NULL       | index | idx_sid,idx_sid_cre_time | idx_sid_cre_time | 10      | NULL | 997130 |   100.00 | Using index; Using temporary; Using filesort |
+----+-------------+--------------+------------+-------+--------------------------+------------------+---------+------+--------+----------+----------------------------------------------+
1 row in set, 1 warning (0.00 sec)

⑨再来测试,交换字段顺序建立联合索引idx_cre_time_sid,耗时5.24s。下面查询真正使用的索引keyidx_sid

mysql> ALTER TABLE student_info ADD INDEX idx_cre_time_sid(create_time DESC,student_id);
Query OK, 0 rows affected (2.10 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> DROP INDEX idx_sid_cre_time ON student_info; #删除联合索引idx_sid_cre_time
Query OK, 0 rows affected (0.01 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql> show INDEX from student_info; # 查看student_info中的索引
+--------------+------------+------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table        | Non_unique | Key_name         | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+--------------+------------+------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| student_info |          0 | PRIMARY          |            1 | id          | A         |      993366 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| student_info |          1 | idx_sid          |            1 | student_id  | A         |      199180 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
| student_info |          1 | idx_cre_time     |            1 | create_time | A         |          82 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| student_info |          1 | idx_cre_time_sid |            1 | create_time | D         |          77 |     NULL |   NULL | YES  | BTREE      |         |               | YES     | NULL       |
| student_info |          1 | idx_cre_time_sid |            2 | student_id  | A         |      967825 |     NULL |   NULL |      | BTREE      |         |               | YES     | NULL       |
+--------------+------------+------------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
5 rows in set (0.00 sec)
mysql> SELECT student_id,COUNT(*) AS num FROM student_info
    -> GROUP BY student_id
    -> ORDER BY create_time DESC
    -> LIMIT 100;
+------------+-----+
| student_id | num |
+------------+-----+
|      64044 |   1 |
.....此处省略n行......
|     101052 |   1 |
|     152620 |   1 |
+------------+-----+
100 rows in set (5.24 sec)
mysql> EXPLAIN SELECT student_id,COUNT(*) AS num FROM student_info
    -> GROUP BY student_id
    -> ORDER BY create_time DESC
    -> LIMIT 100;#起作用的是idx_sid
+----+-------------+--------------+------------+-------+--------------------------+---------+---------+------+--------+----------+---------------------------------+
| id | select_type | table        | partitions | type  | possible_keys            | key     | key_len | ref  | rows   | filtered | Extra                           |
+----+-------------+--------------+------------+-------+--------------------------+---------+---------+------+--------+----------+---------------------------------+
|  1 | SIMPLE      | student_info | NULL       | index | idx_sid,idx_cre_time_sid | idx_sid | 4       | NULL | 997130 |   100.00 | Using temporary; Using filesort |
+----+-------------+--------------+------------+-------+--------------------------+---------+---------+------+--------+----------+---------------------------------+
1 row in set, 1 warning (0.00 sec)

**总结:**如果我们仅仅使用GROUP BY 或者 ORDER BY,且后面只有一个字段,则单独建立索引;如果后面跟多个字段,则建立联合索引。如果既有GROUP BY 又有 ORDER BY,那就建立联合索引,且GROUP BY的字段写在前面,ORDER BY的字段写在后面。8.0后的版本也可以考虑使用降序索引


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
5天前
|
存储 自然语言处理 关系型数据库
MySQL高级篇——索引的创建与设计原则
索引的分类与使用、MySQL8.0索引新特性、适合创建索引的情况、不适合创建索引的情况
MySQL高级篇——索引的创建与设计原则
|
5天前
|
存储 SQL 关系型数据库
MySQL高级篇——索引失效的11种情况
索引优化思路、要尽量满足全值匹配、最佳左前缀法则、主键插入顺序尽量自增、计算、函数导致索引失效、类型转换(手动或自动)导致索引失效、范围条件右边的列索引失效、不等于符号导致索引失效、is not null、not like无法使用索引、左模糊查询导致索引失效、“OR”前后存在非索引列,导致索引失效、不同字符集导致索引失败,建议utf8mb4
MySQL高级篇——索引失效的11种情况
|
14天前
|
存储 关系型数据库 MySQL
MySQL基础:索引
MySQL中的索引是一种数据结构,能大幅提升数据库查询效率和减少I/O成本,类似于书的目录帮助快速定位内容。其优势包括提高检索效率和降低排序成本,但会占用空间并影响更新表的效率。鉴于查询远多于更新,索引仍被推荐使用。索引分为多种类型,如B+树和哈希索引,其中B+树因其较低的高度和稳定的查询开销成为常用选择。创建和删除索引需谨慎,以免影响性能。
40 4
MySQL基础:索引
|
5天前
|
存储 SQL 关系型数据库
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
MySQL调优主要分为三个步骤:监控报警、排查慢SQL、MySQL调优。 排查慢SQL:开启慢查询日志 、找出最慢的几条SQL、分析查询计划 。 MySQL调优: 基础优化:缓存优化、硬件优化、参数优化、定期清理垃圾、使用合适的存储引擎、读写分离、分库分表; 表设计优化:数据类型优化、冷热数据分表等。 索引优化:考虑索引失效的11个场景、遵循索引设计原则、连接查询优化、排序优化、深分页查询优化、覆盖索引、索引下推、用普通索引等。 SQL优化。
【MySQL调优】如何进行MySQL调优?从参数、数据建模、索引、SQL语句等方向,三万字详细解读MySQL的性能优化方案(2024版)
|
5天前
|
存储 缓存 关系型数据库
MySQL高级篇——存储引擎和索引
MyISAM:不支持外键和事务,表锁不适合高并发,只缓存索引,内存要求低,查询快MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM不支持事务、行级锁、外键,有一个毫无疑问的缺陷就是崩溃后无法安全恢复。5.5之前默认的存储引擎优势是访问的速度快,对事务完整性没有要求或者以SELECT、INSERT为主的应用针对数据统计有额外的常数存储。故而 count(*) 的查询效率很高表名.frm 存储表结构;表名.MYD 存储数据 (MYData);
MySQL高级篇——存储引擎和索引
|
5天前
|
存储 关系型数据库 MySQL
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
覆盖索引、前缀索引、索引下推、SQL优化、EXISTS 和 IN 的区分、建议COUNT(*)或COUNT(1)、建议SELECT(字段)而不是SELECT(*)、LIMIT 1 对优化的影响、多使用COMMIT、主键设计、自增主键的缺点、淘宝订单号的主键设计、MySQL 8.0改造UUID为有序
MySQL高级篇——覆盖索引、前缀索引、索引下推、SQL优化、主键设计
|
10天前
|
SQL 关系型数据库 MySQL
MySQL:表的设计原则和聚合函数
本文详细介绍了数据库表设计的原则与范式,包括从需求中找到实体及其属性,确定实体间关系,并使用SQL创建具体表。文章还深入探讨了一范式、二范式和三范式的要求及不满足这些范式时可能遇到的问题。此外,文中通过实例解释了一对一、一对多和多对多关系的表设计方法,并介绍了如何使用聚合函数如 COUNT()、SUM()、AVG()、MAX() 和 MIN() 进行数据统计和分析。最后,文章还展示了如何通过 SQL 语句实现数据的复制和插入操作。
32 7
MySQL:表的设计原则和聚合函数
|
2天前
|
NoSQL 关系型数据库 MySQL
微服务架构下的数据库选择:MySQL、PostgreSQL 还是 NoSQL?
在微服务架构中,数据库的选择至关重要。不同类型的数据库适用于不同的需求和场景。在本文章中,我们将深入探讨传统的关系型数据库(如 MySQL 和 PostgreSQL)与现代 NoSQL 数据库的优劣势,并分析在微服务架构下的最佳实践。
|
4天前
|
存储 SQL 关系型数据库
使用MySQL Workbench进行数据库备份
【9月更文挑战第13天】以下是使用MySQL Workbench进行数据库备份的步骤:启动软件后,通过“Database”菜单中的“管理连接”选项配置并选择要备份的数据库。随后,选择“数据导出”,确认导出的数据库及格式(推荐SQL格式),设置存储路径,点击“开始导出”。完成后,可在指定路径找到备份文件,建议定期备份并存储于安全位置。
59 11
|
23天前
|
弹性计算 关系型数据库 数据库
手把手带你从自建 MySQL 迁移到云数据库,一步就能脱胎换骨
阿里云瑶池数据库来开课啦!自建数据库迁移至云数据库 RDS原来只要一步操作就能搞定!点击阅读原文完成实验就可获得一本日历哦~

热门文章

最新文章