召隆企博汇论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 3344|回复: 1

MySQL索引详解和优化技巧

[复制链接]

24

主题

5

回帖

199

积分

公司现有员工

积分
199
发表于 2019-12-9 11:33:39 | 显示全部楼层 |阅读模式
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能
1 q7 S: ^& h" m) M+ e5 V% u有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成/ E/ n( C8 H! a7 Q/ X8 g, l, `% S
一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列2 \6 L9 V. R0 ^" o5 Y
在InnoDB中,只有事务提交后才会解锁
1 d* k* P- D% a( ~$ R% t2 b) B7 }! a! ?
索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只
7 h% D" {# E7 E  Z+ j能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。# u5 p: P9 N; `- S$ G$ z
' a3 A% s; R& j/ p6 k1 G( u, A
B-TREE4 I4 ~. f+ \! s6 L% r. J, _
能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找
: a% K/ `! ]) O0 m% o5 W) }使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。# C- D# J# |& J; t7 g

  1. 8 {+ \3 E) z2 C8 r/ V2 R9 R2 J. d
  2. CREATE TABLE People(% I1 [# I2 s# |0 W' x
  3. last_name varchar(50)   not  null
    . r8 b# I' y- z" }5 y
  4.           first_name  varchar(50)     not   null) c3 H0 {; `5 h& }' R7 x7 [" @
  5.           dob  date      not    null
    - Y* L- e% t; r# `2 l% k0 ?
  6.       gende       enum('m','f')    not    null
    ( \9 s5 k! m, o8 C5 P; s" x- S  f3 Z
  7.         key(last_name,first_name,dob)
复制代码
匹配全名3 U3 G( P4 J, z2 D4 ?  F% C! O5 _
全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。
- O$ L/ j' J! ]# U! V* ^9 D的人。* r/ c3 R  d8 ?2 y1 i. E( J" q
匹配最左前缀
6 ]. Y% D1 `3 ]  zB-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。2 o3 w% a5 U) O+ n. |# e6 K: i5 r
匹配列前缀
4 l6 z. R& B, I# i可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。: m" I: Z1 V4 j0 ?( B" G. o
匹配范围值, Q, a0 l" _) L( D0 K, q! G
这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列." a% ^& B. c! S& @: |
精确匹配一部分并且匹配某个范围中的另一部分
% ~( x2 _% i9 \  |' p' J8 ~5 I这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last1 A$ W4 g% I! i$ s
列并且对first name列进行了范囤查询。
3 Y/ g& D% ?6 @3 ], _' n# hname
& X: T" G/ D, n1 i5 E' j9 s只访问索引的查询0 ^7 E" q7 ^% @
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。
' b+ \& v. N8 m7 q7 j8 S3 ^7 H# b$ S8 y* S6 h/ `8 V" K- A& L8 U- @
由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,
0 O' s) |, C/ ~8 g( B如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查
) \4 t- V$ R) L6 p6 w6 \找方式也可以同等地应用于ORDER BY。3 F% K3 N7 d8 t( P8 H7 i: {( n
- v% `1 _. E# _* |# L% j/ Z3 r' C
下面是B-Tree索引的一些局限:) E% c) |+ w/ i/ b* H" ?4 z
# |% U7 J# i% T2 @
1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,
3 t. U5 D2 y" O3 ^7 }也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓, q' {( S" j  j  }# Z$ D
氏以特定字符结尾的人。1 \+ S5 B) m" a0 u" C$ O" N* K
- l5 J& C7 V! N1 T$ O& S
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定& P) o9 ~1 q' J1 c8 Y5 V. w% q* u
义first_name列的值,MySQL就只能使用索引的第一列。8 O* u( V! N6 [. A. ?

) n8 F) Q& s) V! q  Y; J0 c3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
- H+ z! s6 F! j6 n" w4 S7 {% }范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而; ^  _" |9 k& c
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。
6 C: O# A' R/ n3 G: ?' {9 j
( g3 E( B% n2 e" N! W1 H哈希索引,空间索引和全文索引等,暂时没有设计5 M' J3 w. ]+ K

0 F. a/ m0 [1 _9 i8 H8 ^4 H. E高性能索引策略6 b* F/ k. x8 I

  M- ]! [9 Y: i$ s$ P1,隔离列,意思就是不要对查询条件中列进行计算等操作
- G5 X8 d/ n& F2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引
' S2 U: e7 c* hSelect count(distinct 列) /count(*) from table;7 n8 W. i. I1 o% [) L5 x
看看这个值时多少,如0.0312
9 K$ Q/ l( t" ~* S那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算% k. u& w% S5 n' P
,这对于大表很有用。4 s+ a7 Q$ l6 `( O5 Q
Select  count(distinct left(列,3)) /count(*)  as  sel1," w& B, n$ [. n8 p, w9 n3 e2 ]
count(distinct left(列,4)) /count(*)  as  sel1  ,& L# H* N; `6 z6 x, ~4 s
count(distinct left(列,5)) /count(*)  as  sel1,
4 W# q; A- M4 ` count(distinct left(列,6)) /count(*)  as  sel1,
$ A1 e. @/ T- `: o" K0 m count(distinct left(列,7)) /count(*)  as  sel1   from table;# W9 q) K; ~3 T$ f; b* D6 v# S9 E
找到接近0.0312即可。
+ J3 N; M. _1 C3 R4 U
# [, E0 y# y* K8 g" y0 F% XAlter table table_name add key (列(7))
% J( O5 w: A5 _) \6 D& a/ y3,覆盖索引* D' m) X- i3 ]+ O- Q* T
包含或者覆盖所有满足查询的数据索引叫做覆盖索引
  A3 E8 N8 T& p  P/ |: m6 u3 Aexplain时,extra中的会显示using index8 d, a5 z, |/ I3 O5 c
这里一个重要的原则是
3 M! Y8 n1 ]. ~4 b/ P5 m1 C. k; nselect后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列8 w+ a4 c" y4 o/ ], J
如select id from table_name;& Q/ C' j! _* F+ ]8 s

; A$ Y8 d6 L, I6 P* [- X很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
: f6 [- ?) Z' m) ^( P. F一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。
6 F9 X4 `9 S  U+ R4 s: ?' B/ k
4 p% L8 x, ~$ ]6 W4 b6 }
  1. Explain Select * from table_name where col ='nam' and col1 like '%name%';6 s1 l5 D! x0 j8 ]2 \/ O; V6 ^  t7 L% _
  2. Extra:using where
复制代码
该索引不能覆盖查询的原因:
2 q. B+ H6 H0 |: e1,' x- x6 w4 P' ?/ R2 W4 K
没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。$ N5 O7 F. H$ V, i
2,- s' {( w. T) ?
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
1 @4 ?& ^) M. a$ x8 c有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:$ b+ `5 i5 ]% D3 }+ {1 @" d- |
( C- q4 i- u1 u
4,为排序使用索引扫描! Q- r1 p+ e5 R( {7 ^
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。( s# m8 x9 G/ b( a" U0 S8 b2 J( X
explain输出type为index,表示mysql会扫描索引
3 p" T2 h0 v" [3 _2 y/ M
! ?7 i) o( D; h; N" m7 K+ l6 d扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.9 K) `6 N4 H+ |

3 f! @) G; _6 P1 e9 K' UMySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。, ]& L% E3 s6 ~0 |- e, U3 K
& U7 d/ X8 w' r( y! M7 H1 v4 z
按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。6 \# q% w1 s# K. U' i$ b

4 x' O% P4 X8 V* A2 tORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。. f# J. w  P5 C" N7 `
( N' P' B' c7 T
使用join可能情况会有不同
$ c9 Z4 K$ m: X6 z, c5 p) p6 Q3 {6 f( N8 e1 o/ X, n
5,压缩索引(myisam)
, F) i# E: w9 p4 O6,多余和重复索引(应该避免)
8 H* W9 V: R3 \6 g* x6 V! [$ L+ Y
& x+ X) a7 @9 E$ W多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B), {7 o) e, u6 i0 R
上有索引,那么另外一个列(A)上的
9 a( Q( _2 v; I索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
) S. e8 Y4 c. o然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。6 s3 r$ {( H, y/ J4 V

2 X6 g$ L* {* H3 r+ w3 e要点:
$ q: m, y* \. p0 O2 ~  J) ^在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.) y- m0 {+ @7 ^! E' j  ~
; P$ d3 K* |( Y: r  l* }
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行7 ~, E, C; E; j$ X0 U/ j& a  @
5 Z# a2 h+ S) _$ z; N
. K; }  s' `2 k+ S3 r4 ~: P
" B0 u& i. N2 g6 |+ y! W1 f3 i
回复

使用道具 举报

24

主题

5

回帖

199

积分

公司现有员工

积分
199
 楼主| 发表于 2019-12-9 11:34:27 | 显示全部楼层
创建索引时,
+ \- L% m, d8 }' Y& r& \
( c  C- m6 ]) V) q. C拥有唯一值的列选择性最高,那些具有很多相同值的不适合创建索引( P0 O5 W; V2 j& Q- z' s- N
. b( a+ s3 v3 M

2 |5 l3 z3 c' s  `  ]4 G$ S, e5 d9 X: e9 z& t9 w* w; y# R, G
一个通用的规则:保持表上的所有选项。当你设计索引的时候,不要只想着已有查询需要的索* p( e* j& f' d# b' g
& k4 ^  i+ ?' l
引,也要想着优化查询。如果看到需要某个索引,但是一些查询会因它而受到损害,就要问问自己是否应该改变这些查询。应该一起优化查询和索引,以找到最佳的折中。没有必要闭门造车,以得到最好的索引。
: ]0 s1 q/ _; ~
) C" U+ T" J8 L- C& M+ ]# X6 e' M( R
. i3 V( @9 o" S5 }4 [
: K6 Q. I) A: z0 X一个在多列上面的索引,为了是这个索引生效,必须满足最左原则。2 x. d* |+ e- _- w

) J$ m. t" R- \1 E% q/ G- H& D例如inex(a,b,c),这个时候如果只是用了a,c。没有使用b这个时候就不会使用索引。怎么处理
$ U- Z: Q8 e$ x$ `) X
: n' t0 o" t, x- ]. z0 L这里如果b是一个可以枚举的类型那么可以使用in(…),将b全部列出。这样相当于b没有起到筛选的作用,但是却可以是索引发挥作用。这个方法也不能滥用,因为会出现n*n的结果,如果枚举数相乘过大,应该选择其他方式
: k" g0 [6 c" H# ?- @8 ?8 v% k2 C4 K+ Q, U

' d& k/ r% z' ?" B' t2 [. u
1 |2 @' f6 p/ V# y& L3 r" ^避免多个范围条件,只能对其中一个使用索引! L3 N# x8 t  t, N) ^5 y( n% Y
. M5 J. |" e9 W9 ^8 \* \% M1 p

! {2 A& A& A! c4 }! f1 e! B; h! d& R5 n+ o  M) o) f
索引和表维护
2 z; C! g0 k# t' a( W/ R: G6 F3 N: W. f- x
表维护的主要目标:查找和修复损坏,维护精确的索引统计,并且减少碎片.( i/ |$ x3 d, P; O
  `' K; X1 V9 s! ?
check table table_name;
  }- z$ r* b4 }4 erepair table table_name;
# y. q* z+ M4 ]; U' RShow index from table_name;检查索引的基数性
/ _# d# Z+ W3 j2 G+ F' x2 a* b% N+ y, `8 O
主要关注cardinality列,显示存储引擎估计的索引中唯一值的数量- \( q9 o; _  n1 Z4 X, z
; q) v% M  s4 j% l
3 t" A* r. x5 P4 G

# r: @5 p; M. GB-Tree索引能变成碎片,它降低了性能。碎片化的索引可能会以很差或非顺序的方式保存在磁盘上。
/ M0 ~8 j# y" T0 ^3 C) m3 a1 I' q3 s/ c6 f3 l3 e  H( ^
表数据也能变成碎片化。两种类型:' |# ?: F  {7 S9 {" y
( K* E& ^5 ?( K" ^. k
1,行碎片) B* M: h" v( x

5 g# u0 w+ u' ~7 [3 q- u当行披存储在多个地方的多个片段中时,就会是这种碎片。即使查询只从索引中找一行数据,行碎片也会降低性能。& J4 N& D) ]; X# v6 S
3 G! Y, J( v7 P; P# U' u' ]
6 L4 e. y7 V: [1 x$ b

6 Z* s. j; Z9 z  H, Q2,内部行碎片) Z9 l& ^: Y& @( P, ]4 o" ^; z" c5 h  f
( ^5 L: Q+ }3 u' Z
当逻辑上顺序的页面或行在磁盘上没有被顺序存储的时候,就会产生这种碎片。它影响了诸如全表扫描和6 d9 Q& l( u' w; ~1 n: N4 o1 _& |6 q

9 j/ l- g% a+ j( [聚集素引范围扫描这样的操作。这些操作通常从磁盘上的顺序数据布局得益。2 D' T9 Z% l2 x7 Y( Y) I

2 b$ O( o& ]% I4 i! B
8 B2 ~& [$ v  H" r4 h, |
4 [, n6 h- X, I1 f# \, d4 S4 Q为了消除碎片,可以允许OPTIMIZE TABLE或转储并重新加载数据。
# B5 P8 ?8 ^6 j" r* U( ~/ ?3 m7 N' c+ X1 G' g" R5 B
2 j/ @' ^" i* |" t6 O

% K; O7 Q; J3 n) F* M7 ~ALTER TABLE <table> ENGINE=<engine>4 ^/ e$ V# n- c& m) ^5 b

1 x* z. x# \/ J5 P
7 U; O1 U: H1 {) J# `* r' ?. _6 j9 |' G# G8 O" J/ O6 _
加速ALTER TABLE
* b: D% z0 _* E; F0 N
- J* p! `5 |7 B6 |9 {6 [. F4 e0 c6 t* a3 |! C6 i4 z. j+ P
0 u& d# O% \. E7 t
MySQL的ALTER TABLE的性能在遇到很大的表的时候会出问题。MySQL执行大部分更改操作都是新建一个需4 r8 q9 i  Q* L2 @0 m' Z) A
  F( K3 ^* ?0 D, F6 q3 b
要的结构的空表,然后把所有老的数据插入到新表中,最后删除旧表.这会耗费很多时间,尤其是在内存紧张,( I6 B; r+ N. _' c8 ]
7 x8 R# X# v& ~/ M. Z# z/ F
而表很大并含有很多索引的时候.许多人都遇到过ALTER TABLE操作需要几小时或几天才能完成的情况。) y: C# u* j; ~$ K6 X+ F

5 k- `7 n5 }1 P! J传统:$ A- G" Z0 N" A8 p1 _9 A6 x
7 e4 |4 \7 x5 r- L- E7 l% P
ALTER TABLE table_name MODIFY COLUMN col TINYINT(3) NOT NULL DEFAULT 5;; ?2 r; T, d* [. E
理论上,MySQL能跳过构建一个新表的方式。列的默认值实际保存在表的.frm文件中,因此可以不接触表而更
; Y: Y' ~4 n* Y# l! y5 C/ X' v改它。MySQL没有使用这种优化,然而,任何MODIFY COLUMN都会导致表重建。# q1 Z7 L- p2 w  z" v3 t

- R7 U, j( [, ^* D4 T, R变化:4 x3 v8 O: O! }

9 D+ g: h2 V6 s: r3 t1 _" Q! H7 qALTER TABLE table_name ALTER COLUMN col SET DEFAULT 5;
( z) T, x; h! Q+ j; X3 K- Y这个命令更改了.frm文件并且没有改动表。它非常快。4 l: ^; q, ?! U, y
还有一个CHANGE COLUMN
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-10-10 11:32 , Processed in 0.049380 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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