|
|
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能( d9 D; `' {& u& k/ X/ b
有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成) g7 [$ y' F( i; S! e" o
一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列+ ]& ]5 a. z! u
在InnoDB中,只有事务提交后才会解锁( l& [ J1 C. C! y/ D; x$ y# W
$ I% X$ J# W3 O$ m索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只0 f" p. y% s& w L6 r4 W; V3 l: V* u
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。
8 U/ K# }: {( s% K0 r
: u1 d2 X* F8 R, Z0 b/ y) T- IB-TREE( k, U8 k. P! I2 v0 u# R
能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找3 L! s9 A/ a' i5 b9 U; r
使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。9 z- Q1 e! L, _, }7 O
- / m$ p: m9 g% Y) o6 Z
- CREATE TABLE People( _( A5 H+ l% w- j- r
- last_name varchar(50) not null
2 J: g" i9 Y7 i2 C0 F, d - first_name varchar(50) not null- w* S* I6 s4 _# H
- dob date not null, O) l! S* ?2 L3 m
- gende enum('m','f') not null
$ W& U6 o3 a0 a P - key(last_name,first_name,dob)
复制代码 匹配全名
( C X' H) b" D& F# [' V全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。1 d6 z6 X0 Q# r0 Z+ D( D) J
的人。
! n. l" s; J; q9 ~$ j匹配最左前缀/ D+ w9 p: Q. U4 d) A8 L+ i- b9 L
B-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。' \ U8 k& J4 V# q; w
匹配列前缀
4 r, F# G) z2 ?& }可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。6 o* j* x+ |7 W/ m5 J8 q1 A
匹配范围值
0 N7 m0 H& A" d5 u2 P: h: W4 G' a" Q这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.6 H" W( m5 e) r4 _
精确匹配一部分并且匹配某个范围中的另一部分
; l; c" [9 c' R" V! F) a这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last
- ^0 s8 M- g% h4 a列并且对first name列进行了范囤查询。5 i& S- e+ O4 K
name+ T: K& p6 F1 t' }8 ~
只访问索引的查询1 V! i9 C( S6 \) B3 A+ U, y( h0 b
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。2 }& A# \4 W9 I; F# A
" \' G) U# I& Z/ s+ j2 d! o8 {. E由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,
$ f: k7 ]; C; g如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查* ]8 T9 [6 [4 l! [) \
找方式也可以同等地应用于ORDER BY。9 G2 o |) B" Q, I2 s7 b: q
+ q" X2 K" J! @3 Y7 n6 g
下面是B-Tree索引的一些局限:5 F/ ?& k5 K! s" D2 W5 X, x" ]
7 |8 g K! j6 D! t4 n% x( E: b
1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,) H+ A2 j8 `: H1 G
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓! q5 f) X8 C, U
氏以特定字符结尾的人。
" c( A" d$ U' Z& m; c1 S5 }# W0 E R7 \5 J& F8 P5 u
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定* K6 v, V1 C! ]0 W/ o4 X$ F1 J
义first_name列的值,MySQL就只能使用索引的第一列。% `" h& m: i8 w: P$ C" n
, H& M6 x5 S3 I( }
3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
4 J0 {: z; c4 ?5 O范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而5 e# {# b$ u( f* B0 o* [- q5 m$ l- `
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。. K/ l" Y2 Y; o4 ?' X
* u# j) C8 D8 O: s' F9 q* m
哈希索引,空间索引和全文索引等,暂时没有设计. j8 r1 ?1 P+ v
$ v3 y$ x* ?, A7 p/ F
高性能索引策略
# v' A9 l% f9 a- ^
* L0 B6 {! M+ ]( B3 a% A1,隔离列,意思就是不要对查询条件中列进行计算等操作: C) K& i- S+ b3 T
2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引+ {# J8 ]. L3 J. @3 u3 y& t
Select count(distinct 列) /count(*) from table;1 q7 L+ J9 E7 X
看看这个值时多少,如0.0312
) I2 c2 X1 m4 \4 I8 m( z7 w那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算9 _9 l w9 i4 U5 A, W
,这对于大表很有用。
# H/ v7 W! e3 m7 cSelect count(distinct left(列,3)) /count(*) as sel1,
7 n8 V" u/ ~* s2 L% V! ~ count(distinct left(列,4)) /count(*) as sel1 ,
: @) \! L- `" |8 j9 t+ z% p0 [ count(distinct left(列,5)) /count(*) as sel1,( f- U: ]0 J) J0 b
count(distinct left(列,6)) /count(*) as sel1,9 O- b$ L, U% y8 z) I9 Q
count(distinct left(列,7)) /count(*) as sel1 from table;
, u9 R+ c% U0 x; E; ]8 q找到接近0.0312即可。3 m, t2 G, ^" J; ~ j
: A" @' [: ^# s6 b" JAlter table table_name add key (列(7)). ]( C8 A4 _# P3 g; m% P+ W! i
3,覆盖索引% C6 y0 J) _8 [ G! k# S( ?' W. O$ D4 J
包含或者覆盖所有满足查询的数据索引叫做覆盖索引7 f, @, V! ?, m' @6 J
explain时,extra中的会显示using index7 E. T9 L& d$ B/ X/ R5 w' d
这里一个重要的原则是
( A [, O4 Q6 G. `9 R9 iselect后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列6 w! l K X) S5 `
如select id from table_name;1 F M& a! V' K' p
5 c8 R6 z }4 T4 }很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
2 X, A8 p4 [* I' {( R一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。, O( m5 Z% f5 q/ ?( S
5 [* Z6 X4 w4 w& r8 _$ m- I M- Explain Select * from table_name where col ='nam' and col1 like '%name%';
' z$ J# \4 s+ W+ o - Extra:using where
复制代码 该索引不能覆盖查询的原因:: t3 O% E7 [, H
1,4 ~% L' V* C) y5 h6 X$ {
没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。# Z% C8 N. I( D5 ?
2,* e0 m( Z; I" W( c; s, w
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。- f0 |2 }7 \8 o3 b
有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:
) Y3 U5 j9 J% E/ b5 ]) x A- X! p# v4 M' m! A( V; h
4,为排序使用索引扫描
3 [0 g" S4 e1 Kmysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。) ~8 Q% `; N+ X6 }+ ^9 a1 J
explain输出type为index,表示mysql会扫描索引5 X6 v4 z9 V% q) B2 J/ X. F
8 F M' Q: I8 Z7 ]8 Q# a
扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.5 ?$ [' t! P) P
8 C v. _& P! L' B2 g T7 IMySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。2 S; [6 c0 L u- c# e
B/ G) J4 ?; V( w' y* x
按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。' ], \ ?- F% ?6 `
2 i5 c! O3 j5 Q! H* h( {ORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。4 T7 I' w ?: N, q% J
' b" s! M8 n1 x
使用join可能情况会有不同, @4 P, ^/ t0 L# M! G
9 A. ^9 ^( U# k: }4 H
5,压缩索引(myisam)
/ L$ I7 _1 M' Q m8 d* G) v6,多余和重复索引(应该避免)
4 h2 b! N& l7 [
& o" ]6 @, E: }# F1 F多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)
( }* C' q3 I- m( l上有索引,那么另外一个列(A)上的
* L& Q6 h4 J. C: }3 r& o0 z索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
( E- m/ i% t' t, X然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。
% U# Y* l. T( J+ I' f
; c+ \/ c: ^$ ? h& r要点:
7 M2 y' m% b5 I在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.
2 r3 Y' ]' g% r$ G7 u8 F- l4 a* \9 a1 R0 [3 c& }
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行
2 ~. F: A/ B4 u# h0 U, T/ j
; ~" A1 N% @5 m/ D. c8 m" I' I4 s) c( H# j$ B2 i6 H- g- ]
; Z" V& ?3 Y3 w- I! z7 j! V! J
|
|