|
|
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能
9 a0 F( S! C2 r& L3 Q) O$ u2 }0 h有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成/ N' @/ U) f6 m4 d8 e
一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列
: [* Q5 m+ r2 l在InnoDB中,只有事务提交后才会解锁- D$ h! S( U; v) Y+ G- g! m$ J
; X1 m! O6 z7 K) C' i6 {
索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只; t( A3 v! Y" S
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。
; X" K2 b% X" @7 p# k, k0 z; n& P) P8 b% y0 k
B-TREE
& E" W1 n- d+ W; v) k4 D) l% [9 h能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找9 x+ P. w$ }; ]; {* b# D5 h
使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。! g; @0 F5 _& b$ J
- N) T2 L L8 M0 t6 R% f% B! D- CREATE TABLE People(3 l& }$ x# b0 r0 |
- last_name varchar(50) not null) q9 Z2 E0 z0 I8 D! ^4 n
- first_name varchar(50) not null; Q( E9 [8 w4 `2 T9 r# V' t, p, p
- dob date not null
* I5 S/ w8 Z+ T1 p" a/ C: f - gende enum('m','f') not null
) J0 l+ ?0 y8 w) r4 L - key(last_name,first_name,dob)
复制代码 匹配全名" q) m1 E9 _! V3 b0 C. h( C! ^0 d# I& M
全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。: \( P7 l7 Q$ b9 y- p
的人。# Y/ N. M+ }# k% D. }: n
匹配最左前缀
/ t% Y I* d) I: \! aB-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。9 o2 u; G/ x) ]' Z( n% S8 E6 r
匹配列前缀
& Q K: h7 ]2 h, }4 ?1 @% @可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。
7 r! Z' f5 S$ C% z! F* a匹配范围值$ _6 _& }/ D3 Q4 N [6 Q0 p
这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.4 ~+ K' Y) \. ^- ^5 ^$ B- h
精确匹配一部分并且匹配某个范围中的另一部分
4 K' _$ c7 P! k* p; A这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last
% E$ t. \' h8 h3 h i! z4 L- W) u列并且对first name列进行了范囤查询。- Z* I. ^1 W( x$ f& t
name3 h6 W5 v* C; G5 U5 Q* U6 j
只访问索引的查询) T* j: C9 [# J
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。
/ }/ ^, b+ W( L, W; K6 |
& i+ j8 {9 ]2 e% ~7 ?1 I由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,0 d/ b! |6 P) r$ h2 u! ?* z% K
如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查
, S- e8 x: g- W5 k- b) O找方式也可以同等地应用于ORDER BY。! L* H/ ]& T7 t, b9 g
v2 C. A$ n! I. {6 o下面是B-Tree索引的一些局限:( t: G0 f @" O) t
" C% i% t% J6 s* Z# v
1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,* y& H# q: r% h6 Y9 R( @# r
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓
' @6 Q" u, T9 ~- K4 }) Y, E氏以特定字符结尾的人。
* V. f3 I/ L. s$ C! J/ [3 h: o. D7 i" M( a3 y$ W9 X3 W- ^2 }8 q( a
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定+ L' @% c" p _3 M* Y( M1 T9 y" Z7 P
义first_name列的值,MySQL就只能使用索引的第一列。
$ W0 F. N) |! H7 b3 t
7 }, U* F5 M% E3 _7 s; ?3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是0 g S2 ^) |3 q( q
范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而& Y, ~* N' J. |! m
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。
' h% B* m. \) [0 o' N6 c- q
# x+ I% X1 D6 X: u' A/ ?/ ?哈希索引,空间索引和全文索引等,暂时没有设计
# X! [+ n9 C$ D$ I) P* ~( Z2 w1 h# w. J5 j X: l9 x w' x
高性能索引策略
* Y, W' g8 \7 H' V9 w: S: g) G ~( ]) T: z0 h! O' N% n5 w
1,隔离列,意思就是不要对查询条件中列进行计算等操作: m5 I+ ~$ h4 h" g0 i
2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引
, a+ C; [' G" A2 J$ t% aSelect count(distinct 列) /count(*) from table;/ U1 e/ z* l" {% u& z5 E5 e b
看看这个值时多少,如0.03128 o2 S8 x! \6 t
那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算) J& c5 t0 U8 U! o* A1 j! H/ i
,这对于大表很有用。& h% D' c: b* w
Select count(distinct left(列,3)) /count(*) as sel1,1 f9 _$ R; {2 l6 ?/ B& L4 ~
count(distinct left(列,4)) /count(*) as sel1 ,
" n9 h) t5 E O; i, C' W# D count(distinct left(列,5)) /count(*) as sel1,8 G* M6 A7 I) ^+ Z$ g* V
count(distinct left(列,6)) /count(*) as sel1,- k6 y( ?3 g" D3 l
count(distinct left(列,7)) /count(*) as sel1 from table;9 s8 H6 z( A3 T: x% S, _
找到接近0.0312即可。, g" h2 |9 H, T/ U
* M) W7 C/ G2 G% k1 M* X) @Alter table table_name add key (列(7))( ~! D" E/ X. U& B, Y( X( l0 C9 \
3,覆盖索引1 j1 ?& \( O3 \7 X
包含或者覆盖所有满足查询的数据索引叫做覆盖索引
4 \4 C! \- C) n1 b0 u: texplain时,extra中的会显示using index, ]2 c# o0 y2 H9 P$ m# w9 G
这里一个重要的原则是* \3 m" P; a! F/ G
select后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列, I8 c7 q- y$ e+ w5 S
如select id from table_name;5 ]8 o. i( q6 {7 I* [
5 ^9 C% g; v. y D# H @6 ]6 H很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
. J; N# m K- n( |6 r一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。+ X# v+ W* R" M3 k3 g3 L* I
' [" |% F4 }+ W' f, t# Q- Explain Select * from table_name where col ='nam' and col1 like '%name%';' I1 H+ r2 r, u7 a4 m/ T
- Extra:using where
复制代码 该索引不能覆盖查询的原因:& A6 G7 J4 k" x# }5 w
1,
& e h) X0 m! z, T' @5 o8 |# F没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。6 v! t. d* g7 V7 w
2,+ x0 i7 E4 u# ~ S4 H5 ^$ ~
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。7 K8 U4 F+ w! n& R
有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:
2 @$ S4 r5 c8 K* b4 }6 `4 Q# {6 N& g' s D3 F
4,为排序使用索引扫描+ W! @ Q. \4 P" }5 J1 w
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。
( V8 l9 q# I: W8 N+ |explain输出type为index,表示mysql会扫描索引
' l% X) p- D5 |. Y( B$ c4 u. X& h9 s6 ]3 g
扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.
* s4 x( b1 w2 L" U" h* \# A1 O4 S3 [+ l
MySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。+ B; u1 e" V& c1 N! ^
$ q+ e, p N6 N8 z' Z按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。7 G; \/ T" A; d# J
- c! I, Y! @% b* uORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。
4 q9 o5 Q5 ]7 w f7 X8 i/ q" W2 O3 ~" p2 g" b; V
使用join可能情况会有不同4 a' U0 s2 V% g* N8 I3 f
9 @ L c _0 x5,压缩索引(myisam)3 S# }6 K! a4 k; w; |
6,多余和重复索引(应该避免)
: x$ C6 z& |. T+ D5 [' J
) u) `9 ^. {6 x, S5 x多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)
) F3 h* ]( t! z6 Q7 H1 K上有索引,那么另外一个列(A)上的
- K# l8 \3 W# v* r/ j索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
8 @0 n$ w# Z/ _4 e7 T然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。
+ `& D* b3 z* ~3 H7 T- `7 l4 q
& g: f! d3 I) [ J5 s7 ^; e& {' c要点:) K2 P6 r4 j- \& q# X
在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.& v! Q! E, c4 ?& Y
! y( j4 a% F1 {4 R& t即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行. i0 C% d5 j) {6 L
0 r" B. C" ]$ [+ ]" E6 P/ l* ^; q
1 |6 S$ L6 p5 b$ D( J1 a- K3 W! `$ b! j7 z
|
|