|
|
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能: \' n1 L$ ]9 u- F/ H- B0 g
有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成+ k1 \% U. q, q# y) {0 O, f3 t( I
一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列1 g8 R* E5 K5 I( C0 e" m- k& k
在InnoDB中,只有事务提交后才会解锁# U T* k) N# J- @
' X) d& G" G$ _* ] x索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只
# e0 ^3 F! H' ~# O( m能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。
. q( Y+ D& K* F7 W+ U/ R
, l" M$ l: g. j! Z z" D6 kB-TREE
$ |" N; g1 {* f. u8 d1 H能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找
" i, l9 f, h: R9 m3 E* G; J使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。+ m: }% I \3 N" a
8 }" @5 g6 U" e9 {' J- CREATE TABLE People(
- Z5 g# X- J5 N% k - last_name varchar(50) not null* j4 Z& d2 _5 Q7 C
- first_name varchar(50) not null& l0 H( P$ {% D# G; D4 o
- dob date not null
2 V* D1 J* _( _2 v7 a; Y4 u2 b - gende enum('m','f') not null
. Z- X/ [3 n! C" J/ s$ h9 Y' S - key(last_name,first_name,dob)
复制代码 匹配全名1 j; R- Q! N# v- L5 N8 q" u8 Q: r
全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。( B$ b7 ^2 t L) X3 r% Y
的人。
7 M; V: P% T- f% m! F' ?匹配最左前缀
2 f% j4 K4 k0 e8 IB-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。
0 c+ r$ Z$ [( F' H8 Y" Z匹配列前缀3 u0 V8 G8 ]1 i+ g9 V( @5 L
可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。
. {3 o" }7 _4 w" Y4 d匹配范围值* J4 G7 z: `9 F: i6 |+ c
这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.
' i% `$ X4 u3 q8 L4 S6 s1 {0 x精确匹配一部分并且匹配某个范围中的另一部分
) ^1 P V! w7 r2 Y; E这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last, |. v6 \$ t6 S
列并且对first name列进行了范囤查询。- t2 y' c z J( Q x* I
name
# y5 c9 W2 u8 Y2 f4 e0 A只访问索引的查询6 C* m; r- K. P, @( P% }( @
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。
& e# v. b9 ?) r H+ i1 n2 M. d% l* X8 t% g; u: ~/ o
由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,- ?! g1 v' H) v* l8 p
如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查9 m$ L* \" A+ |0 A
找方式也可以同等地应用于ORDER BY。( c) K% a8 u: L/ j
! y, A+ q' j9 `5 e" L0 O/ q" h下面是B-Tree索引的一些局限:
, q% c0 u' L5 D0 m& ~2 `6 N- S" W1 i0 |6 T# N2 G& N; d$ l
1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,
9 |0 U6 l1 k) c" _) p也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓
$ Q6 y4 {5 ]- p. ~ h3 J9 G, N2 T氏以特定字符结尾的人。
{$ n( q& u' q; S3 Y5 [, o( Y2 m3 N4 }" s6 o9 P, G1 D* ?# N
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定. ~$ M& d/ H+ ~' C3 X
义first_name列的值,MySQL就只能使用索引的第一列。
1 j/ u3 K/ u* r% F
- _) L/ C8 Z" Z3 a# C7 _# B& d3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
" Q6 J& R. X7 q. Z2 w6 S范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而+ a) Y! J/ S1 X9 d$ _2 M9 c
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。5 A; L6 J" U- H* v% P
2 A q8 A: a, r5 m( l8 A( {
哈希索引,空间索引和全文索引等,暂时没有设计
5 h, H0 o% g C! z
u8 F9 p, L1 A9 k高性能索引策略& P. N# C. O! |: z
) ?6 r; }5 M/ O+ o% [4 t- W1,隔离列,意思就是不要对查询条件中列进行计算等操作
1 m) d; b+ a& f, U2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引9 U9 H4 H: Z O9 q R* d& v$ t |
Select count(distinct 列) /count(*) from table;8 _4 @5 I+ M& Z- L
看看这个值时多少,如0.03120 _5 z! j( S" @" D0 ^
那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算
/ ?! O+ ?, L( P: d L, c,这对于大表很有用。
0 {" p. F! I% S% v$ J7 I( {0 ^. YSelect count(distinct left(列,3)) /count(*) as sel1,
' T) ^) e9 t6 _" r% q7 C count(distinct left(列,4)) /count(*) as sel1 ,2 _+ z! B+ A% l+ p
count(distinct left(列,5)) /count(*) as sel1,
% E7 A& _" o* U; F" m/ ] count(distinct left(列,6)) /count(*) as sel1,
8 n8 p& r Y, q count(distinct left(列,7)) /count(*) as sel1 from table;
A, U& f* Q/ H& {找到接近0.0312即可。
( i0 P' M. `: T/ n1 @& u$ r
, V# Y# @, I! XAlter table table_name add key (列(7))
1 n, Q" z' }5 v2 l3,覆盖索引
5 Z# _/ H7 B% W2 h9 {$ Y包含或者覆盖所有满足查询的数据索引叫做覆盖索引- _2 u* o& `3 y# x: H/ `0 H
explain时,extra中的会显示using index
! t' U9 S) F5 Z' \0 P这里一个重要的原则是4 l3 X- V; m% }. V/ P/ f" g
select后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列
, d) D5 [# s/ A2 ~% c' v4 J如select id from table_name;
0 V$ W7 P9 m- M5 h& b) f1 e) q: B% @9 I- k
很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
$ y1 m! w2 {6 i& k/ s一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。/ Q1 w A/ v- K. x0 H+ ^
2 c4 \% O5 `1 g
- Explain Select * from table_name where col ='nam' and col1 like '%name%';
1 @% I! U+ M# n! l' i$ ` - Extra:using where
复制代码 该索引不能覆盖查询的原因:
# f& k2 p% ]# j$ N1 r1,8 H0 X1 D2 w) n3 G
没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。
! a3 O* t# z+ F; u" Q2,7 V) M( r5 o( m, n/ B( [( M
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
1 s% d- D. A" i* q有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:3 y% t5 \, ~ e, d& v
3 o- H) A) `" U. T( z. N4,为排序使用索引扫描" w7 Q$ J' j' Q, ^% W
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。9 D6 G1 t; x F# K. C" J
explain输出type为index,表示mysql会扫描索引
, A: q, W! D% i, a0 P+ T+ R4 c( E5 _( I" P$ A; V
扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.
) {% _. l5 w) c( U: \" ~
3 I$ L, X- V T* y, x3 s2 @MySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。
( f2 Q" f# \4 O; c/ k" A P/ D8 P
' z- l1 O+ D! u! ?% y7 ]2 e按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。
# ]5 C# T1 \, X' V2 a3 k4 a/ P+ s
ORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。
6 h5 v d* ^, q, D }- [2 C
- a ?$ D, t) W! E3 I" b3 D) O使用join可能情况会有不同3 L0 N$ @2 u+ o, I0 _% s* [5 F1 `# I
# q% ]- t2 V* _: Q5,压缩索引(myisam)
, }& u: f- n9 U6,多余和重复索引(应该避免)- ~- Q2 J1 ~3 r; T
( z) N# b) L9 `9 c ~4 z9 w( g
多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)7 ]( V7 F! [9 L) F+ R) m
上有索引,那么另外一个列(A)上的' ?0 e; { t2 h7 j+ ]
索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
# m( b4 c, t. w+ q" j然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。
: S" z: c2 |% O f2 f
7 P/ o6 k8 o. Q$ b要点:+ q0 r* x3 D9 P: l
在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.2 {0 e+ V8 y( {0 e
; P. l+ p3 ?: u5 p2 r: h o1 b
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行
+ r \- P6 |8 h0 L y' d; \3 W/ r4 I, a' j
9 v+ A# w d. h2 U& X/ J7 T' A( [. L$ L8 O3 I A5 G& D! T9 b( O$ {
|
|