|
|
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能# O8 m2 P0 t% f* p* P$ X
有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成 K5 h# Z% O/ Y/ l! S: g7 q. i
一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列( w1 ~8 \5 {' b+ _* K
在InnoDB中,只有事务提交后才会解锁& t8 w: V% S9 t8 [! n
0 z! G9 A N7 o' j9 y P' C索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只2 y3 B8 B' o6 F
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。
2 X' Y5 v* _9 D3 p" o
7 W5 ~7 K/ ~3 OB-TREE: P; o5 Z( Y- a/ ?1 b1 d% [
能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找, _) B4 c9 t0 `* {+ x7 m
使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。5 h) i0 I2 n1 a5 A3 k. c& S
" f9 x& w( z; I7 R2 l- CREATE TABLE People( [8 C# z4 h; E7 P9 S) q# R
- last_name varchar(50) not null Y& E. |# d+ w
- first_name varchar(50) not null
9 f/ E" r3 E J9 B$ m y - dob date not null
. q& M+ M: ?) A) H- t* s9 R! n - gende enum('m','f') not null( z* e( p( k9 [- R3 c
- key(last_name,first_name,dob)
复制代码 匹配全名4 h S5 J+ u7 e; D Q7 |. [; Z
全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。3 G1 B( J* c2 d
的人。
r8 W8 L' S' F) l! d4 ^匹配最左前缀( e( q9 S9 [* h% ?7 p5 A1 M3 \
B-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。5 H$ u9 \4 @# G
匹配列前缀
) A; g# z0 F: k8 R* Z9 v可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。+ Q5 U; R# D' k( }% E0 C7 w- E
匹配范围值
% `4 o% _, H( H+ O; H: |6 f; a9 Q这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.
, A- o: ` N: K& Q: c* j v精确匹配一部分并且匹配某个范围中的另一部分
6 a |- @6 F X) `+ V2 G/ W. y这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last
+ U# ?7 t$ B0 C6 B) Z9 |( N/ J/ y列并且对first name列进行了范囤查询。# R! T' ]3 ~0 e% I4 _
name
4 c; ~: m. I5 Q; P只访问索引的查询+ D! A" g& z# ]& j1 B
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。
; z- N3 i8 d, ]" R' z# `) f( b' v. T1 f) v9 l
由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,
. [- I4 D, d9 x+ w. g如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查
4 G/ ]/ ^) W* ^: A- P! t/ N; Y找方式也可以同等地应用于ORDER BY。5 R8 {. L& i$ U; ^. A) P, b6 x
2 L9 x- [" b4 K$ d9 o4 m" B
下面是B-Tree索引的一些局限:
+ b4 ^7 j' B! z6 ]# w( f, V/ q% G# h5 \8 C/ L; S
1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,2 {% S* C; Z! p n* ?! c3 F
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓
- M" s8 e$ a$ {& L ^- u5 y7 \氏以特定字符结尾的人。
1 S" M* n3 R1 i. B
+ e* z& t" S0 O0 j9 t# q# L. R2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定
0 r. M6 Z% J+ A. W; k( R义first_name列的值,MySQL就只能使用索引的第一列。
, Z& W' S: j) E8 m& i0 K' y+ I/ y- t/ { P' U/ a. [" `
3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
' Y, r0 N/ s" Z9 x* J$ A4 l范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而, D$ ?2 F" u( e d
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。 b e3 x" ]+ ^( p# H: ~
& ~' h# `6 u4 T
哈希索引,空间索引和全文索引等,暂时没有设计% b* n/ {, F- k2 Z' a8 I: E0 U
8 w% s% L T, `
高性能索引策略- G. k9 o7 {) n+ Q6 A
1 h, [- o. \: _) c4 i) d
1,隔离列,意思就是不要对查询条件中列进行计算等操作
; e& @: H* S6 g5 }2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引" @/ C) ?, P3 X7 Z5 g! E
Select count(distinct 列) /count(*) from table;" d1 ]% }7 M$ `6 z' G
看看这个值时多少,如0.0312
" Q) M8 c6 x' d# f7 w6 }- N! k那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算+ x- l8 {& p7 f: X; B" o& O
,这对于大表很有用。
! A2 Y3 b0 i0 g! `Select count(distinct left(列,3)) /count(*) as sel1,9 \8 x0 z! Z1 X8 e* V% V
count(distinct left(列,4)) /count(*) as sel1 ,
" U- p" M5 \2 Z) k. ] count(distinct left(列,5)) /count(*) as sel1,
: [0 `0 e: P, e1 ^ count(distinct left(列,6)) /count(*) as sel1,# O$ G }5 I6 E! `- a
count(distinct left(列,7)) /count(*) as sel1 from table;8 m- D( t8 f% o! x/ `
找到接近0.0312即可。( ~/ W3 V; A* }
, D8 T! C" c4 h" iAlter table table_name add key (列(7))
s, e8 D2 @" l$ a; k, g7 I3,覆盖索引
5 m9 d# g4 C7 U# m: F5 `4 ?: H包含或者覆盖所有满足查询的数据索引叫做覆盖索引
2 W/ j# o6 g9 {explain时,extra中的会显示using index
6 q" L( b P. o3 n5 O( D: H2 ^这里一个重要的原则是" ?8 n. h& l# ~0 N
select后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列
! ]5 ^8 s" g" e如select id from table_name;
8 J6 w, s9 J3 g: r$ N$ R
( ]3 s5 f% O5 F很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
4 U1 T& t) ]! U. G3 R0 S8 r一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。
! z% ^$ l5 q/ D) X0 r) Z7 ~! t( o; n) w4 h% ?
- Explain Select * from table_name where col ='nam' and col1 like '%name%';
+ z2 M! q' j! U - Extra:using where
复制代码 该索引不能覆盖查询的原因:
: r, t& | Q, U1,
6 `8 o8 C# K- R. f/ }4 c3 [没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。
- w& H, e' j; b1 P, v( {" j2,
) y7 p1 N% S/ H- U* ?MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。$ w0 H a8 [1 p
有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:
$ `* o9 t: p4 m" l: r
0 w# }! I% L+ n' _. [6 h4,为排序使用索引扫描! b6 }% [* j+ R" o
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。
6 ]. ?& u/ ]6 j/ ^, ?" Eexplain输出type为index,表示mysql会扫描索引! Q6 O7 R ]. ~( p5 B6 E
" |3 [. _/ m) ~6 U6 [' U
扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.
- K$ \+ A/ T8 |& [7 l! r9 ^, s, u
MySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。
9 e" ]3 ?/ M! D
* c! I+ ?5 K) G: n' A按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。
4 r/ a( L" Y+ x9 u" @! L
, n+ }) y$ A; a# QORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。$ a( |: R" j! |: L& _1 G! l
: A! h M% i2 S. M3 i: u, ~+ B
使用join可能情况会有不同4 c* t% ]. |( t5 _
( m5 h, F% _4 X3 m0 N
5,压缩索引(myisam)
7 r9 x1 j7 f1 K6,多余和重复索引(应该避免)
- @, U/ A/ v; p1 @, i u F1 Z0 t1 q: Q0 y) G: T
多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)
! [6 f/ c, L) ^7 K e1 `上有索引,那么另外一个列(A)上的
8 f3 r3 z4 u9 e9 b7 b w: u4 |: P* J$ g索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)' @4 o6 t2 F( ?$ g: ?
然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。# W( @! k) a9 N
7 i$ h) \( y8 B* N要点:
7 \( c% e. }7 s4 r- z3 u9 o: Q在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.
8 u3 X [7 e3 u$ ]- K* V; `- T% R
& o2 K1 m" v8 X+ C即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行 h- e1 l* I3 Z( w. D, x" ?
3 m0 j3 `( C/ ]$ n' S7 g% w+ e# C0 T( ]! t% Y$ P& y7 P
9 j: \5 k# L+ k/ @) V |
|