|
|
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能
* Y; e: u9 r2 _. @& A8 m# n; G有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成
" d/ }( \9 @' [3 p& ^, B一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列; u' r# E/ r1 V2 H6 C. F
在InnoDB中,只有事务提交后才会解锁
1 [. U0 A {' I' `" |
# S$ T1 k3 D. C' E2 Z+ i4 V5 {索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只! u# a v& J* ~7 E
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。2 D5 ~: K& i' o
4 v% ?, i# M* F+ S
B-TREE4 T1 a& S" I7 X0 e5 o7 Z% @4 f. X9 t
能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找# t4 n8 Z( G! V' {7 T% i. N
使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。
& y' K& \" T, m9 e- ! h% E3 W6 \# l# V8 r Z# A
- CREATE TABLE People(1 w; M$ f E8 u2 ~
- last_name varchar(50) not null1 k) Q; j. V# B c
- first_name varchar(50) not null1 T7 Q3 v( x/ s4 F
- dob date not null8 q8 g& n2 t8 z7 h/ ?6 _ J. m
- gende enum('m','f') not null' c2 ^' A2 r8 G3 k% T
- key(last_name,first_name,dob)
复制代码 匹配全名+ z& j4 B C3 p( C7 T2 ~1 v2 I
全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。
7 f; x* C- I/ z2 d2 H的人。1 \! ], D6 H/ } ~ a8 {3 R
匹配最左前缀6 m9 h# E8 L# I$ c# J
B-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。! l- G: B& O) f8 b8 s$ O% y
匹配列前缀
& d8 n `( i; Q; n2 q可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。" E+ n& a9 ~- |! K6 d
匹配范围值* _# p* ]( }2 W9 A* E* `
这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.
& K* o( L. j, E: G6 g0 ?' A) s精确匹配一部分并且匹配某个范围中的另一部分: ~& @8 o" [1 T2 w4 d- w
这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last
/ c% M1 N4 w/ |4 j0 c列并且对first name列进行了范囤查询。9 ] Z) ^0 U" T4 [6 ?) v# `3 A
name6 U0 ?3 Z7 m/ H; ?1 h
只访问索引的查询
; v' Y! u' w, J7 kB-Tree索引通常能支持只访问索引的查询,它不会访问数据行。
- G# u( {1 k6 u! l: x7 @: R
' @' S8 [ D/ m9 d% k, j7 J7 ~由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,& c2 M% P- B# k) e* x
如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查
& r$ a& t1 z; k. y, K6 E0 O6 ?0 i' C+ r找方式也可以同等地应用于ORDER BY。* ?% K; X v$ p9 j* G
5 i- m4 F! [3 l
下面是B-Tree索引的一些局限:
3 v5 L+ |9 |4 p) R% M
: M! q9 c/ B! G: c9 j1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,& Y( w. {, C% Z/ D9 Y
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓
& {; ~; o5 C& n氏以特定字符结尾的人。, _! J3 f# D/ `, E: g( X
" ?/ y* _* z; E7 _2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定
* D4 k' _( P. O4 z& I( N义first_name列的值,MySQL就只能使用索引的第一列。
! c, D. h4 D* A1 i0 m/ @/ a7 e% U$ m9 E
3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
) s! `4 D: W7 a& R/ U1 V" h1 H" w范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而
/ ]2 T1 k o; S4 U' i/ i. K0 C4 k9 W不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。
' E6 z* c# s3 b! z, h0 L% E$ v* @' r, Q5 l9 N# i
哈希索引,空间索引和全文索引等,暂时没有设计
( a0 D% C$ t9 M8 Y) Z8 K% R
( x* {1 t' b8 f& o! N高性能索引策略
/ ~' N7 a* K4 U" p% u- a
, `/ t' x6 E) c2 j4 i1,隔离列,意思就是不要对查询条件中列进行计算等操作
; B* O, S! s0 ]' ]2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引4 L" p, D) L" n: ~! D2 b
Select count(distinct 列) /count(*) from table;
3 z2 {& C( q5 h& {" R$ @看看这个值时多少,如0.0312
/ a( @+ K: o0 c! G0 A那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算- t! X# @5 P- q' s& D/ {
,这对于大表很有用。. R, e2 H: V2 Y7 G% G9 I7 J, U4 D
Select count(distinct left(列,3)) /count(*) as sel1,- K( G, t o& M
count(distinct left(列,4)) /count(*) as sel1 ,% K1 W. y6 V- G0 w; _) w
count(distinct left(列,5)) /count(*) as sel1,0 X3 i' y6 B% _$ I! M) } J
count(distinct left(列,6)) /count(*) as sel1,2 O ]8 G6 _: d7 Y% P/ L
count(distinct left(列,7)) /count(*) as sel1 from table;/ |7 p8 {, B S3 E2 w
找到接近0.0312即可。
: F* i' ^# P$ U0 g7 \6 s: s9 O! R- x/ m @5 r) x
Alter table table_name add key (列(7))
0 S% v! Q8 X I5 ?3,覆盖索引 B C( j y% ^2 r$ G
包含或者覆盖所有满足查询的数据索引叫做覆盖索引
) Y$ p- X: X9 Q9 `explain时,extra中的会显示using index& U }3 W: S6 V, q9 K* [
这里一个重要的原则是
V' y; K8 l( W0 m0 e9 iselect后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列0 F+ Z+ @. i; M- i" }
如select id from table_name;/ j9 T; ^/ D, g! l E
* u# h B {! T' p
很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
; y" ?. u. m( V一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。
6 |4 _6 D; g8 m2 b" X/ R9 I) N
# Z7 k3 C2 N3 c/ |5 F7 \% t- Explain Select * from table_name where col ='nam' and col1 like '%name%';3 I4 C3 V; B* r: V# \' f. e
- Extra:using where
复制代码 该索引不能覆盖查询的原因:1 Y# h6 j- v5 C4 k! T; l! u% t
1,
2 v) W' P# w! j" ?1 C$ d没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。+ L- O) G- Q, m7 F D
2,
. K$ {# M6 |" C# aMySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
0 ^1 @* ?/ P# A% x; `( ]有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:
# g6 k; s: ?! U6 g, t7 n7 s' |& S$ G6 P( U
4,为排序使用索引扫描
! b; M: e5 z+ N Pmysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。) k ^( a% P9 A/ I) L6 o1 p- @5 B% G
explain输出type为index,表示mysql会扫描索引
: r# j. f( f- m8 j# f! D- ?3 V+ @, p) z5 L5 D$ o% W, q$ P( a
扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.
6 I. V9 W3 Y+ ^2 G3 G0 l! C- c2 h; X" f1 G# J7 D" D
MySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。: f% o: J; k) `
9 i+ q, N2 C- y( c, {1 D6 g7 }按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。
; `$ \2 u2 v& t9 y; Q' _# U% z1 t( O6 \! Y0 ?2 V$ }, a
ORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。+ \+ o; y1 ~" Y5 u c9 w1 ]) D7 \
5 |3 x* |1 I+ ]# o使用join可能情况会有不同
! h: U7 ?: i+ u0 R% D8 `
1 J: m0 X" R# T7 c; ^5,压缩索引(myisam)8 j, ?: X* r, s
6,多余和重复索引(应该避免)
4 \( W. o0 T! A
% Y; n! V" a1 L: q! j7 x( S多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)) j' {4 ?: G9 l, d+ U6 D
上有索引,那么另外一个列(A)上的
. F2 w# V3 `& Q索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
& G. l- ^7 }, C7 G: Y然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。) z. |2 R( @ Q d
3 ?; X( n4 w$ \1 O0 _. i" A! V要点:
5 W3 H& R; T/ Z) {4 I5 O2 k# s在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.
8 L1 M& d9 c7 w3 Z; g6 N9 e6 ?" z3 s9 E$ z
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行
8 E( d5 o O9 @- ~+ e
9 y/ m1 O, U/ k
7 w1 n2 ?$ y8 } @8 |; n! \
* b; D1 p. h! b$ b2 x5 o8 R |
|