索引
索引
一、索引的创建、删除
- UNIQUE(唯一索引):不可以出现相同的值,可以有NULL值
- INDEX(普通索引):允许出现相同的索引内容
- PROMARY KEY(主键索引):不允许出现相同的值
- fulltext index(全文索引):可以针对值中的某个单词,但效率确实不敢恭维
- 组合索引:实质上是将多个字段建到一个索引里,列值的组合必须唯一
根据《阿里巴巴Java开发手册》里的mysql规约,唯一索引建议命名为uk_字段名,普通索引名则为idx_字段名。(uk_即unique key; idx_即index的简称)。
1.使用ALTER TABLE语句创建索性
ALTER TABLE 表名 ADD 索引类型 (unique,primary key,fulltext,index)[索引名](字段名)
//普通索引
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) ;
2.使用CREATE INDEX语句对表增加索引
CREATE INDEX可用于对表增加普通索引或UNIQUE索引,可用于建表时创建索引。
CREATE INDEX index_name ON table_name(username(length));
如果是CHAR,VARCHAR类型,length可以小于字段实际长度;如果是BLOB和TEXT类型,必须指定 length。
//只能添加这两种索引;
CREATE INDEX index_name ON table_name (column_list)
CREATE UNIQUE INDEX index_name ON table_name (column_list)
table_name、index_name和column_list具有与ALTER TABLE语句中相同的含义,索引名不可选。另外,不能用CREATE INDEX语句创建PRIMARY KEY索引。
3.删除索引
删除索引可以使用ALTER TABLE或DROP INDEX语句来实现。DROP INDEX可以在ALTER TABLE内部作为一条语句处理,其格式如下:
drop index index_name on table_name ;
alter table table_name drop index index_name ;
alter table table_name drop primary key ;
4.组合索引与前缀索引
在这里要指出,组合索引和前缀索引是对建立索引技巧的一种称呼,并不是索引的类型。为了更好的表述清楚,建立一个demo表如下。
create table USER_DEMO
(
ID int not null auto_increment comment '主键',
LOGIN_NAME varchar(100) not null comment '登录名',
PASSWORD varchar(100) not null comment '密码',
CITY varchar(30) not null comment '城市',
AGE int not null comment '年龄',
SEX int not null comment '性别(0:女 1:男)',
primary key (ID)
);
为了进一步榨取mysql的效率,就可以考虑建立组合索引,即将LOGIN_NAME,CITY,AGE建到一个索引里:
ALTER TABLE USER_DEMO ADD INDEX name_city_age (LOGIN_NAME(16),CITY,AGE);
建立这样的组合索引,就相当于分别建立如下三种组合索引:
LOGIN_NAME,CITY,AGE
LOGIN_NAME,CITY
LOGIN_NAME
**为什么没有CITY,AGE等这样的组合索引呢?**这是因为mysql组合索引“最左前缀"的结果。简单的理解就是只从最左边的开始组合,并不是只要包含这三列的查询都会用到该组合索引。也就是说name_city_age(LOGIN_NAME(16),CITY,AGE)从左到右进行索引,如果没有左前索引,mysql不会执行索引查询。
如果索引列长度过长,这种列索引时将会产生很大的索引文件,不便于操作,可以使用前缀索引方式进行索引,前缀索引应该控制在一个合适的点,控制在0.31黄金值即可(大于这个值就可以创建)。
SELECT COUNT(DISTINCT(LEFT(`title`,10)))/COUNT(*) FROM Arctic; -- 这个值大于0.31就可以创建前缀索引,Distinct去重复
ALTER TABLE `user` ADD INDEX `uname`(title(10)); -- 增加前缀索引SQL,将人名的索引建立在10,这样可以减少索引文件大小,加快索引查询速度
二、索引的使用及注意事项
Explain select * from user where id=1;
尽量避免这些不走索引的sql:
SELECT name,phone FROM `user` WHERE `age`+10=30; -- 不会使用索引,因为所有索引列参与了计算
SELECT name,phone FROM `user` WHERE LEFT(`date`,4) <1990; -- 不会使用索引,因为使用了函数运算,原理与上面相同
SELECT * FROM `user` WHERE `name` LIKE'后盾%' -- 走索引
SELECT * FROM `user` WHERE `name` 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估计使用全表扫描要比使用索引快,则不使用索引
三、使用索引时,有一些技巧
- 索引不会包含有NULL的列
只要列中包含有NULL值,都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此符合索引就是无效的。
- 使用短索引
对串列进行索引,如果可以就应该指定一个前缀长度。例如,如果有一个char(255)的列,如果在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
- 索引列排序
mysql一张表查询只能用到一个索引。因此如果where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作,尽量不要包含多个列的排序,如果需要最好给这些列建复合索引。这一点是很多程序猿容易忽略的,如where子句的字段建了索引,排序的字段建了索引,但是分开建的,以为会走索引,其实这样的话排序的字段不会使用索引的,除非建复合索引,切记。
不使用NOT IN 、<>、!=操作,但<,<=,=,>,>=,BETWEEN,IN是可以用到索引的。
在where和join中出现的列需要建立索引。
where的查询条件里有不等号(where column != ...),mysql将无法使用索引。
在join操作中(需要从多个数据表提取数据时),mysql只有在主键和外键的数据类型相同时才能使用索引,否则即使建立了索引也不会使用。这一点很容易忽略,切记,切记,切记!
组合索引中有多个字段,其中一个字段是有范围判断,则需将此字段在最后面
ALTER TABLE USER_DEMO ADD INDEX name_age (NAME,AGE);
字符集字段比较,UTF8与UTF-BIN联合查询是不能走索引的
如某张表的order_no字段类型为varchar(50),另一张表的order_no字段类型为varchar(50) COLLATE utf8_BIN。则此时联合查询时不能走索引的,切记。 即两张表的字段类型如下:
`order_no` varchar(50) COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT '订单号';
`order_no` varchar(50) NOT NULL DEFAULT '' COMMENT '订单号';
避免表字段为null,建议设置默认值(如int类型设置默认值为0),这样在索引查询上,效率会高很多。
- 关于order by的索引问题重点说下:
1.无条件查询如果只有order by create_time,即便create_time上有索引,也不会使用到。因为优化器认为走二级索引再去回表成本比全表扫描排序更高,所以选择走权标扫描。 2.无条件查询但是order by create_time limit m,如果m值较小,是可以走索引的。 因为优化器认为根据索引有序性去回表查数据,然后得到m条数据,就可以终止循环, 3.那么成本比全表扫描小,则选择走二级索引。即便没有二级索引,mysql针对order by limit也做了优化,采用堆排序。 4.order by排序分为file sort和index,index的效率更高。但以下情况不会使用index排序: 1)检查的行数过多,并且没有使用覆盖索引 2)使用了多个索引,mysql一次只会采用一个索引 3)where和order by使用了不同的索引,与上一条类似 4)order by中加入了非索引列,且非索引列不在where中 5)当使用left join,使用右边的表字段排序
- explain
MariaDB [foo]> EXPLAIN SELECT COUNT(*) FROM user WHERE name > 'A' AND name < 'Z';
+------+-------------+-------+-------+---------------+-----------+---------+------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+------+-------------+-------+-------+---------------+-----------+---------+------+------+--------------------------+
| 1 | SIMPLE | user | range | user_name | user_name | 1022 | NULL | 686 | Using where; Using index |
+------+-------------+-------+-------+---------------+-----------+---------+------+------+--------------------------+
1 row in set (0.000 sec)
对于每一列的解释如下:
id: 语句的id
select_type: 查询语句的类型,可选值见下图。
table: 所查询的表
partitions: 所涉及到的分区,如果不是NDB集群,没有这个字段
type: 查询数据的类型,类型见 这里 或 我的这篇博客
possible_keys: 可能会使用的索引
key: 实际使用的索引
key_len: 使用到索引的长度
ref: 和索引做比较的列
rows: MySQL预估有多少行需要处理
filtered: 大概被过滤的行数的百分比
Extra: 其它信息
了解了查询如何被MySQL执行,以及为什么慢之后,就会有对策出来,一般要么是加新的索引,要么是MySQL选错了索引。如果是选错了 索引,那么我们就需要使用索引提示。语句如下:
SELECT * FROM table1 USE INDEX (col1_index,col2_index)
WHERE col1=1 AND col2=2 AND col3=3;
SELECT * FROM table1 IGNORE INDEX (col3_index)
WHERE col1=1 AND col2=2 AND col3=3;