|
|
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能
1 q7 S: ^& h" m) M+ e5 V% u有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成/ E/ n( C8 H! a7 Q/ X8 g, l, `% S
一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列2 \6 L9 V. R0 ^" o5 Y
在InnoDB中,只有事务提交后才会解锁
1 d* k* P- D% a( ~$ R% t2 b) B7 }! a! ?
索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只
7 h% D" {# E7 E Z+ j能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。# u5 p: P9 N; `- S$ G$ z
' a3 A% s; R& j/ p6 k1 G( u, A
B-TREE4 I4 ~. f+ \! s6 L% r. J, _
能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找
: a% K/ `! ]) O0 m% o5 W) }使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。# C- D# J# |& J; t7 g
8 {+ \3 E) z2 C8 r/ V2 R9 R2 J. d- CREATE TABLE People(% I1 [# I2 s# |0 W' x
- last_name varchar(50) not null
. r8 b# I' y- z" }5 y - first_name varchar(50) not null) c3 H0 {; `5 h& }' R7 x7 [" @
- dob date not null
- Y* L- e% t; r# `2 l% k0 ? - gende enum('m','f') not null
( \9 s5 k! m, o8 C5 P; s" x- S f3 Z - key(last_name,first_name,dob)
复制代码 匹配全名3 U3 G( P4 J, z2 D4 ? F% C! O5 _
全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。
- O$ L/ j' J! ]# U! V* ^9 D的人。* r/ c3 R d8 ?2 y1 i. E( J" q
匹配最左前缀
6 ]. Y% D1 `3 ] zB-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。2 o3 w% a5 U) O+ n. |# e6 K: i5 r
匹配列前缀
4 l6 z. R& B, I# i可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。: m" I: Z1 V4 j0 ?( B" G. o
匹配范围值, Q, a0 l" _) L( D0 K, q! G
这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列." a% ^& B. c! S& @: |
精确匹配一部分并且匹配某个范围中的另一部分
% ~( x2 _% i9 \ |' p' J8 ~5 I这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last1 A$ W4 g% I! i$ s
列并且对first name列进行了范囤查询。
3 Y/ g& D% ?6 @3 ], _' n# hname
& X: T" G/ D, n1 i5 E' j9 s只访问索引的查询0 ^7 E" q7 ^% @
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。
' b+ \& v. N8 m7 q7 j8 S3 ^7 H# b$ S8 y* S6 h/ `8 V" K- A& L8 U- @
由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,
0 O' s) |, C/ ~8 g( B如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查
) \4 t- V$ R) L6 p6 w6 \找方式也可以同等地应用于ORDER BY。3 F% K3 N7 d8 t( P8 H7 i: {( n
- v% `1 _. E# _* |# L% j/ Z3 r' C
下面是B-Tree索引的一些局限:) E% c) |+ w/ i/ b* H" ?4 z
# |% U7 J# i% T2 @
1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,
3 t. U5 D2 y" O3 ^7 }也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓, q' {( S" j j }# Z$ D
氏以特定字符结尾的人。1 \+ S5 B) m" a0 u" C$ O" N* K
- l5 J& C7 V! N1 T$ O& S
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定& P) o9 ~1 q' J1 c8 Y5 V. w% q* u
义first_name列的值,MySQL就只能使用索引的第一列。8 O* u( V! N6 [. A. ?
) n8 F) Q& s) V! q Y; J0 c3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
- H+ z! s6 F! j6 n" w4 S7 {% }范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而; ^ _" |9 k& c
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。
6 C: O# A' R/ n3 G: ?' {9 j
( g3 E( B% n2 e" N! W1 H哈希索引,空间索引和全文索引等,暂时没有设计5 M' J3 w. ]+ K
0 F. a/ m0 [1 _9 i8 H8 ^4 H. E高性能索引策略6 b* F/ k. x8 I
M- ]! [9 Y: i$ s$ P1,隔离列,意思就是不要对查询条件中列进行计算等操作
- G5 X8 d/ n& F2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引
' S2 U: e7 c* hSelect count(distinct 列) /count(*) from table;7 n8 W. i. I1 o% [) L5 x
看看这个值时多少,如0.0312
9 K$ Q/ l( t" ~* S那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算% k. u& w% S5 n' P
,这对于大表很有用。4 s+ a7 Q$ l6 `( O5 Q
Select count(distinct left(列,3)) /count(*) as sel1," w& B, n$ [. n8 p, w9 n3 e2 ]
count(distinct left(列,4)) /count(*) as sel1 ,& L# H* N; `6 z6 x, ~4 s
count(distinct left(列,5)) /count(*) as sel1,
4 W# q; A- M4 ` count(distinct left(列,6)) /count(*) as sel1,
$ A1 e. @/ T- `: o" K0 m count(distinct left(列,7)) /count(*) as sel1 from table;# W9 q) K; ~3 T$ f; b* D6 v# S9 E
找到接近0.0312即可。
+ J3 N; M. _1 C3 R4 U
# [, E0 y# y* K8 g" y0 F% XAlter table table_name add key (列(7))
% J( O5 w: A5 _) \6 D& a/ y3,覆盖索引* D' m) X- i3 ]+ O- Q* T
包含或者覆盖所有满足查询的数据索引叫做覆盖索引
A3 E8 N8 T& p P/ |: m6 u3 Aexplain时,extra中的会显示using index8 d, a5 z, |/ I3 O5 c
这里一个重要的原则是
3 M! Y8 n1 ]. ~4 b/ P5 m1 C. k; nselect后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列8 w+ a4 c" y4 o/ ], J
如select id from table_name;& Q/ C' j! _* F+ ]8 s
; A$ Y8 d6 L, I6 P* [- X很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
: f6 [- ?) Z' m) ^( P. F一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。
6 F9 X4 `9 S U+ R4 s: ?' B/ k
4 p% L8 x, ~$ ]6 W4 b6 }- Explain Select * from table_name where col ='nam' and col1 like '%name%';6 s1 l5 D! x0 j8 ]2 \/ O; V6 ^ t7 L% _
- Extra:using where
复制代码 该索引不能覆盖查询的原因:
2 q. B+ H6 H0 |: e1,' x- x6 w4 P' ?/ R2 W4 K
没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。$ N5 O7 F. H$ V, i
2,- s' {( w. T) ?
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
1 @4 ?& ^) M. a$ x8 c有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:$ b+ `5 i5 ]% D3 }+ {1 @" d- |
( C- q4 i- u1 u
4,为排序使用索引扫描! Q- r1 p+ e5 R( {7 ^
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。( s# m8 x9 G/ b( a" U0 S8 b2 J( X
explain输出type为index,表示mysql会扫描索引
3 p" T2 h0 v" [3 _2 y/ M
! ?7 i) o( D; h; N" m7 K+ l6 d扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.9 K) `6 N4 H+ |
3 f! @) G; _6 P1 e9 K' UMySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。, ]& L% E3 s6 ~0 |- e, U3 K
& U7 d/ X8 w' r( y! M7 H1 v4 z
按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。6 \# q% w1 s# K. U' i$ b
4 x' O% P4 X8 V* A2 tORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。. f# J. w P5 C" N7 `
( N' P' B' c7 T
使用join可能情况会有不同
$ c9 Z4 K$ m: X6 z, c5 p) p6 Q3 {6 f( N8 e1 o/ X, n
5,压缩索引(myisam)
, F) i# E: w9 p4 O6,多余和重复索引(应该避免)
8 H* W9 V: R3 \6 g* x6 V! [$ L+ Y
& x+ X) a7 @9 E$ W多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B), {7 o) e, u6 i0 R
上有索引,那么另外一个列(A)上的
9 a( Q( _2 v; I索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
) S. e8 Y4 c. o然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。6 s3 r$ {( H, y/ J4 V
2 X6 g$ L* {* H3 r+ w3 e要点:
$ q: m, y* \. p0 O2 ~ J) ^在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.) y- m0 {+ @7 ^! E' j ~
; P$ d3 K* |( Y: r l* }
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行7 ~, E, C; E; j$ X0 U/ j& a @
5 Z# a2 h+ S) _$ z; N
. K; } s' `2 k+ S3 r4 ~: P
" B0 u& i. N2 g6 |+ y! W1 f3 i
|
|