召隆企博汇论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2804|回复: 1

MySQL索引详解和优化技巧

[复制链接]

24

主题

5

回帖

199

积分

公司现有员工

积分
199
发表于 2019-12-9 11:33:39 | 显示全部楼层 |阅读模式
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能/ [. W1 h; U$ f0 f1 w% d! i
有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成
  P9 m. B; E4 r2 P. c' w! r+ R6 s& R一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列4 @7 N# l9 w1 g8 F
在InnoDB中,只有事务提交后才会解锁
3 P$ ?2 w3 j  I! L3 N$ @0 Z1 @# c4 V0 x$ A
索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只* j& d6 \' y- d! n# p+ g
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。) L: h' A, \* N7 O4 N2 c

. O/ q2 F) j# e0 f/ _, DB-TREE
  y; V4 Z  n2 M" ^8 M  F$ X7 L3 J能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找
7 Y! [! {& I0 ~+ Q+ I) f使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。
/ j( j2 C& m/ H  y* ~

  1. ( B2 A# f0 O) o; d" N
  2. CREATE TABLE People(
    3 l& v: D  I" A% M
  3. last_name varchar(50)   not  null0 ~4 q; K7 k/ o" f, g0 d6 V
  4.           first_name  varchar(50)     not   null( Y- W8 Y" ~+ }; I" A
  5.           dob  date      not    null
    2 o' b1 a# z5 {) S9 t  R  \& j
  6.       gende       enum('m','f')    not    null
    & k2 ~- T- V3 t4 y
  7.         key(last_name,first_name,dob)
复制代码
匹配全名
8 w' f: X7 F/ S9 N全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。
2 o$ s* k4 B9 b& g的人。
; D, u# ^1 J- q: d# B匹配最左前缀6 A8 O& O9 L: |, f8 |# a& I
B-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。
' f3 O; W0 ?* t$ u匹配列前缀
" ?" n( L# \# R可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。0 `; x* [7 @& t8 A+ D4 g
匹配范围值
/ c% K; o" K% |* j' L* U这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.
% g/ H9 D7 @+ c. R- N精确匹配一部分并且匹配某个范围中的另一部分! q4 ]& W3 l3 f* o: M% g! B
这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last
1 Y! I) A; ], E( v1 @7 m) r- W+ D列并且对first name列进行了范囤查询。
( q) |# y. l) kname
' ~, V" l- `) ^! k% v, c2 k0 M只访问索引的查询
  g  e2 \- n! L7 [- C6 v1 SB-Tree索引通常能支持只访问索引的查询,它不会访问数据行。  {+ ~  [1 q, B- A4 w

- K* Y7 O: K! a* c由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,+ F# F% J( x" \/ z* ?
如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查
- f$ f3 {! w: `- ]8 U# s+ R找方式也可以同等地应用于ORDER BY。
' D& c" _& ~, a; d0 u: F8 d, P: d. E! i' @# ]
下面是B-Tree索引的一些局限:+ E! M. T) A4 M/ x! G0 \; J" N
. o) e) w" J, |, S' s- }8 F! g- Y
1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,7 _& x. u6 B, ]8 S% p9 c2 R
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓3 g+ |8 I. h- D) P7 T
氏以特定字符结尾的人。
1 v# ?& Z" b2 L: ^9 @' r! z. R6 [; V4 B0 y
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定
* `% M0 ~& d) a( l+ T7 G义first_name列的值,MySQL就只能使用索引的第一列。) Y. ^' g% i: m- Z5 ^9 H* ~

0 Z& k3 w3 i$ a' @2 x+ b3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
6 V$ w$ S0 ^6 N+ Z$ t; D范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而  L/ _* \% O$ u, k- o) z) P0 I
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。
0 i# e3 C0 [7 o: }, P3 x9 j3 e) s4 {& A
哈希索引,空间索引和全文索引等,暂时没有设计
5 g- K/ E6 g0 Q: G# b5 R8 L% C/ d0 ~6 a- J& k& s3 k4 l
高性能索引策略
6 {) u6 {: R1 b  G; k7 [. J
# R5 T+ A$ U4 V& d) q! s1,隔离列,意思就是不要对查询条件中列进行计算等操作
7 W; ?' W: Z6 S; C6 X, P- K2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引
% R6 I! }9 c7 K  r% h9 X' f, f, ISelect count(distinct 列) /count(*) from table;% d" }  q- R0 h- m( Q3 W) v
看看这个值时多少,如0.0312
& @- U1 e& P9 `+ z4 H3 [那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算
! O6 Q$ y/ y4 o3 Z2 q$ D) h! O,这对于大表很有用。3 E* j1 w) O9 ~$ R" [: Q, b
Select  count(distinct left(列,3)) /count(*)  as  sel1,
  L" ^2 }9 \0 D1 w' b count(distinct left(列,4)) /count(*)  as  sel1  ,
% ^. H" c" V" i2 q/ Z8 N count(distinct left(列,5)) /count(*)  as  sel1,: c  H3 D1 s" N, X+ T- a
count(distinct left(列,6)) /count(*)  as  sel1,+ f5 }8 W8 z/ h3 @% _9 r
count(distinct left(列,7)) /count(*)  as  sel1   from table;
  J& r$ G# y( P! \8 g1 ^+ N7 B. A找到接近0.0312即可。
, V7 V# s" V8 o
: B1 O/ _' i' m! a9 AAlter table table_name add key (列(7)), Z" ~' }1 ], C0 @& ?8 U8 `
3,覆盖索引
, O0 Q8 I. T# h( o- i2 K包含或者覆盖所有满足查询的数据索引叫做覆盖索引  K! b0 C& q9 t5 U
explain时,extra中的会显示using index
6 Z% ?& ?7 l/ S0 R, K1 ?5 K3 C这里一个重要的原则是: [- z+ ?' N. y, t3 o2 ]
select后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列
$ i3 e- V8 ^' j# ]) k如select id from table_name;
1 j( M9 E, r: h/ X7 ~5 c3 j- @9 f8 c1 L+ F$ P5 m; r$ q6 c
很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不5 N( {0 o! M3 @. M! |' W4 }4 y
一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。
5 c* P# s# N. {  L
, b9 R3 T: X0 P4 e/ ~2 Y5 D& e4 ]9 \
  1. Explain Select * from table_name where col ='nam' and col1 like '%name%';
    7 j+ Y5 f6 A* Z
  2. Extra:using where
复制代码
该索引不能覆盖查询的原因:
- L0 j5 i! Y; S3 R1,0 J! o+ W, e1 T/ V' p3 u7 _1 ]
没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。4 g0 t: J  F8 L" O- D& `
2,* h) p( m1 ^, ]
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
+ U9 E  V9 g2 ^7 I5 [8 A有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:, F( J9 S6 B- w( H, ]7 W
2 A- v! k) w7 J- m" x7 ^2 G* f- @! i
4,为排序使用索引扫描2 ~& O9 G  z# _* @% Z4 J2 D9 [* Z
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。
) \/ G0 ~7 |7 Y$ ?* Zexplain输出type为index,表示mysql会扫描索引# z1 ?  P, W3 \2 r

1 w5 [5 O8 Z2 W; G& w$ x7 F1 L扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.+ H% C% Y2 a& H) L
; B8 `8 V; E3 n. A- M
MySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。
' b9 H& j) F% u/ m- V
2 o: \" L! `+ k2 y6 a! R; [. D' t按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。% I$ _! r4 j, @" u5 G
- j6 g0 O' X, w% S! S/ F8 }/ d
ORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。
) V; N# N' o; Z2 Y- h! C0 N8 k( ?8 o  I, k. \1 T8 t$ G  A
使用join可能情况会有不同! k/ p/ B6 R# N" s" x3 T
1 [6 U5 ^; o# m7 }% j
5,压缩索引(myisam)
" e6 v, ?; R/ {! ?0 F8 a/ F% }6,多余和重复索引(应该避免)
$ J& T" e/ E. p
+ n2 |5 D- ?/ Z+ u' f% S4 F& N  q# r多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)
9 e" v; D: D9 O$ ?上有索引,那么另外一个列(A)上的
- H4 u; E8 l1 Z* a- y索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
; z: {0 V6 r6 d/ O1 |然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。
* s6 O2 b( ?8 r7 x: p
' [! N0 r+ u0 {7 z- x, u7 D要点:  l0 E0 w/ G, U
在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.3 a5 m: n9 J6 }  a! a2 K  i

0 H& [$ b7 c8 T/ ^& @' |4 [即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行& I8 v8 Z# u/ ^; Z

5 M9 z$ j. {& G
' b, x$ L  k8 q% }* i
0 J* Y8 W& [* W- z; k# K* }
回复

使用道具 举报

24

主题

5

回帖

199

积分

公司现有员工

积分
199
 楼主| 发表于 2019-12-9 11:34:27 | 显示全部楼层
创建索引时,! |1 S! T* n" M) A: d. r  c% N  L
% x8 i5 i& v/ u0 V! i( ]. V9 w' h/ |
拥有唯一值的列选择性最高,那些具有很多相同值的不适合创建索引
( }- @- x% r: e  b, G7 D2 C
4 {5 l( |2 V' ^' f6 K5 W- q* D2 b
- v$ \, C2 Q! g, L! q  ^6 L, F5 D/ U# w' W" q0 ]4 O* x
一个通用的规则:保持表上的所有选项。当你设计索引的时候,不要只想着已有查询需要的索
! D3 z, G3 r/ L) B' q% y/ [# @
引,也要想着优化查询。如果看到需要某个索引,但是一些查询会因它而受到损害,就要问问自己是否应该改变这些查询。应该一起优化查询和索引,以找到最佳的折中。没有必要闭门造车,以得到最好的索引。$ i/ h; ^  Q6 v7 ]( `9 I3 r

; H1 T) v" I# I; w2 `" k  ]8 L1 R6 t# Z% H3 Y* r3 m, c

; t  h7 R7 |( ]! R$ f5 H一个在多列上面的索引,为了是这个索引生效,必须满足最左原则。
9 u& u, J' Q+ j' P2 X
5 Y9 l# Z: e) X9 t& U+ ~例如inex(a,b,c),这个时候如果只是用了a,c。没有使用b这个时候就不会使用索引。怎么处理4 B- _6 f: x, c5 f- I$ Y% ^

- V$ H: n) _2 T/ `这里如果b是一个可以枚举的类型那么可以使用in(…),将b全部列出。这样相当于b没有起到筛选的作用,但是却可以是索引发挥作用。这个方法也不能滥用,因为会出现n*n的结果,如果枚举数相乘过大,应该选择其他方式6 R, e( |; g% _. e* d7 ~
$ I7 W& y. k" j0 n

( T9 }: y9 N3 Q4 U
$ R: [# m2 u4 `避免多个范围条件,只能对其中一个使用索引% _' Z! L- T( B9 f  j  Y5 Q

- E. p" u3 l7 q3 S) V% z  e# A9 Q) I' y, V/ W& Q, R

+ L0 Y! _( M4 B. E/ Y索引和表维护
0 V% h  N. [  c! j$ c; f
1 M' V$ c% z. Y! o! f7 A7 {表维护的主要目标:查找和修复损坏,维护精确的索引统计,并且减少碎片.% j$ J/ H) A7 x! T+ }% U

0 z( E+ k/ _# o- \& n& Tcheck table table_name;
9 c! {3 y( f7 F. K$ L0 M  L9 ]repair table table_name;1 E! _  N2 r- V% ?
Show index from table_name;检查索引的基数性6 G2 f5 g$ J7 W  q5 q1 [5 B- C5 V8 a

9 X# S! f  W% U: z2 _  u! i主要关注cardinality列,显示存储引擎估计的索引中唯一值的数量! {9 s! K) O8 H

3 s7 h  Z9 {" h, r0 a
* h+ T. x3 v& a0 Z+ D( P- r0 k! e' D: h1 }: n
B-Tree索引能变成碎片,它降低了性能。碎片化的索引可能会以很差或非顺序的方式保存在磁盘上。
: W" ^1 }6 P8 [0 K$ c% J+ k6 _# \( V' m: _
表数据也能变成碎片化。两种类型:
. H% V. N) f, b% J5 e; q
: z0 u' Q: t3 j2 b1,行碎片
* P: V4 p- b4 w' F4 l) W2 B7 r/ v6 W6 E" n. {4 H& [8 p
当行披存储在多个地方的多个片段中时,就会是这种碎片。即使查询只从索引中找一行数据,行碎片也会降低性能。. Z  g5 n% Y( w$ t* L- r6 T( I

& B1 l8 P/ A" i: y. e3 K* Z' \. u2 a3 S. c. d

9 x  J: z) w  o3 f; K0 a5 `9 S2,内部行碎片1 j% P# f/ @* w7 o( p* f- o

; O0 i/ ^# J: u7 z, q! [/ {3 V当逻辑上顺序的页面或行在磁盘上没有被顺序存储的时候,就会产生这种碎片。它影响了诸如全表扫描和0 ?. \% K+ f: _" w

0 I+ f6 I# v# A- n/ L- x9 I聚集素引范围扫描这样的操作。这些操作通常从磁盘上的顺序数据布局得益。
6 ]: @2 R" g$ }# T6 C" @4 K3 m1 T$ Z  K" }5 f- T

: ^8 L: R. T" P! L. v: l% T6 U9 A  }1 y) M4 K
为了消除碎片,可以允许OPTIMIZE TABLE或转储并重新加载数据。
  p% c8 z& n# E+ }& c# O
6 V) q+ W' S2 @3 N
, E) }* }3 `5 {0 R; k) V1 z5 N; M& H+ E; B: ?! y
ALTER TABLE <table> ENGINE=<engine>
+ s5 Y& E# t" h6 q7 _: }- g; r! ?: X8 Q1 D

- E( G' J( X4 `5 [. B: v
" ?* r3 S0 H6 g8 l' r9 i加速ALTER TABLE
! y) D+ t0 i" f7 e- g3 j) z3 W- e2 s) b' V( t. k

1 K5 [; P) B% b+ h; o
/ C- f. n# O/ A  V& m  EMySQL的ALTER TABLE的性能在遇到很大的表的时候会出问题。MySQL执行大部分更改操作都是新建一个需# X$ U/ T2 @" v7 N9 i# s0 |
0 Z: L& l* X$ G" d6 s9 f
要的结构的空表,然后把所有老的数据插入到新表中,最后删除旧表.这会耗费很多时间,尤其是在内存紧张,
+ W" q4 ]* A/ G, T# |7 l, q$ {" k. N
而表很大并含有很多索引的时候.许多人都遇到过ALTER TABLE操作需要几小时或几天才能完成的情况。$ C2 e5 @; g: Q  j" R( R
% E7 Z& s3 a* V- v" b) ]
传统:3 |  X$ y0 `4 t- h, ?
/ S0 Y! \, z' Z1 y2 T
ALTER TABLE table_name MODIFY COLUMN col TINYINT(3) NOT NULL DEFAULT 5;
) [8 {! h- i* }% Y3 U8 @# m理论上,MySQL能跳过构建一个新表的方式。列的默认值实际保存在表的.frm文件中,因此可以不接触表而更0 g0 K/ W/ c  K+ I3 ^. L
改它。MySQL没有使用这种优化,然而,任何MODIFY COLUMN都会导致表重建。! ~8 u0 ]- ?* W& i( _

9 w2 |9 o+ C- {8 c. h! C, n变化:
# V8 @: M" P* w& `. w% z) L. f; T! e% M
ALTER TABLE table_name ALTER COLUMN col SET DEFAULT 5;
& |- w) p3 o8 y这个命令更改了.frm文件并且没有改动表。它非常快。
! m2 d) w' V. }- d' V) g还有一个CHANGE COLUMN
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-1-31 06:22 , Processed in 0.038665 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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