对于任何DBMS,索引都是进行优化的最主要的因素。对于少许的数据,没有合适的索引影响不是很大,可是,当随着数据量的增长,性能会急剧降低。html
若是对多列进行索引(组合索引),列的顺序很是重要,MySQL仅能对索引最左边的前缀进行有效的查找。mysql
例如:
假 设存在组合索引it1c1c2(c1,c2),查询语句select * from t1 where c1=1 and c2=2可以使用该索引。查询语句select * from t1 where c1=1也可以使用该索引。可是,查询语句select * from t1 where c2=2不可以使用该索引,由于没有组合索引的引导列,即,要想使用c2列进行查找,必需出现c1等于某值。sql
索引是快速搜索的关键。MySQL索引的创建对于MySQL的高效运行是很重要的。数据库
下面介绍几种常见的MySQL索引类型:数据库设计
在数据库表中,对字段创建索引能够大大提升查询速度。假如咱们建立了一个 mytable表:函数
咱们随机向里面插入了10000条记录,其中有一条:5555, admin。性能
在查找username="admin"的记录优化
时,若是在username上已经创建了索引,MySQL无须任何扫描,即准确可找到该记录。相反,MySQL会扫描全部记录,即要查询10000条记录。spa
索引分单列索引和组合索引。单列索引,即一个索引只包含单个列,一个表能够有多个单列索引,但这不是组合索引。组合索引,即一个索包含多个列。设计
MySQL索引类型包括:
这是最基本的索引,它没有任何限制。它有如下几种建立方式:
建立索引
若是是CHAR,VARCHAR类型,length能够小于字段实际长度;若是是BLOB和TEXT类型,必须指定 length,下同。
修改表结构
建立表的时候直接指定
删除索引的语法:
它与前面的普通索引相似,不一样的就是:索引列的值必须惟一,但容许有空值(设计时候除非有必要,通常不要设为null,能够设空字符串等)。若是是组合索引,则列值的组合必须惟一。它有如下几种建立方式:
建立索引
修改表结构
建立表的时候直接指定
它是一种特殊的惟一索引,不容许有空值。通常是在建表的时候同时建立主键索引:
固然也能够用 ALTER 命令。记住:一个表只能有一个主键。
为了形象地对比单列索引和组合索引,为表添加多个字段:
为了进一步榨取MySQL的效率,就要考虑创建组合索引。就是将 name, city, age建到一个索引里:
建表时,usernname长度为 16,这里用 10。这是由于通常状况下名字的长度不会超过10,这样会加速索引查询速度,还会减小索引文件的大小,提升INSERT的更新速度。
若是分别在 usernname,city,age上创建单列索引,让该表有3个单列索引,查询时和上述的组合索引效率也会大不同,远远低于咱们的组合索引。虽然此时有了三个索引,但MySQL只能用到其中的那个它认为彷佛是最有效率的单列索引。
创建这样的组合索引,实际上是至关于分别创建了下面三组组合索引:
以上的相关内容就是对MySQL索引类型的部份内容的介绍,望你能有所收获。
使用索引的注意事项
使用索引时,有如下一些技巧和注意事项:
索引不会包含有NULL值的列
只要列中包含有NULL值都将不会被包含在MySQL索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。因此咱们在数据库设计时不要让字段的默认值为NULL。
使用短索引
对串列进行索引,若是可能应该指定一个前缀长度。例如,若是有一个CHAR(255)的列,若是在前10个或20个字符内,多数值是唯一的,那么就不要对整个列进行索引。短索引不只能够提升查询速度并且能够节省磁盘空间和I/O操做。
索引列排序
MySQL查询只使用一个索引,所以若是where子句中已经使用了索引的话,那么order by中的列是不会使用索引的。所以数据库默认排序能够符合要求的状况下不要使用排序操做;尽可能不要包含多个列的排序,若是须要最好给这些列建立复合索引。
like语句操做
通常状况下不鼓励使用like操做,若是非使用不可,如何使用也是一个问题。like “%aaa%” 不会使用MySQL索引而like “aaa%”可使用索引。
不要在列上进行运算
将在每一个行上进行运算,这将致使索引失效而进行全表扫描,所以咱们能够改为
不使用NOT IN和<>操做
索引优化 http://blog.codinglabs.org/articles/theory-of-mysql-index.html
最左前缀原理与相关优化 高效使用索引的首要条件是知道什么样的查询会使用到索引,这个问题和B+Tree中的“最左前缀原理”有关,下面经过例子说明最左前缀原理。 这里先说一下联合索引的概念。在上文中,咱们都是假设索引只引用了单个的列,实际上,MySQL中的索引能够以必定顺序引用多个列,这种索引叫作联合索引,通常的,一个联合索引是一个有序元组<a1, a2, …, an>,其中各个元素均为数据表的一列,实际上要严格定义索引须要用到关系代数,可是这里我不想讨论太多关系代数的话题,由于那样会显得很枯燥,因此这里就再也不作严格定义。另外,单列索引能够当作联合索引元素数为1的特例。 以employees.titles表为例,下面先查看其上都有哪些索引: SHOW INDEX FROM employees.titles; +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Null | Index_type | +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+ | titles | 0 | PRIMARY | 1 | emp_no | A | NULL | | BTREE | | titles | 0 | PRIMARY | 2 | title | A | NULL | | BTREE | | titles | 0 | PRIMARY | 3 | from_date | A | 443308 | | BTREE | | titles | 1 | emp_no | 1 | emp_no | A | 443308 | | BTREE | +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+ 从结果中能够到titles表的主索引为<emp_no, title, from_date>,还有一个辅助索引<emp_no>。为了不多个索引使事情变复杂(MySQL的SQL优化器在多索引时行为比较复杂),这里咱们将辅助索引drop掉: ALTER TABLE employees.titles DROP INDEX emp_no; 这样就能够专心分析索引PRIMARY的行为了。
状况一:全列匹配。 EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title='Senior Engineer' AND from_date='1986-06-26'; +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+ | 1 | SIMPLE | titles | const | PRIMARY | PRIMARY | 59 | const,const,const | 1 | | +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+ 很明显,当按照索引中全部列进行精确匹配(这里精确匹配指“=”或“IN”匹配)时,索引能够被用到。这里有一点须要注意,理论上索引对顺序是敏感的,可是因为MySQL的查询优化器会自动调整where子句的条件顺序以使用适合的索引,例如咱们将where中的条件顺序颠倒: EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26' AND emp_no='10001' AND title='Senior Engineer'; +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+ | 1 | SIMPLE | titles | const | PRIMARY | PRIMARY | 59 | const,const,const | 1 | | +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+ 效果是同样的。 状况二:最左前缀匹配。 EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001'; +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+ | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | | +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+ 当查询条件精确匹配索引的左边连续一个或几个列时,如<emp_no>或<emp_no, title>,因此能够被用到,可是只能用到一部分,即条件所组成的最左前缀。上面的查询从分析结果看用到了PRIMARY索引,可是key_len为4,说明只用到了索引的第一列前缀。
状况三:查询条件用到了索引中列的精确匹配,可是中间某个条件未提供。 EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26'; +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+ | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | Using where | +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+ 此时索引使用状况和状况二相同,由于title未提供,因此查询只用到了索引的第一列,然后面的from_date虽然也在索引中,可是因为title不存在而没法和左前缀链接,所以须要对结果进行扫描过滤from_date(这里因为emp_no惟一,因此不存在扫描)。若是想让from_date也使用索引而不是where过滤,能够增长一个辅助索引<emp_no, from_date>,此时上面的查询会使用这个索引。除此以外,还可使用一种称之为“隔离列”的优化方法,将emp_no与from_date之间的“坑”填上。 首先咱们看下title一共有几种不一样的值: SELECT DISTINCT(title) FROM employees.titles; +--------------------+ | title | +--------------------+ | Senior Engineer | | Staff | | Engineer | | Senior Staff | | Assistant Engineer | | Technique Leader | | Manager | +--------------------+ 只有7种。在这种成为“坑”的列值比较少的状况下,能够考虑用“IN”来填补这个“坑”从而造成最左前缀: EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title IN ('Senior Engineer', 'Staff', 'Engineer', 'Senior Staff', 'Assistant Engineer', 'Technique Leader', 'Manager') AND from_date='1986-06-26'; +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 59 | NULL | 7 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 此次key_len为59,说明索引被用全了,可是从type和rows看出IN实际上执行了一个range查询,这里检查了7个key。看下两种查询的性能比较: SHOW PROFILES; +----------+------------+-------------------------------------------------------------------------------+ | Query_ID | Duration | Query | +----------+------------+-------------------------------------------------------------------------------+ | 10 | 0.00058000 | SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26'| | 11 | 0.00052500 | SELECT * FROM employees.titles WHERE emp_no='10001' AND title IN ... | +----------+------------+-------------------------------------------------------------------------------+ “填坑”后性能提高了一点。若是通过emp_no筛选后余下不少数据,则后者性能优点会更加明显。固然,若是title的值不少,用填坑就不合适了,必须创建辅助索引。 状况四:查询条件没有指定索引第一列。 EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26'; +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+ | 1 | SIMPLE | titles | ALL | NULL | NULL | NULL | NULL | 443308 | Using where | +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+ 因为不是最左前缀,索引这样的查询显然用不到索引。 状况五:匹配某列的前缀字符串。 EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title LIKE 'Senior%'; +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 56 | NULL | 1 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 此时能够用到索引,可是若是通配符不是只出如今末尾,则没法使用索引。(原文表述有误,若是通配符%不出如今开头,则能够用到索引,但根据具体状况不一样可能只会用其中一个前缀) 状况六:范围查询。 EXPLAIN SELECT * FROM employees.titles WHERE emp_no < '10010' and title='Senior Engineer'; +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 4 | NULL | 16 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 范围列能够用到索引(必须是最左前缀),可是范围列后面的列没法用到索引。同时,索引最多用于一个范围列,所以若是查询条件中有两个范围列则没法全用到索引。 EXPLAIN SELECT * FROM employees.titles WHERE emp_no < '10010' AND title='Senior Engineer' AND from_date BETWEEN '1986-01-01' AND '1986-12-31'; +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 4 | NULL | 16 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 能够看到索引对第二个范围索引无能为力。这里特别要说明MySQL一个有意思的地方,那就是仅用explain可能没法区分范围索引和多值匹配,由于在type中这二者都显示为range。同时,用了“between”并不意味着就是范围查询,例以下面的查询: EXPLAIN SELECT * FROM employees.titles WHERE emp_no BETWEEN '10001' AND '10010' AND title='Senior Engineer' AND from_date BETWEEN '1986-01-01' AND '1986-12-31'; +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 59 | NULL | 16 | Using where | +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+ 看起来是用了两个范围查询,但做用于emp_no上的“BETWEEN”实际上至关于“IN”,也就是说emp_no实际是多值精确匹配。能够看到这个查询用到了索引所有三个列。所以在MySQL中要谨慎地区分多值匹配和范围匹配,不然会对MySQL的行为产生困惑。 状况七:查询条件中含有函数或表达式。 很不幸,若是查询条件中含有函数或表达式,则MySQL不会为这列使用索引(虽然某些在数学意义上可使用)。例如: EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND left(title, 6)='Senior'; +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+ | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | Using where | +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+ 虽然这个查询和状况五中功能相同,可是因为使用了函数left,则没法为title列应用索引,而状况五中用LIKE则能够。再如: EXPLAIN SELECT * FROM employees.titles WHERE emp_no - 1='10000'; +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+ | 1 | SIMPLE | titles | ALL | NULL | NULL | NULL | NULL | 443308 | Using where | +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+ 显然这个查询等价于查询emp_no为10001的函数,可是因为查询条件是一个表达式,MySQL没法为其使用索引。看来MySQL尚未智能到自动优化常量表达式的程度,所以在写查询语句时尽可能避免表达式出如今查询中,而是先手工私下代数运算,转换为无表达式的查询语句。 索引选择性与前缀索引 既然索引能够加快查询速度,那么是否是只要是查询语句须要,就建上索引?答案是否认的。由于索引虽然加快了查询速度,但索引也是有代价的:索引文件自己要消耗存储空间,同时索引会加剧插入、删除和修改记录时的负担,另外,MySQL在运行时也要消耗资源维护索引,所以索引并非越多越好。通常两种状况下不建议建索引。 第一种状况是表记录比较少,例如一两千条甚至只有几百条记录的表,不必建索引,让查询作全表扫描就行了。至于多少条记录才算多,这个我的有我的的见解,我我的的经验是以2000做为分界线,记录数不超过 2000能够考虑不建索引,超过2000条能够酌情考虑索引。 另外一种不建议建索引的状况是索引的选择性较低。所谓索引的选择性(Selectivity),是指不重复的索引值(也叫基数,Cardinality)与表记录数(#T)的比值: Index Selectivity = Cardinality / #T 显然选择性的取值范围为(0, 1],选择性越高的索引价值越大,这是由B+Tree的性质决定的。例如,上文用到的employees.titles表,若是title字段常常被单独查询,是否须要建索引,咱们看一下它的选择性: SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles; +-------------+ | Selectivity | +-------------+ | 0.0000 | +-------------+ title的选择性不足0.0001(精确值为0.00001579),因此实在没有什么必要为其单独建索引。 有一种与索引选择性有关的索引优化策略叫作前缀索引,就是用列的前缀代替整个列做为索引key,当前缀长度合适时,能够作到既使得前缀索引的选择性接近全列索引,同时由于索引key变短而减小了索引文件的大小和维护开销。下面以employees.employees表为例介绍前缀索引的选择和使用。 从图12能够看到employees表只有一个索引<emp_no>,那么若是咱们想按名字搜索一我的,就只能全表扫描了: EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido'; +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ | 1 | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL | 300024 | Using where | +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+ 若是频繁按名字搜索员工,这样显然效率很低,所以咱们能够考虑建索引。有两种选择,建<first_name>或<first_name, last_name>,看下两个索引的选择性: SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.0042 | +-------------+ SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.9313 | +-------------+ <first_name>显然选择性过低,<first_name, last_name>选择性很好,可是first_name和last_name加起来长度为30,有没有兼顾长度和选择性的办法?能够考虑用first_name和last_name的前几个字符创建索引,例如<first_name, left(last_name, 3)>,看看其选择性: SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.7879 | +-------------+ 选择性还不错,但离0.9313仍是有点距离,那么把last_name前缀加到4: SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees; +-------------+ | Selectivity | +-------------+ | 0.9007 | +-------------+ 这时选择性已经很理想了,而这个索引的长度只有18,比<first_name, last_name>短了接近一半,咱们把这个前缀索引 建上: ALTER TABLE employees.employees ADD INDEX `first_name_last_name4` (first_name, last_name(4)); 此时再执行一遍按名字查询,比较分析一下与建索引前的结果: SHOW PROFILES; +----------+------------+---------------------------------------------------------------------------------+ | Query_ID | Duration | Query | +----------+------------+---------------------------------------------------------------------------------+ | 87 | 0.11941700 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' | | 90 | 0.00092400 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' | +----------+------------+---------------------------------------------------------------------------------+ 性能的提高是显著的,查询速度提升了120多倍。 前缀索引兼顾索引大小和查询速度,可是其缺点是不能用于ORDER BY和GROUP BY操做,也不能用于Covering index(即当索引自己包含查询所需所有数据时,再也不访问数据文件自己)。
补充该节中的"范围查询"说明:
Mysql对于范围查询range分的优化为单字段优化和多元素优化:
单元素索引范围条件的定义以下:
· 对于BTREE和HASH索引,当使用=、<=>、
IN
、
IS
NULL
或者
IS
NOT
NULL
操做符时,关键元素与常量值的比较关系对应一个范围条件,即const范围。
· 对于BTREE索引,当使用>、<、>=、<=、
BETWEEN
、!=或者<>,或者
LIKE
'pattern'
(其中
'pattern'
不以通配符开始)操做符时,关键元素与常量值的比较关系对应一个范围条件。
· 对于全部类型的索引,多个范围条件结合
OR
或
AND
则产生一个范围条件。
前面描述的“常量值”系指:
· 查询字符串中的常量
· 同一联接中的const或system表中的列
· 无关联子查询的结果
· 彻底从前面类型的子表达式组成的表达式
|
多元素索引的范围条件:
1.
----------------
对于BTREE索引,区间能够对结合
AND
的条件有用,其中每一个条件用一个常量值经过=、<=>、
IS
NULL
、>、<、>=、<=、!=、<>、
BETWEEN
或者
LIKE
'pattern'
(其中
'pattern'
不以通配符开头)比较一个关键元素。区间能够足够长以肯定一个包含全部匹配条件(或若是使用<>或!=,为两个区间)的记录的单一的关键元组。例如,对于条件:
key_part1 =
'foo'
AND
key_part2 >= 10
AND
key_part3 > 10
2.
-----------
对于HASH索引,可使用包含相同值的每一个区间。
key_part1 cmp const1
AND
key_part2 cmp const2
AND
...
AND
key_partN cmp constN;
这里,const1,const2,...为常量,cmp是=、<=>或者
IS
NULL
比较操做符之一,条件包括全部索引部分。(也就是说,有N 个条件,每个对应N-元素索引的每一个部分)。
3.
----------
若是包含区间内的一系列记录的条件结合使用
OR
,则造成包括一系列包含在区间并集的记录的一个条件。若是条件结合使用了
AND
,则造成包括一系列包含在区间交集内的记录的一个条件。例如,对于两部分索引的条件:
(key_part1 = 1
AND
key_part2 < 2)
OR
(key_part1 > 5)
区间为:
(1, -inf) < (key_part1, key_part2) < (1, 2)
(5, -inf) < (key_part1, key_part2)
|
Mysql检索时间查询 (版本要求: 5.0.37或以上)
开启profile mysql> set profiling=1; Query OK, 0 rows affected (0.00 sec) eg: mysql> select * from test_1; mysql> show profiles; +----------+------------+----------------------+ | Query_ID | Duration | Query | +----------+------------+----------------------+ | 1 | 0.84718100 | select * from test_1 | +----------+------------+----------------------+ 1 row in set (0.00 sec)
转自:http://www.cnblogs.com/mailingfeng/archive/2012/09/26/2704344.html