Mysql数据库索引
今天,我们来讲讲Mysql数据库的索引的一些东西,想必大家都知道索引能干吗?必然是查找数据表的时候,查找的速度快啊,尤其是那些几百万行的数据库,不建立索引,你是想考验用户的耐心吗?Mysql有多种存储引擎,必然,每种引擎的创建索引也是不尽相同的,比如MyISAM是B+Tree非聚集索引(secondary index)而InnoDB是B+Tree非聚集索引(clustered index),(此处有知识点:聚集索引和聚簇索引是同一个概念,只是中文不同的翻译)。下面,我打算从以下几个角度来讲解下数据库索引:
目录
1、为什么需要建立索引?
2、索引的种类以及如何建立?
3、索引的使用
4、什么情况下查询语句根本没有用到索引呢?
5、如何判断索引是否被用到?
6、使用索引来进行排序
创建本文中需要使用的表:
mysql> DROP TABLE IF EXISTS user_test;
mysql> CREATE TABLE user_test ( id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> user_name varchar(30) NOT NULL,
-> sex bit(1) NOT NULL DEFAULT b'1',
-> city varchar(50) NOT NULL,
-> age int NOT NULL)
-> ENGINE=InnoDB DEFAULT CHARSET=utf8;
mysql> explain user_test;
插入数据:
mysql> INSERT INTO user_test (user_name,sex,city,age) values ('张三',b'1','广州',26), ('王五',b'0','云南',28);
mysql> SELECT * FROM user_test;
1、为什么建立索引?
- 可以大大加快数据的检索速度,这也是创建索引的最主要的原因
- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。
- 在使用分组(GROUP BY)和排序子句(ORDER BY)进行数据检索时,同样可以显著减少查询中分组和排序的时间。
- 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
- 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。
是不是觉得太理论了?没问题,下面我会一个个解释给你们看。
2、索引的种类以及如何建立?
创建索引的3中方式:
- 修改表结构的方式添加索引:ALTER TABLE...
- 直接创建索引:CREATE INDEX...
- 创建表的时候同时创建索引: INDEX...
如何删除索引呢?
DROP INDEX index_name ON table_name;
2.1 主键索引
- ALTER TABLE table_name ADD PRIMARY KEY index_name ('column');
- CREATE PRINARY KEY INDEX index_name ON table_name(column(length));
- PRINARY KEY index_name(column(length));
2.2 唯一性索引
- ALTER TABLE table_name ADD UNIQUE index_name ('column');
- CREATE UNIQUE INDEX index_name ON table_name(column(length));
- UNIQUE index_name(column(length));
2.3 普通索引
- ALTER TABLE table_name ADD INDEX index_name ('column');
- CREATE INDEX index_name ON table_name(column(length));
- INDEX index_name(column(length));
2.4 全文索引
- ALTER TABLE table_name ADD FULLTEST index_name ('column');
- CREATE FULLTEXT INDEX index_name ON table_name(column(length));
- FULLTEXT (column);
2.5 组合索引
ALTER TABLE table_name ADD INDEX index_name ('column1',‘column2’,...);
此处有知识点:主键,也就是user_test表中的id,表中用primary key修饰的,全表就一个主键,主键的值绝对没有重复,当然也不允许为空。
唯一性:你可以通过unique关键字设置表中某一列是唯一的,唯一也就代表,这一列中的所有的值都是独一无二的,绝对不允许重复,而且不能为空。
所以,主键是唯一的,但是唯一的并不一定是主键。
3、索引的使用
上面我们讲了如何建立索引,那建立后该怎么使用呢?
比如:我们建立个联合索引:ALTER TABLE user_test ADD INDEX idx_user (user_name , city , age);
此处有知识点:索引的建立是要有依据的,不要随便建立索引,毕竟创建索引和维护索引要耗费时间,索引也是占用物理内存的,下面会详细讲解。最好是你频繁的需要根据某一列或几列进行查询的时候,最好建立索引,比如:WHERE 后面就是你需要查找的关键字,这就是你需要创建索引。
既然建立的索引,那我们查找的时候,怎么使用呢?难道每次都要这样写:SELECT * FROM user_test WHERE username='张三' AND city='广州' AND age=26; 这样写的也太全了,能不能少些点,或者有没有办法简化一下,当然,在简化的时候有没有什么规则呢?
以上面这个索引为例,什么样的查询是有效的呢?
有效查询:
(1)全值匹配
全值匹配指的是和索引中的所有列进行匹配,如:以上面创建的索引为例,在where条件后可同时查询(user_name,city,age)为条件的数据。
此处有知识点:与where后查询条件的顺序无关,这里是很多同学容易误解的一个地方.
SQL语句:
SELECT * FROM user_test WHERE user_name='张三' AND city='广州' AND age=26;
或SELECT * FROM user_test WHERE user_name='张三' AND age=26 AND city='广州';
或 SELECT * FROM user_test WHERE age=26 AND city='广州 AND user_name='张三'';
这3种的查询结果是一毛一样。
(2)最左前缀匹配
联合查询包含多个列,那么在查询的时候一定要把所有的列的条件都写上去吗?少写几个行不行?答案是可以的,但是你要小心你少写的那一列。
匹配最左前缀是指优先匹配最左索引列,也就是你创建联合索引的第一列,如上面索引的第一列就是:user_name。所以你可以这样写:
SQL语句:
SELECT * FROM user_test WHERE user_name='张三' AND city='广州' ;
或SELECT * FROM user_test WHERE user_name='张三' AND age=26;
或SELECT * FROM user_test WHERE city='广州' AND user_name='张三' ;
但是,这样写查询条件是无法使用索引的,只能全文搜索,你不可以这样写:
SQL语句:
SELECT * FROM user_test WHERE city='广州' AND age=26;
或SELECT * FROM user_test WHERE age=26;
或SELECT * FROM user_test WHERE city='广州' ;
原因是因为你建立索引是(user_name , city , age),所以查询条件必须是包含user_name 这一最左列。
再重申一遍:满足最左前缀查询条件的顺序与索引列的顺序无关,如:(city, user_name)、(age, city, user_name)。
(3)范围值匹配
如:查询用户名以“张”开头的所有用户,这里使用了索引的第一列,
SELECT * FROM user_test WHERE user_name LIKE'张%';
但是你不能这样写:
SELECT * FROM user_test WHERE user_name LIKE'%三';
也就是说,索引必须包含最左列,最左列的前缀必须是确定的查询。
(4)前缀索引匹配
有时候被建立索引的列里面的值很长,这会增加索引的存储空间以及降低索引的效率,一种策略是可以使用hash索引,(此处有知识点:hash索引是定值索引,无法进行范围查询)还有一种就是可以使用前缀索引,前缀索引是选择字符列的前n个字符作为索引,这样可以大大节约索引空间,从而提高索引效率。
如何确定你创建的索引列的长度呢?
首先,前缀索引要选择足够长的前缀以保证高的匹配度,同时又不能太长,我们可以通过以下方式来计算出合适的前缀索引的选择长度值:
SELECT COUNT(DISTINCT index_column )/COUNT(*) FROM table_name; ——index_column 代表你要添加索引的列。
通过以上方式来计算出前缀索引的选择性比值,比值越高说明索引的效率也就越高效。这是计算某一列整体的。如果你想计算某一列的前几个字符匹配度,那么:
SELECT COUNT(DISTINCT LEFT(index_column,1) )/COUNT(*) FROM table_name; ——表示index_column 这一列取值的第一个字符匹配度是多少,
SELECT COUNT(DISTINCT LEFT(index_column,2) )/COUNT(*) FROM table_name; ——表示index_column 这一列取值的第二个字符匹配度是多少,
通过以上语句逐步找到最接近于计算整个列的前缀索引的选择性比值,那么就可以使用对应的字符截取长度来做前缀索引了。
此时,我们就可以创建前缀索引了:
ALTER TABLE table_name ADD INDEX index_name (index_column(length));
注意:前缀索引是一种能使索引更小,更快的有效办法,但是MySql无法使用前缀索引做ORDER BY 和 GROUP BY以及使用前缀索引做覆盖扫描。
4、什么情况下查询语句根本没有用到索引呢?
既然我们已经创建了索引,那查询的时候,什么样的SQL语句才算是有效,什么样的SQL语句是无效的呢?
除了上述的全值索引,最左前缀索引,前缀索引提到的有效查询和无效查询外,下面几种情况可能会导致无效索引:
- 索引列不能是表达式的一部分,也不能作为函数的参数,否则无法使用索引查询。
如:SELECT * FROM user_test WHERE user_name = concat(user_name, 'fei');
或SELECT * FROM user_test WHERE age+=1;
这样的语句是无法使用到索引的。
- 如果where查询条件中有某个列的范围查询,则其右边的所有列都无法使用索引优化查询。
如:SELECT * FROM user_test WHERE user_name = ‘张三’ AND city='广%' AND age=26;
age这一列就会被忽略的。
- !=或者<>(不等于),会导致不走索引。
所以在查询语句中,不要对null判断,这样会导致全表查询,效率极其低。
5、如何判断索引是否被用到?
你以为你用了你创建的索引进行的查询,然而其实你并没有,。
那么怎么可以看到表中创建的索引呢?两种方式:
SHOW INDEX FROM table_name;
或SHOW KEYS FROM table_name;
如何查看索引是否生效呢?
EXPLAIN +你的SQL语句;
table:显示这一行的数据是关于哪张表的
type:这是重要的列,显示连接使用了何种类型。从最好到最差的连接类型为const、eq_reg、ref、range、indexhe和ALL
possible_keys:显示可能应用在这张表中的索引。如果为空,没有可能的索引。可以为相关的域从WHERE语句中选择一个合适的语句
key: 实际使用的索引。如果为NULL,则没有使用索引。很少的情况下,mysql会选择优化不足的索引。这种情况下,可以在SELECT语句中使用USE INDEX(indexname)来强制使用一个索引或者用IGNORE INDEX(indexname)来强制MYSQL忽略索引
key_len:使用的索引的长度。在不损失精确性的情况下,长度越短越好
ref:显示索引的哪一列被使用了,如果可能的话,是一个常数
rows:MYSQL认为必须检查的用来返回请求数据的行数
Extra:关于MYSQL如何解析查询的额外信息。将在表4.3中讨论,但这里可以看到的坏的例子是Using temporary和Using filesort,意思MYSQL根本不能使用索引,结果是检索会很慢
从这张图的key,ref可以看见,三列索引生效。
上图表示用到了索引,但只有一列。
上图表示没有用到索引。
6、使用索引来进行排序
6.1 先讲解下什么是覆盖索引,下面要用到这个概念:
如果一个索引(如:组合索引)中包含所有要查询的字段的值,那么就称之为覆盖索引,如:
SELECT user_name,city,age FROM user_test WHERE city='广州' AND age>26;
因为要查询的字段(user_name, city, age)都包含在组合索引的索引列中,所以就使用了覆盖索引查询,查看是否使用了覆盖索引可以通过执行EXPLAIN中的Extra中的值为Using index则证明使用了覆盖索引,覆盖索引可以极大的提高访问性能。
回到主题,
在排序操作中如果能使用到索引来排序,那么可以极大的提高排序的速度,要使用索引来排序需要满足以下两点即可。
- ORDER BY子句后的列顺序要与组合索引的列顺序一致,且所有排序列的排序方向(正序/倒序)需一致。
- 所查询的字段值需要包含在索引列中,及满足覆盖索引
通过例子来具体分析
在user_test表上创建一个组合索引:
ALTER TABLE user_test ADD INDEX index_user(user_name,city,age);
可以使用到索引排序的案例:
SELECT user_name,city,age FROM user_test ORDER BY user_name;
SELECT user_name,city,age FROM user_test ORDER BY user_name,city;
SELECT user_name,city,age FROM user_test ORDER BY user_name DESC,city DESC;
SELECT user_name,city,age FROM user_test ORDER BY user_name='张三' ORDER BY city;
第4个比较特殊一点,如果where查询条件为索引列的第一列,且为常量条件,那么ORDER BY也可以使用到索引。
说一下无法使用索引排序的:
1、sex不在索引列中
SELECT user_name,city,age FROM user_test ORDER BY user_name,sex;
2、排序列的方向不一致
SELECT user_name,city,age FROM user_test ORDER BY user_name ASC,city DESC;
3、所要查询的字段列sex没有包含在索引列中
SELECT user_name,city,age,sex FROM user_test ORDER BY user_name;
4、where查询条件后的user_name为范围查询,所以无法使用到索引的其他列
SELECT user_name,city,age FROM user_test WHERE user_name LIKE '张%' ORDER BY city;
5、多表连接查询时,只有当ORDER BY后的排序字段都是第一个表中的索引列(需要满足以上索引排序的两个规则)时,方可使用索引排序。如:再创建一个用户的扩展表user_test_ext,并建立uid的索引。
mysql> DROP TABLE IF EXISTS user_test_ext;
mysql> CREATE TABLE user_test_ext ( id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
-> user_name varchar(30) NOT NULL,
-> uid int NOT NULL,
-> u_password varchar(64) NOT NULL,
-> ENGINE=InnoDB DEFAULT CHARSET=utf8;
->ALTER TABLE user_test_ext ADD INDEX index_user_ext(uid);
走索引排序:
SELECT user_name,city,age FROM user_test u LEFT JOIN user_test_ext ue ON u.id=ue.uid ORDER BY u ;
不走索引排序:
SELECT user_name,city,age FROM user_test u LEFT JOIN user_test_ext ue ON u.id=ue.uid ORDER BY ue;
参考文献: