召隆企博汇论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2788|回复: 1

MySQL索引详解和优化技巧

[复制链接]

24

主题

5

回帖

199

积分

公司现有员工

积分
199
发表于 2019-12-9 11:33:39 | 显示全部楼层 |阅读模式
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能- M$ u5 N0 ^" ?$ J
有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成
  A5 y3 t4 f  s3 T1 @5 z  Y: w! O$ }一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列
/ ~4 i- u2 \/ L( |! l在InnoDB中,只有事务提交后才会解锁* O0 p0 d* G- w3 g- R

% B; u) Y8 S, t1 r索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只% c* n: Y/ g$ B. U/ B% a' W# `
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。
3 R$ }) m( ?3 ~. q0 B( j3 @
. |4 }/ Q8 N. [0 hB-TREE
- S- P+ B) p4 D% C5 K能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找3 Q; _$ m% Q, `. ~( F0 k
使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。5 I0 A8 _5 g$ j2 w) A
  1. * S" X3 ~* r7 _% m
  2. CREATE TABLE People(7 c. l) d9 Q- i; d+ w
  3. last_name varchar(50)   not  null
    ' j5 F% g8 _: ~
  4.           first_name  varchar(50)     not   null
    , m. W1 |7 k) ^3 E) n
  5.           dob  date      not    null
    4 S' f; x; _- F6 b0 L* ?6 v
  6.       gende       enum('m','f')    not    null" o2 _6 B; F( s% T( Z/ E8 @
  7.         key(last_name,first_name,dob)
复制代码
匹配全名
' q8 ^+ l9 i0 c4 F9 b2 N4 n全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。
) t9 t: Z2 b7 E6 o的人。' j: w( `% f) |. w% N+ ?) ]: ]# x8 h
匹配最左前缀
9 Q( h9 V5 s( f* \B-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。
. ?. e7 n" z/ ?5 h- r- X匹配列前缀
. n! R: d; m+ I9 E4 {. H. m可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。6 H3 Z, Z: }0 G
匹配范围值
6 G  c# {. Y; \5 J' U& \4 t) o这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.
9 T9 A/ ]$ A2 p" d+ J& g精确匹配一部分并且匹配某个范围中的另一部分4 P% G* m/ V! x7 U
这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last9 a1 f1 M9 S6 `- d
列并且对first name列进行了范囤查询。
9 u6 f# z+ N7 C; f  u- G* v9 fname2 X. [6 A. A: ^7 f# H4 T8 ~8 h
只访问索引的查询
3 n8 T+ @$ c$ p$ p1 }B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。
  u; l+ A! p6 s+ t" A9 H  k6 K: p0 ~
由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,4 e" T' G  O/ ]2 [! [; h
如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查
/ [+ u/ A9 N, t* t/ R" A- y7 H找方式也可以同等地应用于ORDER BY。
! Y& z& \7 K4 A* ]  r. o
' ~" y% q* g7 ^9 ?6 F5 s下面是B-Tree索引的一些局限:
6 {" i) m+ r2 C9 ~; T
8 U5 V- R% K4 Y0 E1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,9 r2 i! D9 h+ X( j3 t5 _( k' i
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓
  ~) F0 ^/ Q" @4 ?2 W4 _* S$ A9 g氏以特定字符结尾的人。  g3 g' q' R# m7 z5 D6 f2 q
4 c1 c, r) [8 r7 @- O1 `9 o, l, Y( W
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定  K& s9 b5 H" |" u1 @
义first_name列的值,MySQL就只能使用索引的第一列。
& z' t) h( D% U7 ?$ b& b
0 C1 J. q( _* ~* v, A3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是8 T) x9 j: A! [! h
范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而: K* V2 M0 e1 t6 s* d
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。
, j$ n' s& w1 E1 T  [6 @) B' l  Q
7 H! y! a/ q) q' h哈希索引,空间索引和全文索引等,暂时没有设计
& g# `- ?% N: {. g  z5 u: q2 }
3 `8 J5 j0 X3 b# X5 J3 {, y% ~* _* q高性能索引策略
2 I: U  u2 C! d" j* R3 U$ H; G) D" ?
1,隔离列,意思就是不要对查询条件中列进行计算等操作
; a! h, U1 F& p+ H2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引
% a9 b/ z. v& A5 w6 \% k/ \  q3 b% ZSelect count(distinct 列) /count(*) from table;% x) k6 j8 f! w6 J& ?! \
看看这个值时多少,如0.0312- ?4 l  N/ a- Z6 A, }7 P
那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算0 r; C( V2 j: g9 g4 v2 a6 o6 R) V
,这对于大表很有用。  n: k/ |% V. [
Select  count(distinct left(列,3)) /count(*)  as  sel1,3 G' n5 @$ q  n/ n' ?5 H
count(distinct left(列,4)) /count(*)  as  sel1  ,! ]9 R, k8 u3 K3 W$ K
count(distinct left(列,5)) /count(*)  as  sel1,
% @% k$ S3 a5 e0 P' ~ count(distinct left(列,6)) /count(*)  as  sel1,
7 k# N! Y, d* k  g count(distinct left(列,7)) /count(*)  as  sel1   from table;
8 [8 Q+ ^1 [4 ]4 M: l7 L3 e找到接近0.0312即可。
8 Q/ [: p# M. |7 c" s* W
9 [/ h/ X' [+ PAlter table table_name add key (列(7))
$ Z; v; x  ~; W" o# |1 v3,覆盖索引5 K: T5 o# g4 @
包含或者覆盖所有满足查询的数据索引叫做覆盖索引
' M5 u( N- j2 Z- S& ^" Z  Bexplain时,extra中的会显示using index+ G6 d% ?: W' {" B" N# O1 H' z
这里一个重要的原则是
: d! K' D: \6 f% U: c% tselect后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列% ^! v/ Y# c; F+ Y+ k
如select id from table_name;% l; h+ X1 W6 m0 F

; i& _& b2 v7 v很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
( c( u) y$ q* Y, f1 K一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。7 M( v- s7 p4 l- {

5 N4 p, }5 c: g3 \
  1. Explain Select * from table_name where col ='nam' and col1 like '%name%';6 B0 p- m7 e) y* i1 s* i- B3 U
  2. Extra:using where
复制代码
该索引不能覆盖查询的原因:
, M6 P9 L& a0 _) {+ v& O/ K" ^1,
9 C3 g: }) g: v7 o& M没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。( D- M  v3 X  g; W1 L* K" E
2,
: i7 I# m! P; R0 ]# \% _2 e( s, v' o3 VMySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
- f2 {  c; @, Z# m有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:* M3 Z: I' y, O; K$ n6 M; U' g% e
3 E# K  }* Z' ^1 Z& b/ ?
4,为排序使用索引扫描
* E0 ]- J( P3 z" p& {mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。
: p' D3 D" Z' `" e0 m3 Iexplain输出type为index,表示mysql会扫描索引
0 P) y: ?& x0 i, L2 d
& o# u/ Y% S" v% _& i  A6 d扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.
6 c' g$ d( G# H3 Y  Z2 u
  E4 `( M( Y! ^/ D4 JMySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。
( h; n& e; `% A, r3 e
9 \, q# w) Y6 B按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。
) G* S; O/ V& H# \  x0 T! z
7 L5 h$ S" R7 U, }* EORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。
; m9 h- w7 j  B$ ^- n6 A* G9 b1 S3 ?' O" w0 X0 D. O. t
使用join可能情况会有不同2 R1 @6 K+ V4 n: X# X& q

* U' y( Y# {' w% y& ?# H* l2 y5,压缩索引(myisam)
5 P6 O. x9 e- A8 q+ u1 B( C6,多余和重复索引(应该避免)9 F7 s" R% [0 q6 e, ^

) t3 _; B$ a) _% Y# n) N多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)  t* d0 \9 b* |2 T1 @- k8 u5 V2 {
上有索引,那么另外一个列(A)上的1 A: y' K' |8 v  u( G$ X
索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)% ?$ C6 G/ I3 o0 t; c" v$ B
然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。& \: V) l, `  @* x% a
8 B) u( X% A4 Q  g# U
要点:) H7 B  Z6 o7 T* n
在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.
* H% z! s& k+ G, O! g# J% x
* Y' W8 f# w) w  C/ q即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行7 l) U# Z: S2 `$ @
3 h9 [- d, W1 A5 F7 H9 o
' R4 @6 O* F4 V/ J7 q

- S8 `7 n! s* [: C% w9 \/ u
回复

使用道具 举报

24

主题

5

回帖

199

积分

公司现有员工

积分
199
 楼主| 发表于 2019-12-9 11:34:27 | 显示全部楼层
创建索引时,
: }, P% V& j5 F
0 m+ C. k2 F* Z) `  M0 \/ ]3 Q拥有唯一值的列选择性最高,那些具有很多相同值的不适合创建索引: \9 w& j6 O5 c+ c4 ]$ l

( c0 D; B& ?4 ~- L/ ]2 u  w0 t& C  f6 D0 Q" S9 X' [3 r7 |

) x8 m5 ^# F  z8 s7 C  G& }一个通用的规则:保持表上的所有选项。当你设计索引的时候,不要只想着已有查询需要的索
" d( s' n. w1 g1 G* s7 ~( m5 ?. ?) T3 R. _
引,也要想着优化查询。如果看到需要某个索引,但是一些查询会因它而受到损害,就要问问自己是否应该改变这些查询。应该一起优化查询和索引,以找到最佳的折中。没有必要闭门造车,以得到最好的索引。
2 d( c) D7 O& s9 _; @
3 Y+ N+ u& T6 ]: n' E& O( g6 F% I6 t, p* ]# k$ m
$ C2 @; o) X( E
一个在多列上面的索引,为了是这个索引生效,必须满足最左原则。. J& x9 A$ C/ n

0 D- A% p0 r! u. A5 e例如inex(a,b,c),这个时候如果只是用了a,c。没有使用b这个时候就不会使用索引。怎么处理
4 x' D# d+ M% E+ J, y
5 M% d4 l/ G; A; U" ~这里如果b是一个可以枚举的类型那么可以使用in(…),将b全部列出。这样相当于b没有起到筛选的作用,但是却可以是索引发挥作用。这个方法也不能滥用,因为会出现n*n的结果,如果枚举数相乘过大,应该选择其他方式
- F, T. C  P5 j4 b4 ?
- p# `6 N2 P5 N6 F' _! Z' P) U5 ]
9 v" B! t2 w9 v. A) K" `5 ~9 d2 e4 c
. z! q9 X: W! p! r' p+ e0 K避免多个范围条件,只能对其中一个使用索引
9 t! t; n5 A! K- f9 P# l( E6 }. Y5 E/ U$ `

# X  M* q" T  N8 B: n$ f
2 ?& U# z) d4 o. L- T索引和表维护
0 p6 U% c) S( d- Z( j: i  S- |9 U, @
表维护的主要目标:查找和修复损坏,维护精确的索引统计,并且减少碎片.
0 r9 a$ X: G% S( _0 D( W# E4 F" ^! q0 g/ k7 h' r1 |2 n1 C$ E7 g
check table table_name;0 w# m0 b+ w" N
repair table table_name;. W9 V; g% g- u$ V3 \2 V1 [; V2 x0 J
Show index from table_name;检查索引的基数性
0 r$ t  c# J) b" w: J7 i/ [8 k6 q8 U: h+ V
主要关注cardinality列,显示存储引擎估计的索引中唯一值的数量. Y) w' D4 }! b& I! j
0 b" N' Y1 U- z; Z, N" _, w* r: z
. d0 b7 e5 G2 z8 x  W; v
- @6 h8 j; M) @& h) C
B-Tree索引能变成碎片,它降低了性能。碎片化的索引可能会以很差或非顺序的方式保存在磁盘上。
0 V: ?* D1 n; s# L3 C. W4 x
: s, P  O" B* M$ |, C2 b表数据也能变成碎片化。两种类型:
+ E2 M& p/ g3 p# h# L' {$ N
8 Z1 B( c( `0 `2 j+ P) Z1,行碎片
. g2 Z5 [( o/ \9 g, z. K0 \& z1 ^
当行披存储在多个地方的多个片段中时,就会是这种碎片。即使查询只从索引中找一行数据,行碎片也会降低性能。
* B$ y6 e. Q" o4 A# {
% ?3 N* b) f/ w; R2 ^
2 z+ H, s. s" J( _2 G; ~$ ?" H1 i4 T
2,内部行碎片2 Z1 X+ A1 A5 t2 y- n. z

3 b7 K9 J- X( b( }; N9 f. @当逻辑上顺序的页面或行在磁盘上没有被顺序存储的时候,就会产生这种碎片。它影响了诸如全表扫描和# z; V1 t# g& p" W
5 A) Z! A$ O/ m# ^3 Y( L, ?
聚集素引范围扫描这样的操作。这些操作通常从磁盘上的顺序数据布局得益。
9 G! I) }2 T* E" ^  C2 \  s7 W4 ^% u% _4 O

; m' `$ P# r; r- K; q- W; B1 I
' y/ K1 T; |( O; ^. U3 V6 B4 k9 b为了消除碎片,可以允许OPTIMIZE TABLE或转储并重新加载数据。
: v- ~+ P. T6 u: e4 `* h4 K; d( l4 h$ r9 T4 P3 q" a
& y) C( x; z0 P" p+ p
- i( P/ L$ K  g, Y5 L6 U. m
ALTER TABLE <table> ENGINE=<engine>+ Z3 x; C% s' E% a1 \$ ~- A
  C, ?% d: l) Y4 j, r. x: O) C3 t
, s/ x8 c; {/ e1 u) {

% r; I+ C7 p% J7 i加速ALTER TABLE$ k+ v. z& g% W% Y

0 V$ N9 P: @' ^1 \! x/ h" ^) _7 ]& F! I

$ Y$ l; d# E% w! xMySQL的ALTER TABLE的性能在遇到很大的表的时候会出问题。MySQL执行大部分更改操作都是新建一个需
+ G. V$ L6 D- w$ O0 f+ t( d$ g: \: A# E! K# S& p6 m- y! c. p
要的结构的空表,然后把所有老的数据插入到新表中,最后删除旧表.这会耗费很多时间,尤其是在内存紧张,
, m% y; O; L6 q% C3 X& `& ?( }( `7 |0 j4 K
而表很大并含有很多索引的时候.许多人都遇到过ALTER TABLE操作需要几小时或几天才能完成的情况。
' x/ o/ n# g) s, C+ d1 Q0 @8 ^. a! F6 k! `. v* j
传统:* X9 j6 \1 B8 C3 a
, U5 ]" N& C: G  n! z# H) d
ALTER TABLE table_name MODIFY COLUMN col TINYINT(3) NOT NULL DEFAULT 5;
: g- H, y! ^6 F" ^' I. d; k. _理论上,MySQL能跳过构建一个新表的方式。列的默认值实际保存在表的.frm文件中,因此可以不接触表而更
2 T5 M6 K0 \) l改它。MySQL没有使用这种优化,然而,任何MODIFY COLUMN都会导致表重建。
0 ^# @5 S4 S" V4 i9 x3 H0 }! z) z6 C  E3 B& r' M/ H
变化:
* e7 q' z: N* b$ }" J! d. B: p9 d! j8 ]# b0 o
ALTER TABLE table_name ALTER COLUMN col SET DEFAULT 5;
8 x0 G6 t' o2 ]) F. I这个命令更改了.frm文件并且没有改动表。它非常快。
" Y% A) A; T/ ]3 K还有一个CHANGE COLUMN
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-1-15 13:35 , Processed in 0.040111 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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