数据库索引在业务中显然是非常重要的,设计一个良好的索引可以带来极大的检索速度的提升。
索引是什么?
首先直接表明,索引的本质就是数据结构。
举个例子:现在有一段长度为100万
的无序序列,序列范围为0~100万
,我们希望在其中找到值为100万
的数据。那么最简单的方法就是顺序查找,其时间复杂度为
O
(
n
)
O(n)
O(n),这在高并发的程序下显然是无法忍受的。后来随着计算机的发展出现了更多实用的查找算法,例如二分查找、二叉树查找等,但这些优秀的算法都只能应用在特定的数据结构中,例如二分查找需要保证序列是有序的,而二叉树查找则需要将数据首先建立在一颗二叉查找树上。而数据本身是丰富的,如一行数据会包含多种类型的数据,这些数据理论上不可能将多列都按照顺序进行排序,因此就需要用户指定某列进行排序,以某列进行排序组成的数据结构就是索引。
看了上面的描述,可以理解到索引实际上就是我们字典中的目录,通过这个目录我们可以很快的查询到我们希望得到的内容。
此时再看百科中索引的解释就很清晰了:
在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。
索引的作用
- 加快检索速度。这是最显然的作用了。
- 确保数据的唯一性。唯一的索引值对应着唯一的数据,但更准确的说,要保证数据的唯一性,在这里特指主键索引和唯一索引。因为索引的种类除了这些还有普通索引以及全文索引,这两种索引并不能保证这个特性。此外,在向数据库中插入数据的时候,每次插入都会查询表中这个值是否存在,如果不存在才能插入,否则无法插入,这也保证了数据的唯一性。
- 提高系统的性能。
索引的分类
索引有以下四类:
-
主键索引(PRIMARY KEY)。 某一个属性组能唯一标识一条记录,不允许出现相同的值,且不能有NULL值,且一个表只能包含一个主键索引。
-
唯一索引(UNIQUE)。不可以出现相同的值,但是可以有NULL值,同时因为NULL!=NULL,因此NULL可以有多个。并且唯一索引可以有多个。
-
普通索引(INDEX)。允许出现相同的值,一般在常用到where子句的字段上添加。但不宜添加过多常规索引,因为影响数据的增删改操作。
-
全文索引(FULLTEXT)。上面的三种索引都是针对具体的值发挥作用的,但全文索引可以正对值中的某个字段(例如content中的一个单词)起作用,因为用的比较少,想看更加详细的解释可以看这里: MySQL 之全文索引 。
索引操作
本节介绍索引的创建和删除以及查看索引。
索引的创建
-
创建表时创建。如下:
CREATE TABLE `test1` ( `id` smallint(5) UNSIGNED AUTO_INCREMENT NOT NULL, -- 注意,下面创建了主键索引,这里就不用创建了 `username` varchar(64) NOT NULL COMMENT '用户名', `nickname` varchar(50) NOT NULL COMMENT '昵称/姓名', `intro` text, PRIMARY KEY (`id`), UNIQUE KEY `unique1` (`username`), -- 索引名称,可要可不要,不要就是和列名一样 KEY `index1` (`nickname`), FULLTEXT KEY `intro` (`intro`) ) DEFAULT CHARSET=utf8 ;
注意
key
和index
在使用时并没有什么区别。 -
在已存在的表上创建索引。
-
ALTER :
ALTER TABLE `table_name` ADD INDEX `index_name` (`column_list`) -- 索引名,可要可不要;如果不要,当前的索引名就是该字段名。 ALTER TABLE `table_name` ADD UNIQUE (`column_list`) ALTER TABLE `table_name` ADD PRIMARY KEY (`column_list`) ALTER TABLE `table_name` ADD FULLTEXT KEY (`column_list`)
-
CREATE, CREATE INDEX可对表增加普通索引或UNIQUE索引。
--例:只能添加这两种索引 CREATE INDEX index_name ON table_name (column_list) CREATE UNIQUE INDEX index_name ON table_name (column_list)
-
索引的删除
DROP INDEX `index_name` ON `talbe_name`
ALTER TABLE `table_name` DROP INDEX `index_name`
-- 这两句都是等价的,都是删除掉table_name中的索引index_name;
ALTER TABLE `table_name` DROP PRIMARY KEY -- 删除主键索引,注意主键索引只能用这种方式删除
索引的查看
show index from tablename;
索引提升速度的直观体现
都说索引可以加快查询速度,但学习中我们并没有直观感受到的机会,此处我们模拟100万条数据,通过这些数据来感受索引对速度的提升,此处测试的方法来源于狂神说,首先建立app_user
表:
CREATE TABLE `app_user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(50) DEFAULT '' COMMENT '用户昵称',
`email` varchar(50) NOT NULL COMMENT '用户邮箱',
`phone` varchar(20) DEFAULT '' COMMENT '手机号',
`gender` tinyint(4) unsigned DEFAULT '0' COMMENT '性别(0:男;1:女)',
`password` varchar(100) NOT NULL COMMENT '密码',
`age` tinyint(4) DEFAULT '0' COMMENT '年龄',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='app用户表'
表中只有一个和业务无关的主键索引id
,在查询中我们并不会用到,但为什么还要建立,这和数据库的索引优化有关,具体可看此文章: MySQL索引背后的数据结构及算法原理 。
之后给这张表插入100万条数据,我们可以对数据进行模拟,让每条数据尽可能的差异化,使用函数式编程,如下:
DROP FUNCTION IF EXISTS mock_data;
DELIMITER $$
CREATE FUNCTION mock_data()
RETURNS INT
BEGIN
DECLARE num INT DEFAULT 1000000;
DECLARE i INT DEFAULT 0;
WHILE i < num DO
INSERT INTO app_user(`name`, `email`, `phone`, `gender`, `password`, `age`)
VALUES(CONCAT('用户', i), '24736743@qq.com', CONCAT('18', FLOOR(RAND()*(999999999-100000000)+100000000)),FLOOR(RAND()*2),UUID(), FLOOR(RAND()*100));
SET i = i + 1;
END WHILE;
RETURN i;
END;
SELECT mock_data();
耗时大约1分钟左右。在测试之前,我们还需要了解到一个关键字:explain
,简单介绍:
EXPLAIN可以帮助开发人员分析SQL问题,explain显示了mysql如何使用索引来处理select语句以及连接表,可以帮助选择更好的索引和写出更优化的查询语句。
详细可看这里: MySQL 索引优化全攻略 ,一定看完再继续。
此时我们就可以进行对比了,假设我们要查询用户名为用户9999
的用户,查询语句如下:
select * from app_user where `name`='用户9999';
最终耗时约1s
,速度显然太慢了,在实际使用中会存在很大的问题。可以用explain
关键字对查询语句进行分析:
EXPLAIN select * from app_user where `name`='用户9999';
得到type
为ALL
,而rows
为992971
。其中ALL
表示连接类型,代表扫描了全表才确定结果。而rows
表示mysql要遍历多少数据才能找到。
之后我们为name
字段增加普通索引:
alter table app_user add index test_index(`name`);
再次查询耗时0.034s
,差距非常大,用explain
分析得到type
为ref
,而rows
为1
。差距非常明显。
组合索引(前缀索引)
除了之前介绍的单列索引,mysql
还可以建立组合索引,即多个单列索引按照顺序组合。而组合索引对其字段进行约束的话又叫前缀索引,并且组合索引满足左前缀原理。
例如对于表:
CREATE TABLE `myIndex` (
`i_testID` INT NOT NULL AUTO_INCREMENT,
`vc_Name` VARCHAR(50) NOT NULL,
`vc_City` VARCHAR(50) NOT NULL,
`i_Age` INT NOT NULL,
`i_SchoolID` INT NOT NULL,
PRIMARY KEY (`i_testID`)
);
我们可以为其建立组合索引:
ALTER TABLE `myIndex` ADD INDEX `name_city_age` (vc_Name(10),vc_City,i_Age);
此处的vc_Name
就是一个前缀约束, 如果索引列长度过长,这种列索引时将会产生很大的索引文件,不便于操作,可以使用前缀索引方式进行索引前缀索引应该控制在一个合适的点( 此处的10就是这个合适的点,因为一般情况下名字的长度不会超过 10,这样会加速索引查询速度,还会减少索引文件的大小,提高 INSERT 的更新速度。)
建立这样的组合索引,其实是相当于分别建立了:
- vc_Name, vc_City, i_Age
- vc_Name, vc_City
- vc_Name
这样的三个组合索引! 而因为需要满足最左前缀原理,因此没有 vc_City,i_Age
这个组合。
关于最左前缀原理的详细解说,强烈建议看这里: MySQL索引背后的数据结构及算法原理 。
什么样的sql不走索引
(摘自)此处
SELECT `sname` FROM `stu` WHERE `age`+10=30;-- 不会使用索引,因为所有索引列参与了计算
SELECT `sname` FROM `stu` WHERE LEFT(`date`,4) <1990; -- 不会使用索引,因为使用了函数运算,原理与上面相同
SELECT * FROM `houdunwang` WHERE `uname` LIKE'后盾%' -- 走索引
SELECT * FROM `houdunwang` WHERE `uname` LIKE "%后盾%" -- 不走索引 %在前面就不走
-- 正则表达式不使用索引,这应该很好理解,所以为什么在SQL中很难看到regexp关键字的原因
-- 字符串与数字比较不使用索引;
CREATE TABLE `a` (`a` char(10));
EXPLAIN SELECT * FROM `a` WHERE `a`="1" -- 走索引
EXPLAIN SELECT * FROM `a` WHERE `a`=1 -- 不走索引
select * from dept where dname='xxx' or loc='xx' or deptno=45 --如果条件中有or,即使其中有条件带索引也不会使用。换言之,就是要求使用的所有字段,都必须建立索引,我们建议大家尽量避免使用or 关键字
-- 如果mysql估计使用全表扫描要比使用索引快,则不使用索引
索引的弊端
索引的创建是有代价的,索引本身会占用存储空间,因此不要盲目的创建索引,只为查询操作频繁的列创建索引,创建索引会使查询操作变得更加快速,但是会降低增加、删除、更新操作的速度,因为执行这些操作的同时会对索引文件进行重新排序或更新。
如果存在大数据需要导入现有的表,而表中以存在影响很大的索引,则此时可以先删除索引,再批量插入数据,最后再添加索引。
一个原则
在使用InnoDB存储引擎时,如果没有特别的需要,请永远使用一个与业务无关的自增字段作为主键。 具体为什么,还是看上面推荐的文章。