第08章_索引的创建与设计原则
1.1索引的分类
MySQL的索引包括普通索引、唯一性索引、全文索引、单列索引、多列索引引和空间索引等。
- ·从功能逻辑上说,索引主要有4种,分别是普通索引、唯一索引、主键索引、全文索引。
- ·按照物理实现方式,索引可以分为2种:聚簇索引和非聚簇索引。
- ·按照作用字段个数进行划分,分成单列索引和联合索引。
1.普通索引
在创建普通索引时,不附加任何限制条件,只是用于提高查询效率。这类索引可以创建在任何数据类型中,其值是否唯一和非空,要由字段本身的完整性约束条件决定。建立索引以后,可以通过索引进行查询。例如,在表student的字段name上建立一个普通索引,查询记录时就可以根据该索引进行查询。
2.唯一性素引
使用UNIQUE参数可以设置索引为唯一性索引1,在创建唯一性索引时,限制该索引的值必须是唯一的,但允许有空值。在一张数据表里可以有多个唯一索引。
例如,在表student的字段email中创建唯一性索引l,那么字段email的值就必须是唯一的。通过唯一性索引,可以更快速地确定某条记录。
3.主键索引
主键索引就是一种特殊的唯一性索引,在唯一索引的基础上增加了不为空的约束,也就是NOTNULL+UNIQUE,1张表里最多只有一个主键索引。Why?这是由主键索引的物理实现方式决定的,因为数据存储在文件中只能按照一种顺序进行存储。
4.单列索引
在表中的单个字段上创建索引。单列索引只根据该字段进行索引。单列索引可以是普通索引,也可以是唯一性索引,还可以是全文索引。只要保证该索引只对应一个字段即可。一个表可以有多个单列索引。
5.多列(组合、联合)索引
多列索引是在表的多个字段组合上创建一个索引。该索引指向创建时对应的多个字段,可以通过这几个字段进行查询,但是只有查询条件中使用了这些字段中的第一个字段时才会被使用。例如,在表中的字段id、name和gender上建立一个多列索引lidx_Id_name_gender,只有在查询条件中使用了字段id时该索引才会被使用。使用组合索引时遵循最左前缀集合。
6.全文索引
全文索引(也称全文检索)是目前搜索引擎使用的一种关键技术。它能够利用【分词技术】等多种算法智能分析出文本文字中关键词的频率和重要性,然后按照一定的算法规则智能地筛选出我们想要的搜索结果。全文索引非常适合大型数据集,对于小的数据集,它的用处比较小。
ElasticSearch就是nb
使用参数FULLTEXT 可以设置索引为全文索引。在定义索引的列上支持值的全文查找,允许在这些索引列中插入重复值和空值。全文索引只能创建在CHAR、VARCHAR或TEXT类型及其系列类型的字段上,查询数据量较大的字符串类型的字段时,使用全文索引可以提高查询速度。例如,表student的字段information是TEXT类型。该字段包含了很多文字信息。在字段information上建立全文索引后,可以提高查询字段information的速度。
全文索引典型的有两种类型:自然语言的全文索引和布尔全文索引。
自然语言搜索引擎将计算每一个文档对象和查询的相关度。这里,相关度是基于匹配的关键词的个数,以及关键词在文档中出现的次数。在整个索引中出现次数越少的词语,匹配时的相关度就越高。相反,非常常见的单词将不会被搜索,如果一个词语的在超过50%的记录中都出现了,那么自然语言的搜索将不会搜索这类词语。
MySQL数据库从3.23.23版开始支持全文索引,但MySQL5.6.4以前只有Myisam支持,5.6.4版本以后innodb才支持,但是官方版本不支持中文分词,需要第三方分词插件。在5.7.6版本,MySQL内置了ngram全文解析器,用来支持亚洲语种的分词。测试或使用全文索引时,要先看一下自己的MySQL版本、存储引擎和数据类型是否支持全文索引。8.0全解决了
随着大数据时代的到来,关系型数据库应对全文索引的需求已力不从心,逐渐被solr ,ElasticSearch等专门的搜索引擎所替代。
7.补充:空间索引。对于初学者来说,这类索引很少会用到。
小结:不同的存储引擎支持的索引类型也不一样
InnoDB:支持B-tree、Full-text等索引,不支持Hash索引;
MyISAM:支持B-tree、Full-text等索引,不支持Hash索引;
Memory:支持B-tree、Hash等索引,不支持Full-text索引;
NDB:支持Hash索引l,不支持B-tree、Full-text等索引;
Archive:不支持B-tree、Hash、Full-text等索引;
1.2 创建索引
1.创建表的时候创建索引
sql
# 01-索引的创建
#第1种:CREATE TABLE
#隐式的方式创建索引。在声明有主键约束、唯一性约束、外键约束的字段上,会自动的添加相关的索引
CREATE DATABASE dbtest2;
USE dbtest2;
CREATE TABLE dept(
dept_id INT PRIMARY KEY AUTO_INCREMENT,
dept_name VARCHAR(20)
);
CREATE TABLE emp(
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(20) UNIQUE,
dept_id INT,
CONSTRAINT emp_dept_id_fk FOREIGN KEY(dept_id) REFERENCES dept(dept_id)
);
但是,如果显式创建表时创建索引的话,基本语法格式如下:
CREATE TABLE table_name [col_name data_type]
UNIQUE \| FULLTEXT\| SPATIAL\] \[INDEX 丨 KEY\] \[index_name\] (col_name \[length\]) \[ASC\| DESC
·UNIQUE、FULLTEXT和SPATIAL为可选参数,分别表示唯一索引I、全文索引和空间索引
·INDEX与KEY为同义词,两者的作用相同,用来指定创建索引;
·index_name指定索引l的名称,为可选参数,如果不指定,那么MySQL默认col_name为索引名;
·col_name为需要创建索引的字段列,该列必须从数据表中定义的多个列中选择;
·length为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度;
·ASC或DESC指定升序或者降序的索引值存储。
sql
#显式的方式创建:
#① 创建普通的索引
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)
);
sql
#通过命令查看索引
#方式1:
SHOW CREATE TABLE book;
#方式2:
SHOW INDEX FROM book;
#性能分析工具:EXPLAIN
EXPLAIN SELECT * FROM book WHERE book_name = 'mysql高级';
sql
#② 创建唯一索引
# 声明有唯一索引的字段,在添加数据时,要保证唯一性,但是可以添加null
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)
);
哈吉之绝米,无韵之咚鸡
sql
SHOW INDEX FROM book1;
INSERT INTO book1(book_id,book_name,COMMENT)
VALUES(1,'Mysql高级','适合有数据库开发经验的人员学习');
INSERT INTO book1(book_id,book_name,COMMENT)
VALUES(2,'Mysql高级',NULL);
SELECT * FROM book1;

sql
#③ 主键索引
#通过定义主键约束的方式定义主键索引
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;
#通过删除主键约束的方式删除主键索引
ALTER TABLE book2
DROP PRIMARY KEY;
sql
#④ 创建单列索引
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;
#⑤ 创建联合索引
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)
);
#创建了索引就一定会生成B+树,先排b_id,123依次,再往下分1个 1个 1个 1个.
SHOW INDEX FROM book4;

你发现把book_id弄没了,联合索引就没了, 整个索引都是按b+树索引 , 最左前缀原则
6.创建全文索引
FULLTEXT全文索引I可以用于全文搜索,并且只为CHAR、VARCHAR和TEXT列创建索引l。索引总是对整个列进行,不支持局部(前缀)索引。
sql
#⑥ 创建全文索引
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;
29.15---30.30
不同于like方式的的查询:
sql
SELECT *FROM papers WHERE content LIKE '%查询字符串%';
全文索引用match+against方式查询:
sql
SELECT * FROM papers WHERE MATCH(title,content) AGAINST('查询字符申');
明显的提高查询效率。
注意点
1.使用全文索引前,搞清楚版本支持情况;
-
全文索引比like+%快N倍,但是可能存在精度问题;
-
如果需要全文索引的是大量数据,建议先添加数据,再创建索引。
2.在已经存在的表上创建索引
sql
#第2种:表已经创建成功
#① ALTER TABLE ... ADD ...
CREATE TABLE book5(
book_id INT ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR
);
SHOW INDEX FROM book5;
ALTER TABLE book5 ADD INDEX idx_cmt(COMMENT);
ALTER TABLE book5 ADD UNIQUE uk_idx_bname(book_name);
ALTER TABLE book5 ADD INDEX mul_bid_bname_info(book_id,book_name,info);

sql
#② CREATE INDEX ... ON ...
CREATE TABLE book6(
book_id INT ,
book_name VARCHAR(100),
AUTHORS VARCHAR(100),
info VARCHAR(100) ,
COMMENT VARCHAR(100),
year_publication YEAR
);
SHOW INDEX FROM book6;
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);
P130暂时跳了
3.索引的设计原则
sql
#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 ;
sql
# 存储过程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;
3.2哪些情况适合创建索引
1. 字段的数值有唯一性的限制
索引本身可以起到约束的作用,比如唯一索引、主键索引都是可以起到唯一性约束的,因此在我们的数据表中如果某个字段是唯一性的,就可以直接创建唯一性索引,或者主键索引。这样可以更快速地通过该索引来确定某条记录。
例如,学生表中学号是具有唯一性的字段,为该字段建立唯一性索引可以很快确定某个学生的信息,如果使用姓名的话,可能存在同名现象,从而降低查询速度。
业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。(来源:Alibaba)
说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的。
2. 频繁作为WHERE查询条件的字段
mysql8.0后没有缓存了,创建完毕show一下即可
sql
#② 频繁作为 WHERE 查询条件的字段
#查看当前stduent_info表中的索引
SHOW INDEX FROM student_info;
#student_id字段上没有索引的:
SELECT course_id, class_id, NAME, create_time, student_id
FROM student_info
WHERE student_id = 123110; #276ms
#给student_id字段添加索引
ALTER TABLE student_info
ADD INDEX idx_sid(student_id);
#student_id字段上有索引的:
SELECT course_id, class_id, NAME, create_time, student_id
FROM student_info
WHERE student_id = 123110; #43ms
3.经常GROUPBY和ORDERBY的列
索引就是让数据按照某种顺序进行存储或检索,因此当我们使用GROUPBY对数据进行分组查询,或者使用ORDER BY对数据进行排序的时候,就需要对分组或者排序的字段进行索引。如果待排序的列有多个,那么可以在这些列上建立组合索引。
tts缺少
5.DISTINCT字段需要创建索引
有时候我们需要对某个字段进行去重,使用DISTINCT,那么对这个字段创建索引,也会提升查询效率。比如,我们想要查询课程表中不同的student_id都有哪些,如果我们没有对student_id创建索引,执行SQL语句:
sql
SELECT DISTINCT(student_id) FROM *student_info′;
运行结果(600637条记录,运行时间0.683s):mysq1
如果我们对student_id创建索引l,再执行SQL语句:
sql
SELECT DISTINCT(student_id) FROM 'student_info';
运行结果(600637条记录,运行时间0.010s):你能看到SQL查询效率有了提升,同时显示出来的student_id还是按照递增的顺序进行展示的。这是因为索引会对数据按照某种顺序进行排序,所以在去重的时候也会快很多。
6.多表JOIN连接操作时,创建索引注意事项
- 首先,连接表的数量尽量不要超过3张,因为每增加一张表就相当于增加了一次嵌套的循环,数量级增长会非常快,严重影响查询的效率。
- 其次,对WHERE条件创建索引,因为WHERE才是对数据条件的过滤。如果在数据量非常大的情况下,没有WHERE条件过滤是非常可怕的。
- 最后,对用于连接的字段创建索引,并且该字段在多张表中的类型必须一致。比如course_id在student_info表和course表中都为int(11)类型,而不能一个为int另一个为varchar类型。
sql
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'; #0.001s
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'; #0.227s
132-适合创建索引的11种情况2_哔哩哔哩_bilibili------------------06.00
7.使用列的类型小的创建索引
我们这里所说的类型大小指的就是该类型表示的数据范围的大小。
我们在定义表结构的时候要显式的指定列的类型,以整数类型为例,有TINYINT、MEDIUMINT、INT、BIGINT等,它们占用的存储空间依次递增,能表示的整数范围当然也是依次递增。如果我们想要对某个整数列建立索引的话,在表示的整数范围允许的情况下,尽量让索引列使用较小的类型,比如我们能使用INT就不要使用BIGINT,能使用MEDIUMINT就不要使用INT。这是因为:
- 数据类型越小,在查询时进行的比较操作越快
- 数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以放下更多的记录,从而减少磁盘io带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率。
这个建议对于表的主键来说更加适用,因为不仅是聚簇索引中会存储主键值,其他所有的二级索引的节点处都会存储一份记录的主键值,如果主键使用更小的数据类型,也就意味着节省更多的存储空间和更高效的io。
8.使用字符串前缀创建索引
假设我们的字符串很长,那存储一个字符串就需要占用很大的存储空间。在我们需要为这个字符串列建立索引时,那就意味着在对应的B+树中有这么两个问题:
-
B+树索引中的记录需要把该列的完整字符串存储起来,更费时。而且字符串越长,在索引中占用的存储空间越大。
-
如果B+树索引中索引列存储的字符串很长,那在做字符串比较时会占用更多的时间。
我们可以通过截取字段的前面一部分内容建立索引,这个就叫前缀索引。这样在查找记录时虽然不能精确的定位到记录的位置,但是能定位到相应前缀所在的位置,然后根据前缀相同的记录的主键值回表查询完整的字符串值。既节约空间,又减少了字符串的比较时间,还大体能解决排序的问题。
例如,TEXT和BLOG类型的字段,进行全文检索会很浪费时间,如果只检索字段前面的若干字符,这样可以提高检索速度。
创建一张商户表,因为地址字段比较长,在地址字段上建立前缀索引
132-适合创建索引的11种情况2_哔哩哔哩_bilibilitts
3.4哪些情况不适合创建索引
1.在where中使用不到的字段,不要设置索引
WHERE条件(包括GROUPBY、ORDERBY)里用不到的字段不需要创建索引I,索引I的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的。举个例子:
sql
SELECT course_id, student_id, create_time
FROM student_info
WHERE student_id = 41251;
因为我们是按照student_id来进行检索的,所以不需要对其他字段创建索引,即使这些字段出现在SELECT字段中。
2.数据量小的表最好不要使用索引
如果表记录太少,比如少于1000个,那么是不需要创建索引的。表记录太少,是否创建索引对查询效率的影响并不大。甚至说,查询花费的时间可能比遍历索引的时间还要短,索引可能不会产生优化效果。
举例:创建表1:
sql
CREATE TABLE t_without_index(
a INT PRIMARY KEY AUTO_INCREMENT,
b INT
):
你能看到运行结果相同,但是在数据量不大的情况下,索引就发挥不出作用了。
结论:在数据表中的数据行数比较少的情况下,比如不到1000行,是不需要创建索引的
3.有大量重复数据的列上不要建立索引
在条件表达式中经常用到的不同值较多的列上建立索引,但字段中如果有大量重复数据,也不用创建索引。比如在学生表的"性别"字段上只有"男"与"女"两个不同值,因此无须建立索引。如果建立索引,不但不会提高查询效率,反而会严重降低数据更新速度。
举例1:要在100万行数据中查找其中的50万行(比如性别为男的数据),一旦创建了索引,你需要先访问50万次索引,然后再访问50万次数据表,这样加起来的开销比不使用索引可能还要大。
举例2:假设有一个学生表,学生总数为100万人,男性只有10个人,也就是占总人口的10万分之1。
比如我们查到叶子节点有一半的数据都
有人也说心别作为非聚簇索引要经常进行回表,这样要大量 的io开销。
sql
SELECT * FROM student_gender WHERE student_gender =1
同样是10条数据,运行结果相同,时间却缩短到了0.052s,大幅提升了查询的效率。
其实通过这两个实验你也能看出来,索引的价值是帮你快速定位。如果想要定位的数据有很多,那么索引就失去了它的使用价值,比如通常情况下的性别字段。
结论:当数据重复度大,比如高于 10%的时候,也不需要对这个字段使用索引。
4.避免对经常更新的表创建过多的索引
第一层含义:频繁更新的字段不一定要创建索引。因为更新数据的时候,也需要更新索引,如果索引太多,在更新索引的时候也会造成负担,从而影响效率。
第二层含义:避免对经常更新的表创建过多的索引,并且索引中的列尽可能少。此时,虽然提高了查询速度,同时却会降低更新表的速度。
5.不建议用无序的值作为索引
例如身份证、UUID(在索引比较时需要转为ASCII,并且插入时可能造成页分裂)、MD5、HASH、无序长字符串等。大部分都是有序的(增序)
6.删除不再使用或者很少使用的索引
表中的数据被大量更新,或者数据的使用方式被改变后,原有的一些索引可能不再需要。数据库管理员应当定期找出这些索引,将它们删除,从而减少索引对更新操作的影响。
7.不要定义余或重复的索引
133-不适合创建索引的7种情况_哔哩哔哩_bilibili------------08.50