|
|
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能
|9 |5 ~7 W+ w; ]9 L: ]/ Y" t5 ~有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成
* L; i; m. @ W/ a" k一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列
1 L3 V0 S8 L6 p在InnoDB中,只有事务提交后才会解锁( T. D) d& ?% J0 x0 z- R6 Y) z
& X7 c. E K1 E1 N# R V
索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只6 S3 _( { }! k% A: i) W
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。7 y! T n# v5 v4 y9 V$ ~& G
* a( [5 G9 N* T* KB-TREE1 H; ~4 S7 K& s9 h
能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找; A( x, o9 K+ d5 v) k
使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。# @* W1 |" u6 G4 ~( D6 K
5 G& l! Q, O' Y' y ~) e& k1 ?- CREATE TABLE People(3 V; i" _4 A+ z' ]6 b4 ]
- last_name varchar(50) not null" }3 K6 O3 Q; H$ H' E8 W" h
- first_name varchar(50) not null
4 Z: E a7 I2 W {2 O0 ~ }6 N - dob date not null
, G: W' b. |0 J9 M - gende enum('m','f') not null
( ^+ W2 O: { s% S& j8 Q0 {* T2 x6 y - key(last_name,first_name,dob)
复制代码 匹配全名
/ [6 j1 J4 I. m" D全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。# i; W, J& ]' [8 X4 i# W4 D, S5 a2 p
的人。
: [" T9 ?7 G$ B2 q5 d7 H匹配最左前缀; D7 g" _/ a7 a2 m! u
B-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。/ @2 k! J A7 C8 c
匹配列前缀2 M6 O! a* g" L) Z
可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。
+ ~2 H: Q1 Y& ^# h# V, X3 ~' x) ~匹配范围值
7 ?( y* [ g2 h9 W这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.
- z+ { w4 f7 q5 R. L0 W精确匹配一部分并且匹配某个范围中的另一部分
4 o2 n; [+ Q6 O$ K- l* E这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last2 ?: n' f* j9 B
列并且对first name列进行了范囤查询。1 V3 X( [/ H. ~
name% S2 s' t$ N% a2 m! [9 l C
只访问索引的查询3 c2 f1 U# t% G* N2 L/ ], J
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。& x- G- Y4 c: s% {" ~
; `" r3 B6 M/ W6 V- d! O由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,
3 w4 p6 c' }8 a如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查: v/ t. x% e) T4 q7 A
找方式也可以同等地应用于ORDER BY。
1 H$ w3 m! B! ~/ b t) ] X) {: r% z% ^4 d/ {! [- f
下面是B-Tree索引的一些局限:
5 u4 C# e& L7 S9 ^9 ~2 b
( T9 @+ d0 _* Q( L8 W1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,2 |1 D7 B8 a& H( j" H
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓 { ?2 M p' e$ |* j
氏以特定字符结尾的人。
! }8 g0 p9 i" _( ^" b. x) r5 i/ V0 L
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定
) g6 M( t, O- p* s3 R- G义first_name列的值,MySQL就只能使用索引的第一列。
) i2 c- N$ O$ Q8 ~
( _! Q5 H. U. V. c( C3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是 u8 }; z B8 e3 d+ r9 x% D
范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而
4 }, `1 D2 Y6 j3 I6 I/ \不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。
( W B; d' a' n4 t
! u6 |4 M2 s; ~9 Z哈希索引,空间索引和全文索引等,暂时没有设计
0 p0 L' |6 ?" R3 ^0 m5 w+ ^# H8 V ^
高性能索引策略
p: V6 }+ G4 Z j# S; |1 C0 p* w9 N) d' W' S8 Q, A2 Q( j# R
1,隔离列,意思就是不要对查询条件中列进行计算等操作& T9 u5 l# [. M) R! m8 p
2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引
: U: ]- D, V- v) I E ASelect count(distinct 列) /count(*) from table;: \9 B( q3 ^! @9 X9 ?) T
看看这个值时多少,如0.0312$ x+ p6 k3 C4 v' y( \
那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算0 I# D' w! [3 Q" Z- q: w, d+ {
,这对于大表很有用。1 c _4 I1 X* Y
Select count(distinct left(列,3)) /count(*) as sel1,
0 W. l5 @3 k2 x( ]# M0 [ count(distinct left(列,4)) /count(*) as sel1 ,1 _, i# Q' ?9 G8 e) i1 b
count(distinct left(列,5)) /count(*) as sel1,
2 p* F$ q. [# H5 f count(distinct left(列,6)) /count(*) as sel1,& b5 J- Y/ ?4 p
count(distinct left(列,7)) /count(*) as sel1 from table;
0 Q+ l7 k1 ]: B找到接近0.0312即可。! p2 v) n1 [3 R6 l8 C
1 I. U% y! E+ L# I: E7 P; v
Alter table table_name add key (列(7))* g* W& r1 f- k- o/ y
3,覆盖索引 ?4 I7 i1 L) e; I0 o7 F
包含或者覆盖所有满足查询的数据索引叫做覆盖索引/ g( J5 e. A8 i) p
explain时,extra中的会显示using index" J. r; s5 A. s' ^1 Z' R- X3 A$ Z
这里一个重要的原则是; m. E; J* b7 g6 k; V8 v
select后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列/ T5 a' X# }/ D) k; P2 k
如select id from table_name;
4 F; z2 N$ t. `' ]+ f8 H& U4 S" \( a* M
很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
1 T& p* j! u9 R y1 z0 @一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。
/ Z5 n5 N6 o: O5 C, P
8 f3 L2 v9 g0 M8 o/ a: R+ n- Explain Select * from table_name where col ='nam' and col1 like '%name%';$ C* E% ?( ~2 b( j3 @! }+ i2 [
- Extra:using where
复制代码 该索引不能覆盖查询的原因:1 \5 y% G6 } F8 h1 x$ |
1,* @) h6 G- G, ? g4 m2 v1 {- h
没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。* C$ g0 G8 m: p5 g) U4 \
2,* f2 }& J- Y# u' z- ]" ~7 Q
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
9 u9 ^6 k0 J7 w# e% Q0 a有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:
' e z# ^! H y
+ t. F, ]5 j* w* {! \4,为排序使用索引扫描
1 ] Q9 N; W# i! E9 Wmysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。+ f( j: |4 V( a3 c" K8 {2 t
explain输出type为index,表示mysql会扫描索引) P( A0 v' ]% E
* e ^/ f3 Y" U扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.( t6 N, k4 o6 }( Z
2 P( f, Z( z# n3 D* bMySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。
! A, L: x9 |3 i8 \% f
+ k2 U' A$ [# b1 ?. M7 _按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。# ]" y( h# I# q/ t
1 N3 I* I+ g! S* k9 a: QORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。8 Z5 V1 C/ u$ u6 Z4 Q9 M6 @
( q9 _3 a0 h1 ?6 c( t使用join可能情况会有不同
d* _1 H- j' x( k7 M) }+ N B$ H" |. Y" x$ r
5,压缩索引(myisam)
# o9 x' D, R& X# d/ U: t& r6,多余和重复索引(应该避免)
# p1 A1 ]9 |3 d6 @4 T9 J7 L
9 R# {" Y) Q# f% z/ P多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)
- j3 R: |* J. [! }3 T上有索引,那么另外一个列(A)上的, B1 Q7 t% X* i: V H" l
索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
, Q8 \ t" Q/ x+ n1 d& E, V6 g然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。
t7 t5 S3 C* Z* J! Y
) N% C6 f, B+ a( k ^ g& W要点:" N8 M4 t# v' [& p/ R) e
在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.- H; P: w3 \/ C0 e* c
6 J F8 T. D K( ^/ X. |. ~1 R
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行: s! S% j: S6 X9 C7 @, @
3 D" O+ }9 ?. s5 r! F; x! y0 Y
) _: G! W; k) C( h, ~5 M
Y" w; N# J% h# Y, ~ |
|