|
|
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能( h- ^7 X) m+ [) U( c7 r
有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成: \" O( E2 C, f: o! z$ p) n
一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列% l; M! m4 P& p. [- }" S+ d T
在InnoDB中,只有事务提交后才会解锁
! @! R1 x& P4 B# E! @, v8 v7 L
8 C6 D, d1 u: E索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只. e; e O( a: y. m$ z2 l9 m
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。
5 _# Q r$ W. f- G+ o8 o7 r
; _3 ~( [, k( |* T# BB-TREE
5 g k, w5 x& y: |/ a: \2 z) s能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找
/ a" ^3 C- g! Y* ]9 p2 K# B使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。
8 z) ^- u# `* ?9 n- 4 P( ~. h% ^' |. S1 q3 d. G. _
- CREATE TABLE People(
* d7 p5 F2 m- c) r2 q - last_name varchar(50) not null
9 D8 g) {3 A, `4 c; T5 ]3 W9 Y1 z - first_name varchar(50) not null, L2 f$ ~- W0 g# S( m8 M& `
- dob date not null
E0 y7 l9 [8 `4 T: F - gende enum('m','f') not null
! i3 q. l( F7 Y2 V# f9 ? - key(last_name,first_name,dob)
复制代码 匹配全名; Y! D2 T: a) t, }
全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。
9 T( }1 w. n9 }/ ]. Q的人。3 x$ ~& `5 [( u% ?6 q, _ P
匹配最左前缀# x0 r9 W8 {* R( _$ w5 G8 r
B-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。: {- C* h N% Z8 w) `, w: b
匹配列前缀
/ p8 G1 Q/ i2 Y4 |; t% e/ Q可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。
+ ^2 C( b8 g9 O, E. Y4 r, ]' Q匹配范围值$ i- }) B! Q( @$ G! y! w& q3 P; a
这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.: e% L! E p$ c+ t, X2 f: @
精确匹配一部分并且匹配某个范围中的另一部分
0 {: C3 v, M3 \! G' A G1 E- E这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last! q3 [- p6 D- J' z% J
列并且对first name列进行了范囤查询。
' U- [1 p; l4 J; Y+ Fname
# s. R8 r2 S3 ^3 n- p3 ^+ Z5 Z, V只访问索引的查询
0 i$ i+ U) l2 BB-Tree索引通常能支持只访问索引的查询,它不会访问数据行。+ S; _ E b% B, U
8 Q6 |+ L* g) ~* F, I
由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,
, H& C/ ?9 d1 T5 Y" d如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查
+ n9 B+ `/ s/ g' s找方式也可以同等地应用于ORDER BY。
% C3 {+ g# i3 z; k, j! e: C m L' }8 J2 l, n' p
下面是B-Tree索引的一些局限:" a) M: w& _- j6 R6 V
8 @: H6 U: z5 U. O; _5 y1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,& w5 i) X7 B o: w
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓/ X9 J/ B9 i1 U8 Y# C2 L
氏以特定字符结尾的人。) }5 V; M6 t! v7 i( w- _
8 f' t. B1 Z( d0 \5 g9 o2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定: M* M! U/ q( c- y5 {, a4 l1 j; F8 z4 ]
义first_name列的值,MySQL就只能使用索引的第一列。, o; \$ ?( I6 P' o: l! N! J' |
, T2 t3 k% W- m/ a
3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
# R- Y+ {3 y# O9 V范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而
$ [: M, z, l1 J6 p不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。4 Q# m( ~- h' g3 b
1 p: I2 R" v% w# F$ J哈希索引,空间索引和全文索引等,暂时没有设计' ~: c0 Z" q" ^ c4 y# U6 M! j
3 |/ o; z: U# _5 b& V高性能索引策略( x4 D8 J2 Q* J& f
; [! c5 U9 n) b1 M8 ~% m0 g3 ?1,隔离列,意思就是不要对查询条件中列进行计算等操作
* A: T4 i8 K! Z1 q: Q) N K2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引7 y6 ~. z3 I- i4 H. G1 U% ?
Select count(distinct 列) /count(*) from table;0 I R% m, w. a$ f
看看这个值时多少,如0.0312; N8 T6 I( R6 i/ b9 t9 L
那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算0 t* V8 ^0 Y# ]4 a& L* C% `
,这对于大表很有用。# Z' K$ y* d: l2 [5 F6 z( C9 y
Select count(distinct left(列,3)) /count(*) as sel1,
7 z5 f) s) L% _5 y6 u& H count(distinct left(列,4)) /count(*) as sel1 ,
3 S8 R1 |( A9 a$ _) R. Y, @/ O3 P count(distinct left(列,5)) /count(*) as sel1,
: ] [6 B# V2 d6 ~ count(distinct left(列,6)) /count(*) as sel1,( b5 q% s" d9 V9 X; z- R8 V
count(distinct left(列,7)) /count(*) as sel1 from table;6 K8 G8 S' e9 O
找到接近0.0312即可。. A, p" Z$ s6 s6 o" J9 h
& o+ {2 ^ }+ _3 M* n3 D
Alter table table_name add key (列(7))
6 C# u% E9 `: [: N3,覆盖索引: k* |! d: t* `' w; ~
包含或者覆盖所有满足查询的数据索引叫做覆盖索引5 k3 @* D( i8 `8 N Q6 P# D
explain时,extra中的会显示using index/ g4 e0 _' v: u8 f2 H
这里一个重要的原则是
; ^0 o# r6 @# l; oselect后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列* Y- Q7 m3 ]5 w, G
如select id from table_name;) M: M4 _7 E- L" H5 @0 K
9 N6 c# \# P/ V ^" g很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不0 ^- ~8 ^) h( i6 R+ ~; m
一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。, t: ^8 m' n6 z# H3 H
% U* Y* e5 \0 l" L" s+ c( _- Explain Select * from table_name where col ='nam' and col1 like '%name%';
; U2 S; l S B" n4 {% ?2 l; R% g - Extra:using where
复制代码 该索引不能覆盖查询的原因:+ e* Y3 U2 V! z( p7 ] H) h* e
1,
$ r9 t, b8 e5 X* V没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。
7 G8 I# p8 c# G; [2,, S( x) E) R' W) G& G; E3 m+ A% \
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
' G' r, ~) d" p& ~' C4 _有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:
* o7 X1 p/ ^# k6 s- k3 f3 h- M' w) B3 o+ R8 _: Z
4,为排序使用索引扫描% a, W9 ^; d( l) I4 T
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。8 G4 z. d8 U4 K X" }* @' k
explain输出type为index,表示mysql会扫描索引
- k. L( W' ]; X3 M- ?! ^4 `- n# }$ Z3 _; W! E. f, d: x' t
扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.9 C( d0 y# k7 ^% J Q3 F
6 W. e; Z+ o( X2 M: h' t
MySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。
9 b+ K) v) {7 `# u: U6 \, a
! X7 y$ @; F% v按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。. i0 Q$ h- O {; i8 k1 Y
/ M" r4 G0 G' ]4 ^3 [1 m$ SORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。7 V+ t! z* }: i$ q& f' V) ?' }* B1 I
0 L' _2 C U; r使用join可能情况会有不同/ w8 J+ |5 N" ]
7 A; ]& f$ [5 H2 _) I0 z+ s$ K7 b! p9 y
5,压缩索引(myisam)6 G1 \1 H/ R. Z" Z! y2 _
6,多余和重复索引(应该避免)4 m$ n. M$ {4 O: L- a1 w
+ l5 `. A2 f5 v8 t0 {, w
多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)
M+ e5 D- B& p5 m5 q上有索引,那么另外一个列(A)上的
: q9 X1 r4 w3 f( y7 ? v( e/ i索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
3 L2 N2 l# P) Q( d; l然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。2 I; b9 j: N! f# _1 G
& v; b4 z, [! ~* r, s' ?
要点:
& n! b' u3 l8 a5 F; K, ]0 {1 W: t在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.
2 n7 h1 z: C# F1 Y' R( M* R: @5 l/ L' Q* _1 S0 a1 v7 P$ E
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行" \! Y8 ]9 @- b% z: s) T( t: w
( p4 S4 n3 l6 M/ R2 L# u8 I8 n! X
8 q. P1 Y, A5 _$ S- s
4 z) W. X b- T% Q3 @, g |
|