索引是对数据库表中一列或多列的值进行排序的一种结构。MySQL索引的建立对于MySQL的高效运行是很重要的,索引可以大大提高MySQL的检索速度。索引只是提高效率的一个因素,如果你的MySQL有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。
??简单类比一下,数据库如同书籍,索引如同书籍目录,假如我们需要从书籍查找与 xx 相关的内容,我们可以直接从目录中查找,定位到 xx 内容所在页面,如果目录中没有 xx 相关字符或者没有设置目录(索引),那只能逐字逐页阅读文本查找,效率可想而知。
如果你不仅仅想了解索引,可看我上篇文章《MySQL体系构架、存储引擎和索引结构》
1索引的优缺点
=======================
索引可以大大提高MySQL的检索速度,为什么不对表中的每一个列创建一个索引呢?
优点
缺点
2创建索引准则
=======================
索引是建立在数据库表中的某些列的上面。因此,在创建索引的时候,应该仔细考虑在哪些列上可以创建索引,在哪些列上不能创建索引。
应该创建索引的列
不该创建索引的列
3索引结构
=====================
MySQL中常用的索引结构(索引底层的数据结构)有:B-TREE ,B+TREE ,HASH 等。这部分强烈建议看我上篇《MySQL体系构架、存储引擎和索引结构》,有更细致的讲解,以下我也会简要说明。
B-TREE
B-树就是B树,多路搜索树,树高一层意味着多一次的磁盘I/O,下图是3阶B树
B树的特征:
B+TREE
B+树是B-树的变体,也是一种多路搜索树
B+树的特征:
HASH
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。
Hash索引仅仅能满足"=",“IN"和”<=>"
查询,不能使用范围查询。也不支持任何范围查询,例如WHERE price > 100
。
由于Hash索引比较的是进行Hash运算之后的Hash值,所以它只能用于等值的过滤,不能用于基于范围的过滤,因为经过相应的Hash算法处理之后的Hash值的大小关系,并不能保证和Hash运算前完全一样。
补充:索引存储在文件系统中
索引是占据物理空间的,在不同的存储引擎中,索引存在的文件也不同。存储引擎是基于表的,以下分别使用MyISAM和InnoDB存储引擎建立两张表。
存储引擎为MyISAM:
存储引擎为InnoDB:
4索引分类
=====================
MySQL 的索引有两种分类方式:逻辑分类和物理分类。
逻辑分类
有多种逻辑划分的方式,比如按功能划分,按组成索引的列数划分等
按功能划分
主键索引:一张表只能有一个主键索引,不允许重复、不允许为 NULL;
ALTER TABLE TableName ADD PRIMARY KEY(column_list);
唯一索引:数据列不允许重复,允许为 NULL 值,一张表可有多个唯一索引,索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。
CREATE UNIQUE INDEX IndexName ON TableName
(字段名
(length));
ALTER TABLE TableName ADD UNIQUE (column_list);
普通索引:一张表可以创建多个普通索引,一个普通索引可以包含多个字段,允许数据重复,允许 NULL 值插入;
CREATE INDEX IndexName ON TableName
(字段名
(length));
ALTER TABLE TableName ADD INDEX IndexName(字段名
(length));
全文索引:它查找的是文本中的关键词,主要用于全文检索。(篇幅较长,下文有独立主题说明)
按列数划分
物理分类
分为聚簇索引和非聚簇索引(有时也称辅助索引或二级索引)
聚簇索引和非聚簇索引
聚簇是为了提高某个属性(或属性组)的查询速度,把这个或这些属性(称为聚簇码)上具有相同值的元组集中存放在连续的物理块。
聚簇索引(clustered index)不是单独的一种索引类型,而是一种数据存储方式。这种存储方式是依靠B+树来实现的,根据表的主键构造一棵B+树且B+树叶子节点存放的都是表的行记录数据时,方可称该主键索引为聚簇索引。聚簇索引也可理解为将数据存储与索引放到了一块,找到索引也就找到了数据。
非聚簇索引:数据和索引是分开的,B+树叶子节点存放的不是数据表的行记录。
虽然InnoDB和MyISAM存储引擎都默认使用B+树结构存储索引,但是只有InnoDB的主键索引才是聚簇索引,InnoDB中的辅助索引以及MyISAM使用的都是非聚簇索引。每张表最多只能拥有一个聚簇索引。
拓展:聚簇索引优缺点
优点:
缺点:
【补充】Mysql中key 、primary key 、unique key 与index区别
key 与 index 含义
key具有两层含义:1.约束(约束和规范数据库的结构完整性)2.索引
index:索引
key 种类
key:等价普通索引 key 键名 (列)
primary key:
unique key:
foreign key:
实战分析
建立个user表,看看表的各个字段,下面我们逐一分析
mysql> create table user(
-> id int auto_increment,
-> username varchar(100) not null,
-> user_id int(8) primary key,
-> depart_no int not null,
-> corp varchar(100),
-> phone char(11),
-> key auto_id (id),
-> unique key phone (phone),
-> index username_depart_corp (username,depart_no,corp),
-> constraint fk_user_depart foreign key(depart_no) references depart(id);
-> )engine=innodb charset=utf8;
auto_increment修饰的字段需要是一个候选键,需要用key指定,否则报错。我们看下表的结构:
查看表的索引
可见key也会生成索引
key 值类型
如果一个Key有多个约束,将显示约束优先级最高的, PRI>UNI>MUL
5InnoDB和MyISAM索引实现
==================================
InnoDB索引实现
InnoDB使用B+TREE存储数据,除了主键索引为聚簇索引,其它索引均为非聚簇索引。
一个表中只能存在一个聚簇索引(主键索引),但可以存在多个非聚簇索引。
InnoDB表的索引和数据是存储在一起的,.idb
表数据和索引的文件
聚簇索引(主键索引)
B+树 叶子节点包含数据表中行记录就是聚簇索引(索引和数据是存放在一块的)
InnoDB主键索引的示意图
可以看到叶子节点包含了完整的数据记录,这就是聚簇索引。因为InnoDB的数据文件(.idb)按主键聚集,所以InnoDB必须有主键(MyISAM可以没有),如果没有显示指定主键,则选取首个为唯一且非空的列作为主键索引,如果还没具备,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整形。
主键索引结构分析:
非聚簇索引(辅助索引或二级索引)
在聚簇索引之外创建的索引(不是根据主键创建的)称之为辅助索引,辅助索引访问数据总是需要二次查找。辅助索引叶子节点存储的不再是行数据记录,而是主键值。首先通过辅助索引找到主键值,然后到主键索引树中通过主键值找到数据行。
InnoDB辅助索引的示意图
拓展:InnoDB索引优化
InnoDB中主键不宜定义太大,因为辅助索引也会包含主键列,如果主键定义的比较大,其他索引也将很大。如果想在表上定义 、很多索引,则争取尽量把主键定义得小一些。InnoDB 不会压缩索引。
InnoDB中尽量不使用非单调字段作主键(不使用多列),因为InnoDB数据文件本身是一颗B+Tree,非单调的主键会造成在插入新记录时数据文件为了维持B+Tree的特性而频繁的分裂调整,十分低效,而使用自增字段作为主键则是一个很好的选择。
MyISAM索引实现
MyISAM也使用B+Tree作为索引结构,但具体实现方式却与InnoDB截然不同。MyISAM使用的都是非聚簇索引。
MyISAM表的索引和数据是分开存储的,.MYD
表数据文件 .MYI
表索引文件
MyISAM主键索引
MyISAM主键索引的原理图
可以看到叶子节点的存放的是数据记录的地址。也就是说索引和行数据记录是没有保存在一起的,所以MyISAM的主键索引是非聚簇索引。
MyISAM辅助索引
在MyISAM中,主索引和辅助索引(Secondary key)在结构上没有任何区别,只是主索引要求key是唯一的,而辅助索引的key可以重复。 MyISAM辅助索引也是非聚簇索引。
InnoDB和MyISAM的索引检索过程
对于InnoDB和MyISAM而言,主键索引是根据主关键字来构建的B+树存储结构,辅助索引则是根据辅助键来构造的B+树存储结构,彼此的索引树都是相互独立的。
InnoDB辅助索引的访问需要两次索引查找,第一次从辅助索引树找到主键值,第二次根据主键值到主键索引树中找到对应的行数据。
MyISM使用的是非聚簇索引,表数据存储在独立的地方,这两棵(主键和辅助键)B+树的叶子节点都使用一个地址指向真正的表数据。由于索引树是独立的,通过辅助键检索无需访问主键的索引树。
假想一个表如下图存储了4行数据。其中Id作为主索引,Name作为辅助索引。图示清晰的显示了聚簇索引和非聚簇索引的差异。
聚簇索引和非聚簇索引的区别
6操作索引
=====================
创建索引
索引名称 index_name 是可以省略的,省略后,索引的名称和索引列名相同。
-- 创建普通索引
CREATE INDEX index_name ON table_name(col_name);
-- 创建唯一索引
CREATE UNIQUE INDEX index_name ON table_name(col_name);
-- 创建普通组合索引
CREATE INDEX index_name ON table_name(col_name_1,col_name_2);
-- 创建唯一组合索引
CREATE UNIQUE INDEX index_name ON table_name(col_name_1,col_name_2);
修改表结构创建索引
ALTER TABLE table_name ADD INDEX index_name(col_name);
创建表时直接指定索引
CREATE TABLE table_name (
ID INT NOT NULL,
col_name VARCHAR (16) NOT NULL,
INDEX index_name (col_name)
);
删除索引
-- 直接删除索引
DROP INDEX index_name ON table_name;
-- 修改表结构删除索引
ALTER TABLE table_name DROP INDEX index_name;
其它相关命令
-- 查看表结构
desc table_name;
-- 查看生成表的SQL
show create table table_name;
-- 查看索引信息(包括索引结构等)
show index from table_name;
-- 查看SQL执行时间(精确到小数点后8位)
set profiling = 1;
SQL...
show profiles;
7索引实战
=====================
索引实战学习的基础,首先应该学会分析SQL的执行,使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,下面我们学习下EXPLAIN。
explain
使用EXPLAIN关键字可以模拟优化器执行SQL查询语句,从而知道MySQL是如何处理SQL语句。不展开讲解,大家可自行百度这块知识点。
使用格式:EXPLAIN SQL...;
Look一下EXPLAIN 查询结果包含的字段(v5.7)
mysql> explain select * from student;
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------+
| 1 | SIMPLE | student | NULL | ALL | NULL | NULL | NULL | NULL | 2 | 100.00 | NULL |
+----+-------------+---------+------------+------+---------------+------+---------+------+------+----------+-------+
Extra 探究
本打算展开讲一下Extra的常见的几个值:Using index,Using index condition,Using where,其中Using index 表示使用了覆盖索引,其它的都不好总结。翻阅网上众多博文,目前暂未看到完全总结到位的,且只是简单的查询条件下也是如此。我本打算好好总结一番,发现半天过去了,坑貌似越来越大,先打住,因为我目前没太多时间研究…
我这有个简单的表结构,有兴趣的同学可以多尝试总结(注意 玩总结的,不能只考虑简单查询的情况)
create table student(
id int auto_increment primary key,
name varchar(255) not null,
c_id int,
phone char(11),
guardian varchar(50) not null,
qq varchar(20) not null,
index stu_class_phone (name,c_id,phone),
index qq (qq)
)engine=innodb charset=utf8;
这里有我的一个比对关键项表,或许对有心探索的同学有点帮助,估计看了也有点懵,建议先尝试后再回头看我这个表。
key_len讲解
我也稍微讲解一下网文中鲜有提及key_len字节长度计算规则,
mysql> explain select * from student where name='Joe';
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------+------+----------+-------+
| 1 | SIMPLE | student | NULL | ref | stu_class_phone | stu_class_phone | 767 | const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from student where name='Joe' and c_id=2;
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------+------+----------+-------+
| 1 | SIMPLE | student | NULL | ref | stu_class_phone | stu_class_phone | 772 | const,const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from student where name='Joe' and c_id=2 and phone='13500000000';
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------------+------+----------+-------+
| 1 | SIMPLE | student | NULL | ref | stu_class_phone | stu_class_phone | 806 | const,const,const | 1 | 100.00 | NULL |
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec)
如果表结构未限制某列为非空,那么MySQL将会使用一个字节来标识该列对应的值是否为NULL;限定非空,不止not null,还有primary key等隐含非空约束。
字符串类型括号内的数字并不是字节数,而是字符长度,一个字符占几个字节与建表选用的字符集有关,如果表使用的是utf8字符集,那么一个字符占3个字节;注意,对于可变长字符串类(varchar)型的实际占用字节数,除了需要考虑设置了非空与否的那个字节,还要使用2个字节来记录字符串的长度。定长字符串类型(char)则不用额外字节记录长度
整数类型括号内的数字无论是什么,都不影响它实际的字节数,int就是4个字节。int(xx),xx只是填充占位符,一般配合zerofill使用,只是一种标识,没有多大用处。
观察三次Explain 的查询结果,留意key_len与where搜索键的微妙关系,如果type列的值是ref时,ref列的值标识索引参考列的形参。
首先,我们看到key列为stu_class_phone
,说明该查询使用了stu_class_phone索引,这是一个组合索引(name,c_id,phone)
。看下这三个字段的结构声明与实际字节计算:
name varchar(255) not null
, (占767字节)
not null
) 那就不额外占1字节c_id int
,(占5字节)
phone char(11)
,(占36字节)
int(xx) xx无论是多少 int永远4字节 xx只是填充占位符(一种标识 一般配合zerofill使用的)
组合索引满足最左前缀原则就会生效,我们看到三次Explain 的查询中stu_class_phone索引都生效了,第一次采用name构建索引树,第二次采用name+c_id构建索引树,第三次采用name+c_id+phone构建索引树。第一次:key_len就是name的存储字节数,767;第二次:key_len就是name+c_id的存储字节数,767+5=772;第三次:255 * 3 + 2 + 5 + 11 * 3 + 1 = 806
我们再看一条执行计划:
mysql> explain select * from student where name='Joe' and phone ='13500000000';
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | student | NULL | ref | stu_class_phone | stu_class_phone | 767 | const | 1 | 50.00 | Using index condition |
+----+-------------+---------+------------+------+-----------------+-----------------+---------+-------+------+----------+-----------------------+
为什么不是255 * 3 + 11 * 3 + 1 +2=801;却是767?我们看下ref为const,说明只有一个索引键生效,明显就是name,因为 不符合最左前缀原则,phone列被忽视了;也可能是mysql做了优化,发现通过name和phone构建的索引树对查询列 (* 表示全部列)并没有加快了查询速率,自行优化,减少键长。
拓展:优秀的索引是什么样的?
比如,在保证查询精度的情况下,两个索引的key_len分别为10字节和100字节,数据行的量也一样(大数据量效果更佳),100字节索引检索的时间会比10字节的要多;再者,一个磁盘页能存储的10字节的索引记录的量是100字节的10倍。
最左前缀原则
在MySQL建立联合索引时会遵守最左前缀匹配原则,即最左优先(查询条件精确匹配索引的左边连续一列或几列,则构建对应列的组合索引树),在检索数据时也从联合索引的最左边开始匹配。
为了方便讲解,我写了点个人理解的概念声明,如下图:
mysql> create table t(
-> a int not null,
-> b char(10) not null,
-> c int not null,
-> d varchar(20) not null,
-> index abc(a,b,c)
-> )engine=innodb charset=utf8;
mysql> insert into t values(1,'hello',1,'world');
mysql> insert into t values(2,'hello',2,'mysql');
以下均为筛选条件不包含主键索引情况下:(主键索引优先级最高)
只要筛选条件中含有组合索引最左边的列但不含有主键搜索键的时候,至少会构建包含组合索引最左列的索引树。(如:index(a))
查询列都是组合索引列且筛选条件全是组合索引列时,会构建满列组合索引树(index(a,b,c) )【覆盖索引】
筛选条件包含普通搜索键但没包含组合索引列最左键,不会构建组合索引树
如果筛选条件全是组合索引最左连续列作为搜索键,将构建连续列组合索引树。(比如:index(a,b)却不能index(a,c))
MySQL查询优化器会优化and连接,将组合索引列规则排号。(比如:b and a 等同于 a and b)
前缀索引
有时候需要索引很长的字符列,这会让索引变得大且慢。通常可以以某列开始的部分字符作为索引,这样可以大大节约索引空间,从而提高索引效率。但这样也会降低索引的选择性。索引的选择性是指不重复的索引值和数据表的记录总数的比值,索引的选择性越高则查询效率越高。
以下是一个百万级数据表的简化呈现
图一 area 字段没有设置为索引,图二 area 字段设置为前4字符作为索引,图三 area 字段设置前5字符作为索引,当数据是百万当量时候,毫无疑问,图三的索引速度将大大优越于前两个图场景。
CREATE TABLE `x_test` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
`x_name` varchar(255) NOT NULL,
PRIMARY KEY (`id`),
) ENGINE=InnoDB DEFAULT CHARSET=utf8
通过存储过程插入10万数据(不建议使用此方法,太慢了)
DROP PROCEDURE IF EXISTS proc_initData;
DELIMITER $
CREATE PROCEDURE proc_initData()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i<=100000 DO
INSERT INTO x_test VALUES(null,RAND()*100000);
SET i = i+1;
END WHILE;
END $
CALL proc_initData();
不使用索引查询某条记录
使用索引查询某条记录
alter table x_test add index(x_name(4));
前缀字符并非越多越好,需要在索引的选择性和索引IO读取量中做出衡量。
覆盖索引与回表
上文我们介绍过索引可以划分为聚簇索引和辅助索引。在InnoDB中的主键索引就是聚簇索引,主键索引的查询效率也是非常高的,除此之外,还有非聚簇索引,其查询效率稍逊。覆盖索引其形式就是,搜索的索引键中的字段恰好是查询的字段(或是组合索引键中的其它字段)。覆盖索引的查询效率极高,原因在与其不用做回表查询。
student表中存在组合索引 stu_class_phone(name,c_id,phone),student表结构如下:
mysql> desc student;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | MUL | NULL | |
| c_id | int(11) | YES | | NULL | |
| phone | char(11) | YES | | NULL | |
| guardian | varchar(50) | NO | | NULL | |
+----------+--------------+------+-----+---------+----------------+
最直观的呈现:(通过explain执行分析SQL可观测到Extra字段值包含Using index)
当然对于组合索引你还可以查询组合索引键中的其他字段:
但是不能包含杂质搜索键(不属于所搜索索引中的列)
典型使用场景: 全表count查询,根据某关键字建立索引,直接count(关键字)即可,如果是count() 则需要回表搜索。(此项做保留,近期发现count() 也是使用了using index,有可能是新版本mysql内部做了优化处理)
alter table student add key(name);
mysql> explain select count(name) from student;
+----+-------------+---------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | student | NULL | index | NULL | name | 767 | NULL | 1 | 100.00 | Using index |
+----+-------------+---------+------------+-------+---------------+------+---------+------+------+----------+-------------+
回表
查询的列数据作为索引树的键值,直接在索引树中得到反馈(存在于索引节点),不用遍历如InnoDB中的叶子节点(存放数据表各行数据)就可得到查询的数据(不用回表)。
下面以InnoDB表中的辅助索引作图示说明:
全文索引 FULLTEXT
通过数值比较、范围过滤等就可以完成绝大多数我们需要的查询,但是,如果希望通过关键字的匹配来进行查询过滤,那么就需要基于相似度的查询,而不是原来的精确数值比较。全文索引,就是为这种场景设计的,通过建立倒排索引,可以极大的提升检索效率,解决判断字段是否包含的问题。
FULLTEXT VS LIKE+%
使用LIKE+%
确实可以实现模糊匹配,适用于文本比较少的时候。对于大量的文本检索,LIKE+%
与全文索引的检索速度相比就不是一个数量级的。
例如: 有title字段,需要查询所有包含 "政府"的记录.,使用 like "%政府%“方式查询,查询速度慢(全表查询)。且当查询包含"政府” OR "中国"的字段时,使用like就难以简单满足,而全文索引就可以实现这个功能。
FULLTEXT的支持情况
InnoDB内部并不支持中文、日文等,因为这些语言没有分隔符。可以使用插件辅助实现中文、日文等的全文索引。
创建全文索引
//建表的时候
FULLTEXT KEY keyname(colume1,colume2) // 创建联合全文索引列
//在已存在的表上创建
create fulltext index keyname on xxtable(colume1,colume2);
alter table xxtable add fulltext index keyname (colume1,colume2);
使用全文索引
全文索引有独特的语法格式,需要配合match 和 against 关键字使用
match()函数中指定的列必须是设置为全文索引的列
against()函数标识需要模糊查找的关键字
create table fulltext_test(
id int auto_increment primary key,
words varchar(2000) not null,a
artical text not null,
fulltext index words_artical(words,artical)
)engine=innodb default charset=utf8;
insert into fulltext_test values(null,‘a’,‘a’);
insert into fulltext_test values(null,‘aa’,‘aa’);
insert into fulltext_test values(null,‘aaa’,‘aaa’);
insert into fulltext_test values(null,‘aaaa’,‘aaaa’);
好,我们使用全文搜索查看一下
select * from fulltext_test where match(words,artical) against('a');
select * from fulltext_test where match(words,artical) against('aa');
select * from fulltext_test where match(words,artical) against('aaa');
select * from fulltext_test where match(words,artical) against('aaaa');
发现只有aaa和aaaa才能查到记录,为什么会这样呢?
全文索引关键词长度阈值
这其实跟全文搜索的关键词的长度阈值有关,可以通过show variables like '%ft%';
查看。可见InnoDB的全文索引的关键词 最小索引长度 为3。上文使用的是InnoDB引擎建表,同时也解释为什么只有3a以上才有搜索结果。
设置关键词长度阈值,可以有效的避免过短的关键词,得到的结果过多也没有意义。
也可以手动配置关键词长度阈值,修改MySQL配置文件,在[mysqld]的下面追加以下内容,设置关键词最小长度为5。
[mysqld]
innodb_ft_min_token_size = 5
ft_min_word_len = 5
然后重启MySQL服务器,还要修复索引,不然参数不会生效
repair table 表名 quick;
为什么上文搜索关键字为aaa的时候,有且仅有一条aaa的记录,为什么没有aaaa的记录呢?
全文索引模式
自然语言的全文索引 IN NATURAL LANGUAGE MODE
默认情况下,或者使用 IN NATURAL LANGUAGE MODE 修饰符时,match()
函数对文本集合执行自然语言搜索,上面的例子都是自然语言的全文索引。
自然语言搜索引擎将计算每一个文档对象和查询的相关度。这里,相关度是基于匹配的关键词的个数,以及关键词在文档中出现的次数。在整个索引中出现次数越少的词语,匹配时的相关度就越高。
MySQL在全文查询中会对每个合适的词都会先计算它们的权重,如果一个词出现在多个记录中,那它只有较低的权重;相反,如果词是较少出现在这个集的文档中,它将得到一个较高的权重。
MySQL默认的阀值是50%。如果一个词语的在超过 50% 的记录中都出现了,那么自然语言的搜索将不会搜索这类词语。
上文关键词长度阈值是3,所以相当于只有两条记录:aaa 和 aaaa
aaa 权重 2/2=100% 自然语言的搜索将不会搜索这类词语aaa了 而是进行精确查找 aaaa不会出现在aaa的结果集中。
这个机制也比较好理解,比如一个数据表存储的是一篇篇的文章,文章中的常见词、语气词等等,出现的肯定比较多,搜索这些词语就没什么意义了,需要搜索的是那些文章中有特殊意义的词,这样才能把文章区分开。
select * from fulltext_test where match(words,artical) against('aaa');
等价
select * from fulltext_test where match(words,artical) against('aaa' IN NATURAL LANGUAGE MODE);
布尔全文索引 IN BOOLEAN MODE
在布尔搜索中,我们可以在查询中自定义某个被搜索的词语的相关性,这个模式和lucene中的BooleanQuery很像,可以通过一些操作符,来指定搜索词在结果中的包含情况。
建立如下表:
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
body TEXT,
FULLTEXT (title,body)
) ENGINE=InnoDB
+
(AND)全文索引列必须包含该词且全文索引列(之一)有且仅有该词
-
(NOT)表示必须不包含,默认为误操作符。如果只有一个关键词且使用了-
,会将这个当成错误操作,相当于没有查询关键词;如果有多个关键词,关键词集合中不全是负能符(~ -
),那么-
则强调不能出现。
– 查找title,body列中有且仅有apple(是apple不是applexx 也不是 xxapple)但是不含有banana的记录
SELECT * FROM articles WHERE MATCH (title,body) AGAINST (‘+apple -banana’ IN BOOLEAN MODE);
>
提高该词的相关性,查询的结果靠前
<
降低该词的相关性,查询的结果靠后
– 返回同时包含apple(是apple不是applexx 也不是 xxapple)和banana或者同时包含apple和orange的记录。但是同时包含apple和banana的记录的权重高于同时包含apple和orange的记录。
SELECT * FROM articles WHERE MATCH (title,body) AGAINST (‘+apple +(>banana
~
异或,如果包含则降低关键词整体的相关性
– 返回的记录必须包含apple(且不能是applexx 或 xxapple),但是如果同时也包含banana会降低权重(只出现apple的记录会出现在结果集前面)。但是它没有 +apple -banana 严格,因为后者如果包含banana压根就不返回。
SELECT * FROM articles WHERE MATCH (title,body) AGAINST (‘+apple ~banana’ IN BOOLEAN MODE);
*
通配符,表示关键词后面可以跟任意字符
– 返回的记录可以为applexx
SELECT * FROM articles WHERE MATCH (title,body) AGAINST (‘apple*’ IN BOOLEAN MODE);
空格 表示OR
– 查找title,body列中包含apple(是apple不是applexx 也不是 xxapple)或者banana的记录,至少包含一个
SELECT * FROM articles WHERE MATCH (title,body) AGAINST (‘apple banana’ IN BOOLEAN MODE);
""
双引号,效果类似like '%some words%'
– 模糊匹配 “apple banana goog”会被匹配到,而“apple good banana”就不会被匹配
SELECT * FROM articles WHERE MATCH (title,body) AGAINST (‘“apple banana”’ IN BOOLEAN MODE);
InnoDB中FULLTEXT中文支持
InnoDB内部并不支持中文、日文等,因为这些语言没有分隔符。可以使用插件辅助实现中文、日文等的全文索引。
MySQL内置ngram插件便可解决该问题。
FULLTEXT (title, body) WITH PARSER ngram
ALTER TABLE articles ADD FULLTEXT INDEX ft_index (title,body) WITH PARSER ngram;
CREATE FULLTEXT INDEX ft_index ON articles (title,body) WITH PARSER ngram;
索引失效
数据库表中添加索引后确实会让查询速度起飞,但前提必须是正确的使用索引来查询,如果以错误的方式使用,则即使建立索引也会不奏效。即使建立索引,索引也不会生效:
create table tb1(
nid int auto_increment primary key,
name varchar(100) not null,
email varchar(100) not null,
num int,
no_index char(10),
index(name),
index(email),
index(num)
)engine=innodb;
以下说明都 排除覆盖索引 情况下:
> < 范围查询
mysql 会一直向右匹配直到遇到索引搜索键使用>、<
就停止匹配。一旦权重最高的索引搜索键使用>、<
范围查询,那么其它>、<
搜索键都无法用作索引。即索引最多使用一个>、<
的范围列,因此如果查询条件中有两个>、<
范围列则无法全用到索引。
like %xx
如搜索键值以通配符%开头
(如:like '%abc'
),则索引失效,直接全表扫描;若只是以%结尾,则不影响索引构建。
mysql> explain select * from tb1 where name like '%oe';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb1 | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 33.33 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
对索引列进行运算
如果查询条件中含有函数或表达式,将导致索引失效而进行全表扫描。 select * from user where YEAR(birthday) < 1990
mysql> explain select * from tb1 where length(name)>2;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb1 | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
or 条件索引问题
or
的条件列除了同时是主键的时候,索引才会生效。其他情况下的,无论条件列是什么,索引都失效。
mysql> explain select * from tb1 where nid=1 or nid=2;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb1 | NULL | range | PRIMARY | PRIMARY | 4 | NULL | 2 | 100.00 | Using where |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from tb1 where name='Joe' or name='Tom';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb1 | NULL | ALL | name | NULL | NULL | NULL | 3 | 66.67 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
数据类型不一致(隐式类型转换导致的索引失效)
如果列是字符串类型,传入条件是必须用引号引起来,不然报错或索引失效。
mysql> explain select * from tb1 where name=12;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb1 | NULL | ALL | name | NULL | NULL | NULL | 3 | 33.33 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
!= 问题
普通索引使用 !=
索引失效,主键索引没影响。
where语句中索引列使用了负向查询,可能会导致索引失效。
负向查询包括:NOT、!=、<>、NOT IN、NOT LIKE等。
mysql> explain select * from tb1 where name!='Joe';
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | tb1 | NULL | ALL | name | NULL | NULL | NULL | 3 | 100.00 | Using where |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+-------------+
联合索引违背最左匹配原则
联合索引中,where中索引列违背最左匹配原则,一定会导致索引失效(上文有说)
order by问题
order by 对主键索引排序会用到索引,其他的索引失效
mysql> explain select * from tb1 order by name;
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
| 1 | SIMPLE | tb1 | NULL | ALL | NULL | NULL | NULL | NULL | 3 | 100.00 | Using filesort |
+----+-------------+-------+------------+------+---------------+------+---------+------+------+----------+----------------+
1 row in set, 1 warning (0.00 sec)
mysql> explain select * from tb1 order by nid;
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
| 1 | SIMPLE | tb1 | NULL | index | NULL | PRIMARY | 4 | NULL | 3 | 100.00 | NULL |
+----+-------------+-------+------------+-------+---------------+---------+---------+------+------+----------+-------+
【主键妙用】LIMIT分页
若需求是每页显示10条数据,如何建立分页?
我们可以先使用LIMIT尝试:
--第一页
SELECT * FROM table_name LIMIT 0,10;
--第二页
SELECT * FROM table_name LIMIT 10,10;
--第三页
SELECT * FROM table_name LIMIT 20,10;
但是这样做有如下弊端:
每一条select语句都会从1遍历至当前位置,若跳转到第100页,则会遍历1000条记录
改善:
若已知每页的max_id和min_id,则可以通过主键索引来快速定位:
--下一页
SELECT * FROM table_name WHERE id in (SELECT id FROM table_name WHERE id > max_id LIMIT 10);
--上一页
SELECT * FROM table_name WHERE id in (SELECT id FROM table_name WHERE id < min_id ORDER BY id DESC LIMIT 10);
--当前页之后的某一页
SELECT * FROM table_name WHERE id in (SELECT id FROM (SELECT id FROM (SELECT id FROM table_name WHERE id < min_id ORDER BY id desc LIMIT (页数差*10)) AS N ORDER BY N.id ASC LIMIT 10) AS P ORDER BY P.id ASC);
--当前页之前的某一页
SELECT * FROM table_name WHERE id in (SELECT id FROM (SELECT id FROM (SELECT id FROM table_name WHERE id > max_id LIMIT (页数差*10)) AS N ORDER BY N.id DESC LIMIT 10) AS P) ORDER BY id ASC;
可能版本会不支持:This version of MySQL doesn't yet support 'LIMIT
只要加多一个子查询即可
SELECT * FROM x_test WHERE id in (SELECT id FROM ( SELECT id FROM x_test WHERE id>max_id LIMIT 10) t);
=============================================================================================
至此!如有不足,还望各位读者指出。如果还不错,一键三连。
=============================================================================================
参考文档:
MySQL索引总结
你来说一下 Mysql 索引有几种类型呢?分别是什么?
聚簇索引和非聚簇索引(通俗易懂 言简意赅)
MySQL 之全文索引