召隆企博汇论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2756|回复: 1

MySQL索引详解和优化技巧

[复制链接]

24

主题

5

回帖

199

积分

公司现有员工

积分
199
发表于 2019-12-9 11:33:39 | 显示全部楼层 |阅读模式
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能( h- ^7 X) m+ [) U( c7 r
有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成: \" O( E2 C, f: o! z$ p) n
一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列% l; M! m4 P& p. [- }" S+ d  T
在InnoDB中,只有事务提交后才会解锁
! @! R1 x& P4 B# E! @, v8 v7 L
8 C6 D, d1 u: E索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只. e; e  O( a: y. m$ z2 l9 m
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。
5 _# Q  r$ W. f- G+ o8 o7 r
; _3 ~( [, k( |* T# BB-TREE
5 g  k, w5 x& y: |/ a: \2 z) s能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找
/ a" ^3 C- g! Y* ]9 p2 K# B使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。
8 z) ^- u# `* ?9 n
  1. 4 P( ~. h% ^' |. S1 q3 d. G. _
  2. CREATE TABLE People(
    * d7 p5 F2 m- c) r2 q
  3. last_name varchar(50)   not  null
    9 D8 g) {3 A, `4 c; T5 ]3 W9 Y1 z
  4.           first_name  varchar(50)     not   null, L2 f$ ~- W0 g# S( m8 M& `
  5.           dob  date      not    null
      E0 y7 l9 [8 `4 T: F
  6.       gende       enum('m','f')    not    null
    ! i3 q. l( F7 Y2 V# f9 ?
  7.         key(last_name,first_name,dob)
复制代码
匹配全名; Y! D2 T: a) t, }
全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。
9 T( }1 w. n9 }/ ]. Q的人。3 x$ ~& `5 [( u% ?6 q, _  P
匹配最左前缀# x0 r9 W8 {* R( _$ w5 G8 r
B-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。: {- C* h  N% Z8 w) `, w: b
匹配列前缀
/ p8 G1 Q/ i2 Y4 |; t% e/ Q可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。
+ ^2 C( b8 g9 O, E. Y4 r, ]' Q匹配范围值$ i- }) B! Q( @$ G! y! w& q3 P; a
这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.: e% L! E  p$ c+ t, X2 f: @
精确匹配一部分并且匹配某个范围中的另一部分
0 {: C3 v, M3 \! G' A  G1 E- E这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last! q3 [- p6 D- J' z% J
列并且对first name列进行了范囤查询。
' U- [1 p; l4 J; Y+ Fname
# s. R8 r2 S3 ^3 n- p3 ^+ Z5 Z, V只访问索引的查询
0 i$ i+ U) l2 BB-Tree索引通常能支持只访问索引的查询,它不会访问数据行。+ S; _  E  b% B, U
8 Q6 |+ L* g) ~* F, I
由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,
, H& C/ ?9 d1 T5 Y" d如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查
+ n9 B+ `/ s/ g' s找方式也可以同等地应用于ORDER BY。
% C3 {+ g# i3 z; k, j! e: C  m  L' }8 J2 l, n' p
下面是B-Tree索引的一些局限:" a) M: w& _- j6 R6 V

8 @: H6 U: z5 U. O; _5 y1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,& w5 i) X7 B  o: w
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓/ X9 J/ B9 i1 U8 Y# C2 L
氏以特定字符结尾的人。) }5 V; M6 t! v7 i( w- _

8 f' t. B1 Z( d0 \5 g9 o2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定: M* M! U/ q( c- y5 {, a4 l1 j; F8 z4 ]
义first_name列的值,MySQL就只能使用索引的第一列。, o; \$ ?( I6 P' o: l! N! J' |
, T2 t3 k% W- m/ a
3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
# R- Y+ {3 y# O9 V范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而
$ [: M, z, l1 J6 p不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。4 Q# m( ~- h' g3 b

1 p: I2 R" v% w# F$ J哈希索引,空间索引和全文索引等,暂时没有设计' ~: c0 Z" q" ^  c4 y# U6 M! j

3 |/ o; z: U# _5 b& V高性能索引策略( x4 D8 J2 Q* J& f

; [! c5 U9 n) b1 M8 ~% m0 g3 ?1,隔离列,意思就是不要对查询条件中列进行计算等操作
* A: T4 i8 K! Z1 q: Q) N  K2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引7 y6 ~. z3 I- i4 H. G1 U% ?
Select count(distinct 列) /count(*) from table;0 I  R% m, w. a$ f
看看这个值时多少,如0.0312; N8 T6 I( R6 i/ b9 t9 L
那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算0 t* V8 ^0 Y# ]4 a& L* C% `
,这对于大表很有用。# Z' K$ y* d: l2 [5 F6 z( C9 y
Select  count(distinct left(列,3)) /count(*)  as  sel1,
7 z5 f) s) L% _5 y6 u& H count(distinct left(列,4)) /count(*)  as  sel1  ,
3 S8 R1 |( A9 a$ _) R. Y, @/ O3 P count(distinct left(列,5)) /count(*)  as  sel1,
: ]  [6 B# V2 d6 ~ count(distinct left(列,6)) /count(*)  as  sel1,( b5 q% s" d9 V9 X; z- R8 V
count(distinct left(列,7)) /count(*)  as  sel1   from table;6 K8 G8 S' e9 O
找到接近0.0312即可。. A, p" Z$ s6 s6 o" J9 h
& o+ {2 ^  }+ _3 M* n3 D
Alter table table_name add key (列(7))
6 C# u% E9 `: [: N3,覆盖索引: k* |! d: t* `' w; ~
包含或者覆盖所有满足查询的数据索引叫做覆盖索引5 k3 @* D( i8 `8 N  Q6 P# D
explain时,extra中的会显示using index/ g4 e0 _' v: u8 f2 H
这里一个重要的原则是
; ^0 o# r6 @# l; oselect后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列* Y- Q7 m3 ]5 w, G
如select id from table_name;) M: M4 _7 E- L" H5 @0 K

9 N6 c# \# P/ V  ^" g很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不0 ^- ~8 ^) h( i6 R+ ~; m
一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。, t: ^8 m' n6 z# H3 H

% U* Y* e5 \0 l" L" s+ c( _
  1. Explain Select * from table_name where col ='nam' and col1 like '%name%';
    ; U2 S; l  S  B" n4 {% ?2 l; R% g
  2. Extra:using where
复制代码
该索引不能覆盖查询的原因:+ e* Y3 U2 V! z( p7 ]  H) h* e
1,
$ r9 t, b8 e5 X* V没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。
7 G8 I# p8 c# G; [2,, S( x) E) R' W) G& G; E3 m+ A% \
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
' G' r, ~) d" p& ~' C4 _有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:
* o7 X1 p/ ^# k6 s- k3 f3 h- M' w) B3 o+ R8 _: Z
4,为排序使用索引扫描% a, W9 ^; d( l) I4 T
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。8 G4 z. d8 U4 K  X" }* @' k
explain输出type为index,表示mysql会扫描索引
- k. L( W' ]; X3 M- ?! ^4 `- n# }$ Z3 _; W! E. f, d: x' t
扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.9 C( d0 y# k7 ^% J  Q3 F
6 W. e; Z+ o( X2 M: h' t
MySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。
9 b+ K) v) {7 `# u: U6 \, a
! X7 y$ @; F% v按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。. i0 Q$ h- O  {; i8 k1 Y

/ M" r4 G0 G' ]4 ^3 [1 m$ SORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。7 V+ t! z* }: i$ q& f' V) ?' }* B1 I

0 L' _2 C  U; r使用join可能情况会有不同/ w8 J+ |5 N" ]
7 A; ]& f$ [5 H2 _) I0 z+ s$ K7 b! p9 y
5,压缩索引(myisam)6 G1 \1 H/ R. Z" Z! y2 _
6,多余和重复索引(应该避免)4 m$ n. M$ {4 O: L- a1 w
+ l5 `. A2 f5 v8 t0 {, w
多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)
  M+ e5 D- B& p5 m5 q上有索引,那么另外一个列(A)上的
: q9 X1 r4 w3 f( y7 ?  v( e/ i索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
3 L2 N2 l# P) Q( d; l然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。2 I; b9 j: N! f# _1 G
& v; b4 z, [! ~* r, s' ?
要点:
& n! b' u3 l8 a5 F; K, ]0 {1 W: t在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.
2 n7 h1 z: C# F1 Y' R( M* R: @5 l/ L' Q* _1 S0 a1 v7 P$ E
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行" \! Y8 ]9 @- b% z: s) T( t: w

( p4 S4 n3 l6 M/ R2 L# u8 I8 n! X
8 q. P1 Y, A5 _$ S- s
4 z) W. X  b- T% Q3 @, g
回复

使用道具 举报

24

主题

5

回帖

199

积分

公司现有员工

积分
199
 楼主| 发表于 2019-12-9 11:34:27 | 显示全部楼层
创建索引时,: }8 a' L: Z2 M+ |4 G3 n

" x8 }9 e/ W- Y, Z3 |$ _拥有唯一值的列选择性最高,那些具有很多相同值的不适合创建索引
9 Y& {: |7 m; U6 M
  t- x4 p. Q  }: L9 }
7 e$ \. x( t# ]) u
0 }* i- @# ~) y2 D) W8 `一个通用的规则:保持表上的所有选项。当你设计索引的时候,不要只想着已有查询需要的索
! L$ l4 y% s3 q' v5 q% r  f9 F
# s0 V- C6 `- j4 r引,也要想着优化查询。如果看到需要某个索引,但是一些查询会因它而受到损害,就要问问自己是否应该改变这些查询。应该一起优化查询和索引,以找到最佳的折中。没有必要闭门造车,以得到最好的索引。6 n" R' |6 k6 I% J2 m  @. Z! W2 ?
8 U8 p. d3 d5 X: r
) a5 }; W6 ]/ l3 f* U

5 u! a# m7 o; r! {一个在多列上面的索引,为了是这个索引生效,必须满足最左原则。
9 _9 N' O2 u( O0 S* b' a' b7 d& g8 s9 ]
例如inex(a,b,c),这个时候如果只是用了a,c。没有使用b这个时候就不会使用索引。怎么处理
0 }! Z* X2 J: y/ W  D9 M( x! I$ f
  |5 u" D3 w+ M# @1 H% N$ S这里如果b是一个可以枚举的类型那么可以使用in(…),将b全部列出。这样相当于b没有起到筛选的作用,但是却可以是索引发挥作用。这个方法也不能滥用,因为会出现n*n的结果,如果枚举数相乘过大,应该选择其他方式
7 j/ J( g4 J5 t, R; x
$ {) y8 a" I9 K2 B. L4 p$ F+ t2 Y  ?4 B' w8 y  c0 v  D

: w7 ^4 Q8 @) T/ Y避免多个范围条件,只能对其中一个使用索引5 s6 h/ ]; M5 k7 o

8 G, V1 C5 V# y8 ~3 I* e+ {* z/ O0 j- L0 v- R6 C: r
- b  d) H1 s  x6 T
索引和表维护
2 p# W9 D! o$ c6 p$ l! _6 b, W! D1 Z2 G5 D: }
表维护的主要目标:查找和修复损坏,维护精确的索引统计,并且减少碎片.
1 b+ @/ s2 \( ~* o4 |0 l8 a& H
check table table_name;
! |' z% k4 y8 ]7 \( Grepair table table_name;  C/ k4 }6 T% \# L2 M
Show index from table_name;检查索引的基数性
( J) n  u; R4 V: u+ X( X+ v3 t2 ?! q# z1 e- }
主要关注cardinality列,显示存储引擎估计的索引中唯一值的数量
! J+ L! m1 e8 N, D3 ~+ |3 o$ }1 W" P6 h6 }1 @

4 S; i5 m2 B' W5 x* K' J0 O# _9 i' C) y
B-Tree索引能变成碎片,它降低了性能。碎片化的索引可能会以很差或非顺序的方式保存在磁盘上。
: h) `  D3 j  }) M0 Z* `: A/ Z1 U* P4 X+ ]6 H/ M
表数据也能变成碎片化。两种类型:
2 W& B2 F5 H) v- y" z8 f( t9 K+ |0 g( g* g% c5 @1 P
1,行碎片
0 i1 K' A, ]3 \. I3 S5 L! E9 e3 }2 f- R8 b" `! g$ u/ t$ M) Z5 ~
当行披存储在多个地方的多个片段中时,就会是这种碎片。即使查询只从索引中找一行数据,行碎片也会降低性能。
" z% n& i& |! x6 v7 h
# n3 {; g, `0 O8 ?  Z8 Z
  m7 \; I3 A, x# [1 E% ?5 }$ W% M  N' s0 v2 P0 \; B
2,内部行碎片& n  v% m% k+ R) [" l, ?' o2 u
. }  W1 J! l8 f. E, I
当逻辑上顺序的页面或行在磁盘上没有被顺序存储的时候,就会产生这种碎片。它影响了诸如全表扫描和$ `9 r" Z, g) S7 x4 R
: w6 i" g  Z( `+ ~9 Z) V
聚集素引范围扫描这样的操作。这些操作通常从磁盘上的顺序数据布局得益。
8 }2 R( ?: t. s
  }+ }3 ^' P& e2 q+ L- M, |+ P, ^" o& u  X4 [: A

4 }7 u& Y9 F9 p, V) |为了消除碎片,可以允许OPTIMIZE TABLE或转储并重新加载数据。
7 U# u1 z8 k$ F9 x  b0 L  j
# ?- U! m- v1 T7 v( l. z
$ A, g7 ~' ?; b' d! o0 n2 r& ]3 X& {: E% n. B
ALTER TABLE <table> ENGINE=<engine>2 D) j7 J* {3 W5 ~4 ^' p) X2 q* l3 O
6 ~, |3 U, `1 p5 f7 L

' I# L# j$ c6 O
3 C/ D2 T. U& A# e' u) H加速ALTER TABLE) l2 O6 n+ s5 F: g/ |& d
. V/ D' B& G' j

( S- ^- Y1 j. t( W, o' x' C* u, B, q% _- c% ?: G
MySQL的ALTER TABLE的性能在遇到很大的表的时候会出问题。MySQL执行大部分更改操作都是新建一个需* G7 L# T; i2 A6 l' \5 Y" d
  P: e- y  z" b! T" C3 o3 ?3 q" k
要的结构的空表,然后把所有老的数据插入到新表中,最后删除旧表.这会耗费很多时间,尤其是在内存紧张,
3 K3 h2 ?; d) N
4 r( W9 x  Q, h2 m而表很大并含有很多索引的时候.许多人都遇到过ALTER TABLE操作需要几小时或几天才能完成的情况。
$ w3 Q$ `# W0 V( D6 _
$ b( s3 X# \7 W  L4 `5 l) d" ]传统:' w+ Q3 s5 a5 c& r
% S  n: w0 G1 i" I: Y# G
ALTER TABLE table_name MODIFY COLUMN col TINYINT(3) NOT NULL DEFAULT 5;
0 i( b9 F6 s3 |& k( N) l: ^0 q理论上,MySQL能跳过构建一个新表的方式。列的默认值实际保存在表的.frm文件中,因此可以不接触表而更
4 [/ O9 D8 p( r( f7 r  O% L6 v改它。MySQL没有使用这种优化,然而,任何MODIFY COLUMN都会导致表重建。
# \6 T* _! {* A3 t6 q8 c- o# g( K) y. l4 p8 a& \8 f# N% o$ R4 C
变化:* t9 |+ @5 [5 O1 S5 M" H# `
! B- a+ F1 _7 [" b' C3 N
ALTER TABLE table_name ALTER COLUMN col SET DEFAULT 5;
" L% C8 |4 t  i) T$ a这个命令更改了.frm文件并且没有改动表。它非常快。
/ q% }+ \2 n# K1 K2 e% a" j, q还有一个CHANGE COLUMN
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|召隆企博汇 ( 粤ICP备14061395号 )

GMT+8, 2025-12-31 04:23 , Processed in 0.041635 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表