召隆企博汇论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2749|回复: 1

MySQL索引详解和优化技巧

[复制链接]

24

主题

5

回帖

199

积分

公司现有员工

积分
199
发表于 2019-12-9 11:33:39 | 显示全部楼层 |阅读模式
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能( d9 D; `' {& u& k/ X/ b
有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成) g7 [$ y' F( i; S! e" o
一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列+ ]& ]5 a. z! u
在InnoDB中,只有事务提交后才会解锁( l& [  J1 C. C! y/ D; x$ y# W

$ I% X$ J# W3 O$ m索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只0 f" p. y% s& w  L6 r4 W; V3 l: V* u
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。
8 U/ K# }: {( s% K0 r
: u1 d2 X* F8 R, Z0 b/ y) T- IB-TREE( k, U8 k. P! I2 v0 u# R
能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找3 L! s9 A/ a' i5 b9 U; r
使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。9 z- Q1 e! L, _, }7 O
  1. / m$ p: m9 g% Y) o6 Z
  2. CREATE TABLE People(  _( A5 H+ l% w- j- r
  3. last_name varchar(50)   not  null
    2 J: g" i9 Y7 i2 C0 F, d
  4.           first_name  varchar(50)     not   null- w* S* I6 s4 _# H
  5.           dob  date      not    null, O) l! S* ?2 L3 m
  6.       gende       enum('m','f')    not    null
    $ W& U6 o3 a0 a  P
  7.         key(last_name,first_name,dob)
复制代码
匹配全名
( C  X' H) b" D& F# [' V全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。1 d6 z6 X0 Q# r0 Z+ D( D) J
的人。
! n. l" s; J; q9 ~$ j匹配最左前缀/ D+ w9 p: Q. U4 d) A8 L+ i- b9 L
B-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。' \  U8 k& J4 V# q; w
匹配列前缀
4 r, F# G) z2 ?& }可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。6 o* j* x+ |7 W/ m5 J8 q1 A
匹配范围值
0 N7 m0 H& A" d5 u2 P: h: W4 G' a" Q这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.6 H" W( m5 e) r4 _
精确匹配一部分并且匹配某个范围中的另一部分
; l; c" [9 c' R" V! F) a这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last
- ^0 s8 M- g% h4 a列并且对first name列进行了范囤查询。5 i& S- e+ O4 K
name+ T: K& p6 F1 t' }8 ~
只访问索引的查询1 V! i9 C( S6 \) B3 A+ U, y( h0 b
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。2 }& A# \4 W9 I; F# A

" \' G) U# I& Z/ s+ j2 d! o8 {. E由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,
$ f: k7 ]; C; g如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查* ]8 T9 [6 [4 l! [) \
找方式也可以同等地应用于ORDER BY。9 G2 o  |) B" Q, I2 s7 b: q
+ q" X2 K" J! @3 Y7 n6 g
下面是B-Tree索引的一些局限:5 F/ ?& k5 K! s" D2 W5 X, x" ]
7 |8 g  K! j6 D! t4 n% x( E: b
1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,) H+ A2 j8 `: H1 G
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓! q5 f) X8 C, U
氏以特定字符结尾的人。
" c( A" d$ U' Z& m; c1 S5 }# W0 E  R7 \5 J& F8 P5 u
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定* K6 v, V1 C! ]0 W/ o4 X$ F1 J
义first_name列的值,MySQL就只能使用索引的第一列。% `" h& m: i8 w: P$ C" n
, H& M6 x5 S3 I( }
3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
4 J0 {: z; c4 ?5 O范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而5 e# {# b$ u( f* B0 o* [- q5 m$ l- `
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。. K/ l" Y2 Y; o4 ?' X
* u# j) C8 D8 O: s' F9 q* m
哈希索引,空间索引和全文索引等,暂时没有设计. j8 r1 ?1 P+ v
$ v3 y$ x* ?, A7 p/ F
高性能索引策略
# v' A9 l% f9 a- ^
* L0 B6 {! M+ ]( B3 a% A1,隔离列,意思就是不要对查询条件中列进行计算等操作: C) K& i- S+ b3 T
2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引+ {# J8 ]. L3 J. @3 u3 y& t
Select count(distinct 列) /count(*) from table;1 q7 L+ J9 E7 X
看看这个值时多少,如0.0312
) I2 c2 X1 m4 \4 I8 m( z7 w那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算9 _9 l  w9 i4 U5 A, W
,这对于大表很有用。
# H/ v7 W! e3 m7 cSelect  count(distinct left(列,3)) /count(*)  as  sel1,
7 n8 V" u/ ~* s2 L% V! ~ count(distinct left(列,4)) /count(*)  as  sel1  ,
: @) \! L- `" |8 j9 t+ z% p0 [ count(distinct left(列,5)) /count(*)  as  sel1,( f- U: ]0 J) J0 b
count(distinct left(列,6)) /count(*)  as  sel1,9 O- b$ L, U% y8 z) I9 Q
count(distinct left(列,7)) /count(*)  as  sel1   from table;
, u9 R+ c% U0 x; E; ]8 q找到接近0.0312即可。3 m, t2 G, ^" J; ~  j

: A" @' [: ^# s6 b" JAlter table table_name add key (列(7)). ]( C8 A4 _# P3 g; m% P+ W! i
3,覆盖索引% C6 y0 J) _8 [  G! k# S( ?' W. O$ D4 J
包含或者覆盖所有满足查询的数据索引叫做覆盖索引7 f, @, V! ?, m' @6 J
explain时,extra中的会显示using index7 E. T9 L& d$ B/ X/ R5 w' d
这里一个重要的原则是
( A  [, O4 Q6 G. `9 R9 iselect后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列6 w! l  K  X) S5 `
如select id from table_name;1 F  M& a! V' K' p

5 c8 R6 z  }4 T4 }很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
2 X, A8 p4 [* I' {( R一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。, O( m5 Z% f5 q/ ?( S

5 [* Z6 X4 w4 w& r8 _$ m- I  M
  1. Explain Select * from table_name where col ='nam' and col1 like '%name%';
    ' z$ J# \4 s+ W+ o
  2. Extra:using where
复制代码
该索引不能覆盖查询的原因:: t3 O% E7 [, H
1,4 ~% L' V* C) y5 h6 X$ {
没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。# Z% C8 N. I( D5 ?
2,* e0 m( Z; I" W( c; s, w
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。- f0 |2 }7 \8 o3 b
有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:
) Y3 U5 j9 J% E/ b5 ]) x  A- X! p# v4 M' m! A( V; h
4,为排序使用索引扫描
3 [0 g" S4 e1 Kmysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。) ~8 Q% `; N+ X6 }+ ^9 a1 J
explain输出type为index,表示mysql会扫描索引5 X6 v4 z9 V% q) B2 J/ X. F
8 F  M' Q: I8 Z7 ]8 Q# a
扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.5 ?$ [' t! P) P

8 C  v. _& P! L' B2 g  T7 IMySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。2 S; [6 c0 L  u- c# e
  B/ G) J4 ?; V( w' y* x
按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。' ], \  ?- F% ?6 `

2 i5 c! O3 j5 Q! H* h( {ORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。4 T7 I' w  ?: N, q% J
' b" s! M8 n1 x
使用join可能情况会有不同, @4 P, ^/ t0 L# M! G
9 A. ^9 ^( U# k: }4 H
5,压缩索引(myisam)
/ L$ I7 _1 M' Q  m8 d* G) v6,多余和重复索引(应该避免)
4 h2 b! N& l7 [
& o" ]6 @, E: }# F1 F多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)
( }* C' q3 I- m( l上有索引,那么另外一个列(A)上的
* L& Q6 h4 J. C: }3 r& o0 z索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
( E- m/ i% t' t, X然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。
% U# Y* l. T( J+ I' f
; c+ \/ c: ^$ ?  h& r要点:
7 M2 y' m% b5 I在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.
2 r3 Y' ]' g% r$ G7 u8 F- l4 a* \9 a1 R0 [3 c& }
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行
2 ~. F: A/ B4 u# h0 U, T/ j
; ~" A1 N% @5 m/ D. c8 m" I' I4 s) c( H# j$ B2 i6 H- g- ]
; Z" V& ?3 Y3 w- I! z7 j! V! J
回复

使用道具 举报

24

主题

5

回帖

199

积分

公司现有员工

积分
199
 楼主| 发表于 2019-12-9 11:34:27 | 显示全部楼层
创建索引时,3 r: p  D7 x" j5 X

! |$ [( K) v. f6 O/ f拥有唯一值的列选择性最高,那些具有很多相同值的不适合创建索引
$ q& {4 Y  J. B4 o! W) U
! U1 v, ?/ t8 o8 l$ E. e0 V8 z0 Q. z; r6 g7 T* C) U' f  H3 p

: Y' {8 s6 Z; J; {$ [一个通用的规则:保持表上的所有选项。当你设计索引的时候,不要只想着已有查询需要的索
- U, h/ J- J( b+ ^8 g! @2 ~" F
% S+ f1 G4 ~' K引,也要想着优化查询。如果看到需要某个索引,但是一些查询会因它而受到损害,就要问问自己是否应该改变这些查询。应该一起优化查询和索引,以找到最佳的折中。没有必要闭门造车,以得到最好的索引。( H: B" l, A; O  n! I# \

3 J$ e* L/ e) q7 e% E( h  O  g9 }* G
3 T0 d' k' d1 ~
一个在多列上面的索引,为了是这个索引生效,必须满足最左原则。1 _$ M) s7 |: }* k9 L) b0 N

- P  T+ N* d" X5 z7 p例如inex(a,b,c),这个时候如果只是用了a,c。没有使用b这个时候就不会使用索引。怎么处理
  `, m/ v) n% j5 Y) u, a. V2 l& r9 k3 C0 k* _. v5 g2 q: P0 f
这里如果b是一个可以枚举的类型那么可以使用in(…),将b全部列出。这样相当于b没有起到筛选的作用,但是却可以是索引发挥作用。这个方法也不能滥用,因为会出现n*n的结果,如果枚举数相乘过大,应该选择其他方式
2 ?2 M& ]" S$ I4 x+ |6 C- o5 D+ N+ d; D# S! U6 h" O4 E; ~
/ T, J. n- Q# I( p1 `# ?1 m
0 i! p% ~% M0 u8 r9 d
避免多个范围条件,只能对其中一个使用索引9 Q/ [1 {6 j( F# }: H

! z" l, V/ \. h0 @9 U! \
/ _) J! W" b7 c0 @1 `
: y1 M/ ?& M; |! I- L# ~; J索引和表维护
, K9 n  X! W+ F5 a
& f. l5 d& r6 Q8 Z表维护的主要目标:查找和修复损坏,维护精确的索引统计,并且减少碎片.
1 ^# y- ^# U  b
* w  r+ [  w* K7 t, |7 r/ Y" ]check table table_name;% J, ]9 H6 d+ j, n9 v
repair table table_name;
* [( T  z, C2 IShow index from table_name;检查索引的基数性8 M5 d7 i+ h. V* i
) Y( ?9 n- o( }  r2 p9 O5 b
主要关注cardinality列,显示存储引擎估计的索引中唯一值的数量
) M0 s3 X/ Y) ^9 R  ~- S# G. j  x& c, j- V' Z1 m; \

! r: |! X5 g# z/ g5 f* X& R8 c- r! s
B-Tree索引能变成碎片,它降低了性能。碎片化的索引可能会以很差或非顺序的方式保存在磁盘上。" L/ _- Q2 X. U/ M& P
6 `% ^8 o% v3 j$ w: _6 H4 B5 U
表数据也能变成碎片化。两种类型:5 G0 l$ i' t& I

% Z; D3 K3 O2 V' V9 |/ ?1,行碎片
( ]1 q; {) F/ S' Z/ K
! S- J5 _. k8 r2 W( }6 X/ p当行披存储在多个地方的多个片段中时,就会是这种碎片。即使查询只从索引中找一行数据,行碎片也会降低性能。( Y; E8 N7 G& ^7 J. I$ ?
6 h( ^3 `$ M9 J2 ?# J* M7 C
# g& _" Y2 g8 E* _9 R$ K' @
1 g5 }/ d" N. t- @; U
2,内部行碎片# U" d) O' I. V4 x* x
6 ^1 L8 \2 X- k% T. k8 p6 D
当逻辑上顺序的页面或行在磁盘上没有被顺序存储的时候,就会产生这种碎片。它影响了诸如全表扫描和
* g4 _; @  F* @$ V) w0 R  {6 I( j; c+ e2 B! ?
聚集素引范围扫描这样的操作。这些操作通常从磁盘上的顺序数据布局得益。
: k4 \, I8 {, `! d% |5 k- |/ ^8 j8 H0 Y) H( ]4 Y/ k
9 `$ K5 c( p  p$ F

2 I, x' l+ `# o: O为了消除碎片,可以允许OPTIMIZE TABLE或转储并重新加载数据。8 O3 T9 q' ?, S+ s6 v* ~
0 M  {# T* Q. w* T
4 e$ l  [- y) I+ F% e4 \% w7 q& {

$ {' D& g7 p% S8 e; @6 L/ H: Y1 ZALTER TABLE <table> ENGINE=<engine>' p1 v# ?7 O( O2 w

" `  J4 [6 [( ~* E. ]+ v. ~; t
/ N. K' B$ O. ]4 V7 R' o
0 F1 x, ~, K0 J) b' U; L0 }1 X加速ALTER TABLE
8 m4 U9 w3 d! T6 d' o, v
8 Q$ N! C0 T3 U6 R$ @5 J
9 Z: D$ }; x/ H7 Y# c# R8 F* R, \! t1 x. \' h. z( B% y( ]  f6 J
MySQL的ALTER TABLE的性能在遇到很大的表的时候会出问题。MySQL执行大部分更改操作都是新建一个需1 F- f* l, S4 y$ E* P

, T( c$ D7 S) a9 M: d. e要的结构的空表,然后把所有老的数据插入到新表中,最后删除旧表.这会耗费很多时间,尤其是在内存紧张,6 F+ y6 J3 {) D" v0 V

, E, a& Q0 w( y9 t0 H而表很大并含有很多索引的时候.许多人都遇到过ALTER TABLE操作需要几小时或几天才能完成的情况。
6 q3 a$ L  f+ l- ~3 w$ T+ l" F/ f& v% p6 w# M9 m
传统:  m# G' z. n& A) K- r$ L1 I, [) X# _
3 M  Y1 R( [4 M
ALTER TABLE table_name MODIFY COLUMN col TINYINT(3) NOT NULL DEFAULT 5;
4 [8 M& f1 m- c) L, {5 m+ H$ T  l3 u! W理论上,MySQL能跳过构建一个新表的方式。列的默认值实际保存在表的.frm文件中,因此可以不接触表而更, P% c" O1 ~7 g2 [% J9 e' T. w
改它。MySQL没有使用这种优化,然而,任何MODIFY COLUMN都会导致表重建。7 D7 `9 d: X! q1 J0 ]8 G0 c

4 o% A5 W( Z- k变化:6 x# B9 u, V' w6 t* o

2 g; y/ ]6 T- dALTER TABLE table_name ALTER COLUMN col SET DEFAULT 5;9 ~9 p4 t; D$ x5 B0 F+ c
这个命令更改了.frm文件并且没有改动表。它非常快。1 z. s8 z) [6 D/ d/ E6 _: P6 B
还有一个CHANGE COLUMN
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-12-17 21:55 , Processed in 0.046579 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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