|
|
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能9 f: m0 o6 z# W
有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成
`" t/ {% V* ^/ s. R一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列
0 T" E* ~- w& P( l! w: _/ g' u在InnoDB中,只有事务提交后才会解锁! c8 }$ d7 t7 w" Y- i. N
- W* ^1 |1 r6 y索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只6 W' `4 V. C% Z% X7 y2 p$ q
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。+ Y, k! H3 D, ~( B5 R
4 I1 @" ]& M% {1 T) B1 WB-TREE
. A3 d6 Q6 C0 P' W4 _能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找
3 n4 l( W1 ^9 ]9 F* V% K- _使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。 E" n* |9 e! `5 M- }8 l
' U( x7 c1 @# c9 ^! _2 ~" e- CREATE TABLE People(# N5 x8 }* W2 g( C& R% F$ s
- last_name varchar(50) not null, O% C0 }& n) o- |( i' @$ A# z& f
- first_name varchar(50) not null7 h9 z- e$ ^3 J, p7 @
- dob date not null8 q8 k" Q# o6 V# N, R! b' X$ j% F
- gende enum('m','f') not null2 A' P5 \4 C: r- ~
- key(last_name,first_name,dob)
复制代码 匹配全名
: t2 M* b1 |. `. D0 f+ }8 Z% Q全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。
4 O9 N: I; n" x+ S" S% ?的人。
4 |8 i+ h& q/ d+ B; S匹配最左前缀
! h" `6 G5 B5 l7 D0 `# e6 AB-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。
$ x1 S+ t4 {- {4 f) U5 \匹配列前缀. [9 c# ^4 q% I/ Y# p& k
可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。1 Z/ C, y8 O" r
匹配范围值7 i5 R: @8 C6 j
这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.
! F3 B. W/ n C3 _( ]精确匹配一部分并且匹配某个范围中的另一部分
) g, K, n2 z6 O& f# q& L这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last& n$ X9 g! ^6 G7 v- l+ t S
列并且对first name列进行了范囤查询。$ Q K- k5 i1 X; b: B' Z, q2 r, Y
name
, w }/ K/ a* |: W, y只访问索引的查询( E$ w' _% n h2 N7 R' r
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。2 D& @- ~: `2 U: l: s; G) t. v
) z" }+ r4 b: S+ X" d由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,
" r$ o; J% `" W如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查! r% H" }8 @; _' k3 k; I
找方式也可以同等地应用于ORDER BY。9 e @% T! S. }. o- {" i
0 R# i1 ^( T g' R
下面是B-Tree索引的一些局限:
+ m( m4 c& M6 R, v* I u( Q( D3 c/ k0 H; H2 |
1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,3 Z, l1 h C0 R: X( d8 w
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓
+ i* A' @' j7 h7 K/ Q4 H氏以特定字符结尾的人。
, b8 G# A$ b/ I5 i2 \( v) T9 d: M9 R8 C, F
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定6 ~7 O2 ?. }2 J% {/ a" {- o
义first_name列的值,MySQL就只能使用索引的第一列。8 z# S, O/ `# F; J+ ~) ]
+ n( B1 F( C2 d/ N) r
3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
9 L- x8 S/ B( W7 d范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而$ \& O& Y; v+ H: J
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。2 J3 Y+ Q; p- s/ Q8 T. m e$ [
0 P% @1 K4 q! l3 a/ {哈希索引,空间索引和全文索引等,暂时没有设计7 S; P! ^5 [( A
n% e( _/ F5 O1 n- R高性能索引策略: U5 O/ I7 t6 K1 K
! z& Z" d5 Y! e1 r9 A- |7 h9 c
1,隔离列,意思就是不要对查询条件中列进行计算等操作/ b5 E- Y5 R4 y. c
2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引
5 v0 d" F/ z9 G' u% \ USelect count(distinct 列) /count(*) from table;
# A, N' B' I3 R6 X3 v {看看这个值时多少,如0.0312
" ^$ Y9 r+ h" \, m. ?2 o- y7 {那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算
* t% [' `* i9 N; Z1 ~,这对于大表很有用。
! @, e3 R2 m% M/ K- dSelect count(distinct left(列,3)) /count(*) as sel1,
! Q$ E4 I$ w1 ^/ ~, B& [! L8 L count(distinct left(列,4)) /count(*) as sel1 , q# l+ a( H$ }' i; p* S
count(distinct left(列,5)) /count(*) as sel1,; A( Z+ b2 q# l5 B$ a
count(distinct left(列,6)) /count(*) as sel1,
, m! e% h4 Z% X count(distinct left(列,7)) /count(*) as sel1 from table;3 j4 y% y3 t0 G) p0 y
找到接近0.0312即可。
$ } T$ ?4 {/ |
, W. F# j8 e# b* x0 X* ~ JAlter table table_name add key (列(7))
& h8 l! Z6 W D) n3,覆盖索引
$ D# W% p. ]5 H- d: v% j$ p包含或者覆盖所有满足查询的数据索引叫做覆盖索引- R1 f% L6 R. c
explain时,extra中的会显示using index+ a8 V. f' \+ j4 m7 S5 s+ ]
这里一个重要的原则是
5 Q2 K4 `$ s/ b( Oselect后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列* M9 i2 M; F# K9 c2 i' m
如select id from table_name;
! [& T2 A$ \$ s$ h
8 I0 i0 H7 R8 H8 D2 m很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
! k+ N( _5 d8 v F一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。# U+ h3 ]& V3 _: u$ {7 Q. Y: g1 ~
) l f# J- D0 X4 }+ v: Y+ M
- Explain Select * from table_name where col ='nam' and col1 like '%name%';
1 ?* ~9 u' ]$ b - Extra:using where
复制代码 该索引不能覆盖查询的原因:# G2 d8 x+ p9 S/ I$ s' M
1,
5 t4 Y' ?, F' }0 Z' q没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。
3 h4 S6 n! \; u* O$ _! o/ m* C2,
. X' f! W+ i. q! K& o4 DMySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
! d% I { F( R" i; @5 A有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:3 J4 |8 z8 J3 w2 N3 j y1 k
& s" x9 W2 k0 G
4,为排序使用索引扫描+ \8 ]4 p# w+ j& D" B' N6 a
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。2 @- v7 }4 F+ n+ g: w) j5 J! d
explain输出type为index,表示mysql会扫描索引
5 p' b9 F1 J; {. V8 c. G" A0 N% z' l
9 J4 D& F. e1 X$ S扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.
+ \ T# ~5 D0 Q+ m9 J
& ~9 L% M3 d- M+ {' xMySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。
) g) m* \* \: X7 O+ K7 d6 z
# _: e) r6 X. _9 p按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。
9 k6 }+ j, c$ a# m3 l, J9 c* N, U% r# R
ORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。
5 X S5 Z6 Y# ~3 C
8 h5 g* T8 u2 I3 d- r- a3 U使用join可能情况会有不同' _' }4 S$ t7 [$ U; ^) ^0 q
3 l: l9 { o8 b/ k' `2 M: D5,压缩索引(myisam)
; Q% A8 F! M. X6,多余和重复索引(应该避免)+ v( [% ~7 U. @1 G) Z( ~, _
# n( R. x" G( T6 H
多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)/ T4 M. z4 P4 l7 E
上有索引,那么另外一个列(A)上的
$ k$ N( ~" ?: R2 D索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
- ]7 u) z9 j# Z/ m然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。. s4 u3 n* s+ M+ V6 _
_8 e9 _5 G' }6 |% P) @" @
要点:
0 \) o, X, F% s" o6 n5 a在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.
" _6 J0 N$ h' h1 _7 u4 O0 G7 F0 g/ K% c3 i2 Y$ R0 _
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行
$ I- Q, }% [5 s, A# O1 l3 I) R* F4 H ?, u4 L
; V N. S" i! Q) a# V4 U5 T4 E- r' V
. m/ Y7 m2 |1 z3 u4 |
|
|