MySQL高级篇——索引的创建与设计原则

Posted 张起灵-小哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL高级篇——索引的创建与设计原则相关的知识,希望对你有一定的参考价值。

文章目录:

1.索引的分类

2.创建索引的三种方式

2.1 方式一:CREATE TABLE

2.1.1 小例子

2.1.2 普通索引

2.1.3 唯一性索引

2.1.4 主键索引

2.1.5 单列索引

2.1.6 联合索引

2.1.7 全文索引

2.2 方式二:ALTER TABLE ... ADD INDEX ...

2.3 方式三:CREATE INDEX ... ON ...

3.删除索引的两种方式

3.1 使用ALTER TABLE删除索引

3.2 使用DROP INDEX语句删除索引

4.索引的设计原则(未完待续,明天补全......)

4.1 哪些情况适合创建索引?

4.1.1 字段的数值有唯一性的限制

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

4.1.3 经常 GROUP BY 和 ORDER BY 的列

4.1.4 UPDATE、DELETE 的 WHERE 条件列

4.1.5 DISTINCT 字段需要创建索引

4.1.6 多表 JOIN 连接操作时,创建索引注意事项

4.1.7 使用列的类型小的创建索引

4.1.8 使用字符串前缀创建索引

4.1.9 区分度高(散列性高)的列适合作为索引

4.1.10 使用最频繁的列放到联合索引的左侧

4.1.11 在多个字段都要创建索引的情况下,联合索引优于单值索引

4.2 限制索引的数目

4.3 哪些情况不适合创建索引?

4.3.1 在where中使用不到的字段,不要设置索引

4.3.2 数据量小的表最好不要使用索引

4.3.3 有大量重复数据的列上不要建立索引

4.3.4 避免对经常更新的表创建过多的索引

4.3.5 不建议用无序的值作为索引

4.3.6 删除不再使用或者很少使用的索引

4.3.7 不要定义冗余或重复的索引


1.索引的分类

mysql 的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引和空间索引等。 功能逻辑 上说,索引主要有 4 种,分别是普通索引、唯一索引、主键索引、全文索引。 按照 物理实现方式 ,索引可以分为 2 种:聚簇索引和非聚簇索引。 按照 作用字段个数 进行划分,分成单列索引和联合索引。


2.创建索引的三种方式

2.1 方式一:CREATE TABLE

2.1.1 小例子

CREATE DATABASE dbtest2;

USE dbtest2;

CREATE TABLE dept (
		dept_id INT PRIMARY KEY AUTO_INCREMENT,
		dept_name VARCHAR(20)
);

SHOW INDEX FROM dept;

CREATE TABLE emp (
		emp_id INT PRIMARY KEY AUTO_INCREMENT,
		emp_name VARCHAR(20) UNIQUE,
		dept_id INT,
		CONSTRAINT fk_emp_dept_id FOREIGN KEY(dept_id) REFERENCES dept(dept_id)
);

SHOW INDEX FROM emp;

2.1.2 普通索引

CREATE TABLE book (
	book_id INT,
	book_name VARCHAR(100),
	`authors` VARCHAR(100),
	info VARCHAR(100),
	`comment` VARCHAR(100),
	year_publication YEAR,
	INDEX idx_bname(book_name)
);

SHOW INDEX FROM book;

如果我们写一个简单的sql语句,在where后面用 book_name 来筛选,可以通过 explain 性能分析工具来看看是什么样的?

EXPLAIN 
SELECT *
FROM book
WHERE book_name = 'mysql';

2.1.3 唯一性索引

CREATE TABLE book1 (
		book_id INT,
		book_name VARCHAR(100),
		`authors` VARCHAR(100),
		info VARCHAR(100),
		`comment` VARCHAR(100),
		year_publication YEAR,
		UNIQUE INDEX uk_idx_cmt(`comment`)
);

SHOW INDEX FROM book1;

接下来,依次向表中插入三条记录,在插入第二条记录的时候,就会报错。而第三条记录是正常执行的。

INSERT INTO book1(book_id, book_name, `comment`)
VALUES(1, 'MySQL高级', '适合有数据库开发经验的人员学习');

INSERT INTO book1(book_id, book_name, `comment`)
VALUES(1, 'MySQL高级', '适合有数据库开发经验的人员学习');

INSERT INTO book1(book_id, book_name, `comment`)
VALUES(1, 'MySQL高级', NULL);

最终,在book1这张表中,将存在两条记录。

2.1.4 主键索引

CREATE TABLE book2 (
		book_id INT PRIMARY KEY,
		book_name VARCHAR(100),
		`authors` VARCHAR(100),
		info VARCHAR(100),
		`comment` VARCHAR(100),
		year_publication YEAR
);

SHOW INDEX FROM book2;

2.1.5 单列索引

其实我们上面创建的那些都是单列索引。

CREATE TABLE book3 (
		book_id INT ,
		book_name VARCHAR(100),
		AUTHORS VARCHAR(100),
		info VARCHAR(100) ,
		COMMENT VARCHAR(100),
		year_publication YEAR,
		UNIQUE INDEX idx_bname(book_name)
);

SHOW INDEX FROM book3;

2.1.6 联合索引

CREATE TABLE book4 (
		book_id INT ,
		book_name VARCHAR(100),
		AUTHORS VARCHAR(100),
		info VARCHAR(100) ,
		COMMENT VARCHAR(100),
		year_publication YEAR,
		INDEX mul_bid_bname_info(book_id, book_name, info)
);

SHOW INDEX FROM book4;

下面我们通过两条sql来分析一下,联合索引在查找过程中是怎么走的?

EXPLAIN 
SELECT *
FROM book4
WHERE book_id = 1001 AND book_name = 'mysql';

解释:因为我们建的联合索引是 book_id, book_name, info 这样的顺序,所以在构建B+树的时候,就是先按照 book_id 来进行排序,当book_id相同时,再按照 book_name来排序,当book_name一样时,最后按照info来排序。(在B+树中,book_name实际上是位于book_id下方的,查找一定是先经过book_id、后经过book_name的)

所以上面这条sql,会先走book_id,再走book_name的。

EXPLAIN 
SELECT *
FROM book4
WHERE book_name = 'mysql';

经过上面的分析,那么这条sql为什么就没有走联合索引呢?(这book_name不是存在于联合索引中吗?),这里其实还是构建B+树的顺序问题,你要想在where筛选中走book_name索引,你就必须先要走book_id索引的,因为book_name在联合索引中位于book_id之后,所以在B+树中book_name就处于book_id下方,你连B+树的第二层(假设)都还没走到,又何谈到达B+树的第三层呢?    所以这条sql是不会走索引的。

2.1.7 全文索引

CREATE TABLE test4 (
		id INT NOT NULL,
		NAME CHAR(30) NOT NULL,
		age INT NOT NULL,
		info VARCHAR(255),
		FULLTEXT INDEX futxt_idx_info(info(50))
);

SHOW INDEX FROM test4;

2.2 方式二:ALTER TABLE ... ADD INDEX ...

DROP TABLE IF EXISTS book5;
CREATE TABLE book5 (
		book_id INT ,
		book_name VARCHAR(100),
		`authors` VARCHAR(100),
		info VARCHAR(100) ,
		`comment` VARCHAR(100),
		year_publication YEAR
);

ALTER TABLE book5 ADD INDEX idx_cmt(`comment`);
ALTER TABLE book5 ADD UNIQUE INDEX uk_idx_bname(book_name);
ALTER TABLE book5 ADD INDEX mul_bid_bname_info(book_id, book_name, info);

SHOW INDEX FROM book5;

2.3 方式三:CREATE INDEX ... ON ...

DROP TABLE IF EXISTS book6;
CREATE TABLE book6 (
		book_id INT ,
		book_name VARCHAR(100),
		`authors` VARCHAR(100),
		info VARCHAR(100) ,
		`comment` VARCHAR(100),
		year_publication YEAR
);

CREATE INDEX idx_cmt ON book6(`comment`);
CREATE UNIQUE INDEX uk_idx_bname ON book6(book_name);
CREATE INDEX mul_bid_bname_info ON book6(book_id, book_name, info);

SHOW INDEX FROM book6;


3.删除索引的两种方式

3.1 使用ALTER TABLE删除索引

3.2 使用DROP INDEX语句删除索引

提示 删除表中的列时,如果要删除的列为索引的组成部分,则该列也会从索引中删除。如果组成索引的所有列都被删除,则整个索引将被删除。
ALTER TABLE book6 DROP INDEX idx_cmt;

DROP INDEX uk_idx_bname ON book6;

ALTER TABLE book6 DROP COLUMN book_name;
ALTER TABLE book6 DROP COLUMN book_id;
ALTER TABLE book6 DROP COLUMN info;

SHOW INDEX FROM book6;


4.索引的设计原则(未完待续,明天补全......)

4.1 哪些情况适合创建索引?

我们先准备一些数据,用作下面创建索引及测试过程。

#1. 数据的准备
CREATE DATABASE atguigudb1;

USE atguigudb1;

#1.创建学生表和课程表
CREATE TABLE `student_info` (
 `id` INT(11) 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;


#函数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 ;

SELECT @@log_bin_trust_function_creators;

SET GLOBAL log_bin_trust_function_creators = 1;


#函数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 ;

# 存储过程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 ;

#调用存储过程:
CALL insert_course(100);

SELECT COUNT(*) FROM course;

CALL insert_stu(1000000);

SELECT COUNT(*) FROM student_info;

4.1.1 字段的数值有唯一性的限制

业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。(来源: Alibaba 说明:不要以为唯一索引影响了 insert 速度,这个速度损耗可以忽略,但提高查找速度是明显的。

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

某个字段在 SELECT 语句的 WHERE 条件中经常被使用到,那么就需要给这个字段创建索引了。尤其是在 数据量大的情况下,创建普通索引就可以大幅提升数据查询的效率。 比如 student_info 数据表(含 100 万条数据),假设我们想要查询 student_id=123110 的用户信息。 下面的sql代码首先是查看student_info表中都有哪些索引?然后在不使用索引的情况下查询效率如何,对where查询条件的字段student_id添加索引之后查询效率又如何?  最后是执行耗时。
SHOW INDEX FROM student_info;

SELECT course_id, class_id, NAME, create_time, student_id 
FROM student_info
WHERE student_id = 123110; #240ms

ALTER TABLE student_info ADD INDEX idx_sid(student_id);

SELECT course_id, class_id, NAME, create_time, student_id 
FROM student_info
WHERE student_id = 123110; #17ms

4.1.3 经常 GROUP BY ORDER BY 的列

索引就是让数据按照某种顺序进行存储或检索,因此当我们使用 GROUP BY 对数据进行分组查询,或者使用 ORDER BY 对数据进行排序的时候,就需要 对分组或者排序的字段进行索引 。如果待排序的列有多 个,那么可以在这些列上建立 组合索引 这里先将GROUP BY中用到的student_id字段相关的索引删除,看下查询效率。之后再对这个字段加索引,再看查询效率。
ALTER TABLE student_info DROP INDEX idx_sid;
SHOW INDEX FROM student_info;

SELECT student_id, COUNT(*) AS num 
FROM student_info 
GROUP BY student_id LIMIT 100; #514ms

ALTER TABLE student_info ADD INDEX idx_sid(student_id);

SELECT student_id, COUNT(*) AS num 
FROM student_info 
GROUP BY student_id LIMIT 100; #17ms

下面是针对查询中同时包含GROUP BY 和ORDER BY的sql,在为student_id和create_time分别创建索引的情况下,它的执行时间花了2.845s,挺长的,我们可以使用explain查看一下它是只走了 student_id 索引?还是只走了create_time索引?还是两个都走了?

ALTER TABLE student_info ADD INDEX idx_sid(student_id);

ALTER TABLE student_info ADD INDEX idx_cre_time(create_time);

SHOW INDEX FROM student_info;

#修改sql_mode

SELECT @@sql_mode;

SET @@sql_mode ='STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';


SELECT student_id, COUNT(*) AS num FROM student_info 
GROUP BY student_id 
ORDER BY create_time DESC 
LIMIT 100; #2845ms

EXPLAIN SELECT student_id, COUNT(*) AS num FROM student_info 
GROUP BY student_id 
ORDER BY create_time DESC 
LIMIT 100;

上面的explain执行结果告诉我们,它只走了student_id对应的单列索引,原因就是sql语句的执行流程中:是 FROM 👉 WHERE 👉 GROUP BY 👉 HAVING 👉  SELECT 👉 ORDER BY 👉 LIMIT,而GROUP BY是先于ORDER BY执行的,所以它这里先考虑使用GROUP BY相关字段的索引,后续就不再采用其他的了。

那么针对这样的问题,我们可以考虑将GROUP BY和ORDER BY的相关字段建一个联合索引。

ALTER TABLE student_info ADD INDEX idx_sid_cre_time(student_id,create_time DESC);

SELECT student_id, COUNT(*) AS num FROM student_info 
GROUP BY student_id 
ORDER BY create_time DESC 
LIMIT 100; #229ms

EXPLAIN SELECT student_id, COUNT(*) AS num FROM student_info 
GROUP BY student_id 
ORDER BY create_time DESC 
LIMIT 100;

4.1.4 UPDATEDELETE WHERE 条件列

对数据按照某个条件进行查询后再进行 UPDATE DELETE 的操作,如果对 WHERE 字段创建了索引,就能大幅提升效率。原理是因为我们需要先根据 WHERE 条件列检索出来这条记录,然后再对它进行更新或 删除。 如果进行更新的时候,更新的字段是非索引字段,提升的效率会更明显,这是因为非索引字段更 新不需要对索引进行维护。
SHOW INDEX FROM student_info;

UPDATE student_info 
SET student_id = 10002 
WHERE NAME = '462eed7ac6e791292a79'; #466ms

ALTER TABLE student_info ADD INDEX idx_name(NAME);

UPDATE student_info 
SET student_id = 10003
WHERE NAME = '462eed7ac6e791292a79'; #16ms

4.1.5 DISTINCT 字段需要创建索引

有时候我们需要对某个字段进行去重,使用 DISTINCT ,那么对这个字段创建索引,也会提升查询效率。 比如,我们想要查询课程表中不同的 student_id 都有哪些,如果我们没有对 student_id 创建索引,执行:
DROP INDEX idx_sid ON student_info;
SHOW INDEX FROM student_info;

 

先确保表中没有与DISTINCT去重相关字段的索引。然后我们对student_id做个去重,看看执行效率。 然后给这个字段加上索引,再看执行效率。

SELECT DISTINCT(student_id)
FROM student_info; #531ms

ALTER TABLE student_info ADD INDEX idx_sid(student_id);
SHOW INDEX FROM student_info;

SELECT DISTINCT(student_id)
FROM student_info; #369ms

4.1.6 多表 JOIN 连接操作时,创建索引注意事项

首先, 连接表的数量尽量不要超过 3 ,因为每增加一张表就相当于增加了一次嵌套的循环,数量级增长会非常快,严重影响查询的效率。 其次, WHERE 条件创建索引 ,因为 WHERE 才是对数据条件的过滤。如果在数据量非常大的情况下, 没有 WHERE 条件过滤是非常可怕的。 最后, 对用于连接的字段创建索引 ,并且该字段在多张表中的 类型必须一致 。比如 course_id student_info 表和 course 表中都为 int(11) 类型,而不能一个为 int 另一个为 varchar 类型。
SELECT s.course_id, NAME, s.student_id, c.course_name 
FROM student_info s JOIN course c
ON s.course_id = c.course_id
WHERE NAME = '462eed7ac6e791292a79'; #16ms

DROP INDEX idx_name ON student_info;

SELECT s.course_id, NAME, s.student_id, c.course_name 
FROM student_info s JOIN course c
ON s.course_id = c.course_id
WHERE NAME = '462eed7ac6e791292a79'; #189ms

4.1.7 使用列的类型小的创建索引

4.1.8 使用字符串前缀创建索引

4.1.9 区分度高(散列性高)的列适合作为索引

4.1.10 使用最频繁的列放到联合索引的左侧

这样也可以较少的建立一些索引。同时,由于 " 最左前缀原则 " ,可以增加联合索引的使用率。

4.1.11 在多个字段都要创建索引的情况下,联合索引优于单值索引

4.2 限制索引的数目

4.3 哪些情况不适合创建索引?

4.3.1 where中使用不到的字段,不要设置索引

4.3.2 数据量小的表最好不要使用索引

结论:在数据表中的数据行数比较少的情况下,比如不到 1000 行,是不需要创建索引的。

4.3.3 有大量重复数据的列上不要建立索引

结论:当数据重复度大,比如`高于 10% `的时候,也不需要对这个字段使用索引。

4.3.4 避免对经常更新的表创建过多的索引

4.3.5 不建议用无序的值作为索引

例如身份证、 UUID( 在索引比较时需要转为 ASCII ,并且插入时可能造成页分裂 ) MD5 HASH 、无序长字 符串等。

4.3.6 删除不再使用或者很少使用的索引

4.3.7 不要定义冗余或重复的索引

CREATE TABLE person_info( 
    id INT UNSIGNED NOT NULL AUTO_INCREMENT, 
    name VARCHAR(100) NOT NULL, 
    birthday DATE NOT NULL, 
    phone_number CHAR(11) NOT NULL, 
    country varchar(100) NOT NULL, 
    PRIMARY KEY (id), 
    KEY idx_name_birthday_phone_number (name(10), birthday, phone_number), 
    KEY idx_name (name(10)) 
);
我们知道,通过 idx_name_birthday_phone_number 索引就可以对 name 列进行快速搜索,再创建一个专门针对 name 列的索引就算是一个 冗余索引 ,维护这个索引只会增加维护的成本,并不会对搜索有 什么好处。
CREATE TABLE repeat_index_demo ( 
    col1 INT PRIMARY KEY, 
    col2 INT, 
    UNIQUE uk_idx_c1 (col1), 
    INDEX idx_c1 (col1) 
);
我们看到, col1 既是主键、又给它定义为一个唯一索引,还给它定义了一个普通索引,可是主键本身就会生成聚簇索引,所以定义的唯一索引和普通索引是重复的,这种情况要避免。

以上是关于MySQL高级篇——索引的创建与设计原则的主要内容,如果未能解决你的问题,请参考以下文章

「MySQL高级篇」MySQL索引原理,设计原则

「MySQL高级实战篇」10分钟探索MySQL索引原理,设计原则

Day515.索引的创建与设计原则 -mysql

MySQL 高级--优化 —— 创建索引原则使用场景和索引失效的情况

mysql创建索引的原则

_索引的创建与设计原则