召隆企博汇论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2727|回复: 1

MySQL索引详解和优化技巧

[复制链接]

24

主题

5

回帖

199

积分

公司现有员工

积分
199
发表于 2019-12-9 11:33:39 | 显示全部楼层 |阅读模式
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能
  |9 |5 ~7 W+ w; ]9 L: ]/ Y" t5 ~有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成
* L; i; m. @  W/ a" k一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列
1 L3 V0 S8 L6 p在InnoDB中,只有事务提交后才会解锁( T. D) d& ?% J0 x0 z- R6 Y) z
& X7 c. E  K1 E1 N# R  V
索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只6 S3 _( {  }! k% A: i) W
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。7 y! T  n# v5 v4 y9 V$ ~& G

* a( [5 G9 N* T* KB-TREE1 H; ~4 S7 K& s9 h
能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找; A( x, o9 K+ d5 v) k
使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。# @* W1 |" u6 G4 ~( D6 K

  1. 5 G& l! Q, O' Y' y  ~) e& k1 ?
  2. CREATE TABLE People(3 V; i" _4 A+ z' ]6 b4 ]
  3. last_name varchar(50)   not  null" }3 K6 O3 Q; H$ H' E8 W" h
  4.           first_name  varchar(50)     not   null
    4 Z: E  a7 I2 W  {2 O0 ~  }6 N
  5.           dob  date      not    null
    , G: W' b. |0 J9 M
  6.       gende       enum('m','f')    not    null
    ( ^+ W2 O: {  s% S& j8 Q0 {* T2 x6 y
  7.         key(last_name,first_name,dob)
复制代码
匹配全名
/ [6 j1 J4 I. m" D全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。# i; W, J& ]' [8 X4 i# W4 D, S5 a2 p
的人。
: [" T9 ?7 G$ B2 q5 d7 H匹配最左前缀; D7 g" _/ a7 a2 m! u
B-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。/ @2 k! J  A7 C8 c
匹配列前缀2 M6 O! a* g" L) Z
可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。
+ ~2 H: Q1 Y& ^# h# V, X3 ~' x) ~匹配范围值
7 ?( y* [  g2 h9 W这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.
- z+ {  w4 f7 q5 R. L0 W精确匹配一部分并且匹配某个范围中的另一部分
4 o2 n; [+ Q6 O$ K- l* E这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last2 ?: n' f* j9 B
列并且对first name列进行了范囤查询。1 V3 X( [/ H. ~
name% S2 s' t$ N% a2 m! [9 l  C
只访问索引的查询3 c2 f1 U# t% G* N2 L/ ], J
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。& x- G- Y4 c: s% {" ~

; `" r3 B6 M/ W6 V- d! O由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,
3 w4 p6 c' }8 a如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查: v/ t. x% e) T4 q7 A
找方式也可以同等地应用于ORDER BY。
1 H$ w3 m! B! ~/ b  t) ]  X) {: r% z% ^4 d/ {! [- f
下面是B-Tree索引的一些局限:
5 u4 C# e& L7 S9 ^9 ~2 b
( T9 @+ d0 _* Q( L8 W1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,2 |1 D7 B8 a& H( j" H
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓  {  ?2 M  p' e$ |* j
氏以特定字符结尾的人。
! }8 g0 p9 i" _( ^" b. x) r5 i/ V0 L
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定
) g6 M( t, O- p* s3 R- G义first_name列的值,MySQL就只能使用索引的第一列。
) i2 c- N$ O$ Q8 ~
( _! Q5 H. U. V. c( C3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是  u8 }; z  B8 e3 d+ r9 x% D
范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而
4 }, `1 D2 Y6 j3 I6 I/ \不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。
( W  B; d' a' n4 t
! u6 |4 M2 s; ~9 Z哈希索引,空间索引和全文索引等,暂时没有设计
0 p0 L' |6 ?" R3 ^0 m5 w+ ^# H8 V  ^
高性能索引策略
  p: V6 }+ G4 Z  j# S; |1 C0 p* w9 N) d' W' S8 Q, A2 Q( j# R
1,隔离列,意思就是不要对查询条件中列进行计算等操作& T9 u5 l# [. M) R! m8 p
2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引
: U: ]- D, V- v) I  E  ASelect count(distinct 列) /count(*) from table;: \9 B( q3 ^! @9 X9 ?) T
看看这个值时多少,如0.0312$ x+ p6 k3 C4 v' y( \
那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算0 I# D' w! [3 Q" Z- q: w, d+ {
,这对于大表很有用。1 c  _4 I1 X* Y
Select  count(distinct left(列,3)) /count(*)  as  sel1,
0 W. l5 @3 k2 x( ]# M0 [ count(distinct left(列,4)) /count(*)  as  sel1  ,1 _, i# Q' ?9 G8 e) i1 b
count(distinct left(列,5)) /count(*)  as  sel1,
2 p* F$ q. [# H5 f count(distinct left(列,6)) /count(*)  as  sel1,& b5 J- Y/ ?4 p
count(distinct left(列,7)) /count(*)  as  sel1   from table;
0 Q+ l7 k1 ]: B找到接近0.0312即可。! p2 v) n1 [3 R6 l8 C
1 I. U% y! E+ L# I: E7 P; v
Alter table table_name add key (列(7))* g* W& r1 f- k- o/ y
3,覆盖索引  ?4 I7 i1 L) e; I0 o7 F
包含或者覆盖所有满足查询的数据索引叫做覆盖索引/ g( J5 e. A8 i) p
explain时,extra中的会显示using index" J. r; s5 A. s' ^1 Z' R- X3 A$ Z
这里一个重要的原则是; m. E; J* b7 g6 k; V8 v
select后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列/ T5 a' X# }/ D) k; P2 k
如select id from table_name;
4 F; z2 N$ t. `' ]+ f8 H& U4 S" \( a* M
很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
1 T& p* j! u9 R  y1 z0 @一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。
/ Z5 n5 N6 o: O5 C, P
8 f3 L2 v9 g0 M8 o/ a: R+ n
  1. Explain Select * from table_name where col ='nam' and col1 like '%name%';$ C* E% ?( ~2 b( j3 @! }+ i2 [
  2. Extra:using where
复制代码
该索引不能覆盖查询的原因:1 \5 y% G6 }  F8 h1 x$ |
1,* @) h6 G- G, ?  g4 m2 v1 {- h
没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。* C$ g0 G8 m: p5 g) U4 \
2,* f2 }& J- Y# u' z- ]" ~7 Q
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
9 u9 ^6 k0 J7 w# e% Q0 a有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:
' e  z# ^! H  y
+ t. F, ]5 j* w* {! \4,为排序使用索引扫描
1 ]  Q9 N; W# i! E9 Wmysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。+ f( j: |4 V( a3 c" K8 {2 t
explain输出type为index,表示mysql会扫描索引) P( A0 v' ]% E

* e  ^/ f3 Y" U扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.( t6 N, k4 o6 }( Z

2 P( f, Z( z# n3 D* bMySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。
! A, L: x9 |3 i8 \% f
+ k2 U' A$ [# b1 ?. M7 _按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。# ]" y( h# I# q/ t

1 N3 I* I+ g! S* k9 a: QORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。8 Z5 V1 C/ u$ u6 Z4 Q9 M6 @

( q9 _3 a0 h1 ?6 c( t使用join可能情况会有不同
  d* _1 H- j' x( k7 M) }+ N  B$ H" |. Y" x$ r
5,压缩索引(myisam)
# o9 x' D, R& X# d/ U: t& r6,多余和重复索引(应该避免)
# p1 A1 ]9 |3 d6 @4 T9 J7 L
9 R# {" Y) Q# f% z/ P多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)
- j3 R: |* J. [! }3 T上有索引,那么另外一个列(A)上的, B1 Q7 t% X* i: V  H" l
索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
, Q8 \  t" Q/ x+ n1 d& E, V6 g然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。
  t7 t5 S3 C* Z* J! Y
) N% C6 f, B+ a( k  ^  g& W要点:" N8 M4 t# v' [& p/ R) e
在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.- H; P: w3 \/ C0 e* c
6 J  F8 T. D  K( ^/ X. |. ~1 R
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行: s! S% j: S6 X9 C7 @, @

3 D" O+ }9 ?. s5 r! F; x! y0 Y
) _: G! W; k) C( h, ~5 M
  Y" w; N# J% h# Y, ~
回复

使用道具 举报

24

主题

5

回帖

199

积分

公司现有员工

积分
199
 楼主| 发表于 2019-12-9 11:34:27 | 显示全部楼层
创建索引时,# e. \1 t$ f. Y' [2 @1 @

& h5 N1 R1 j* O0 T3 J. ]拥有唯一值的列选择性最高,那些具有很多相同值的不适合创建索引) u3 r6 K2 ~/ w; D) |* [1 o

1 X( P5 Q- M1 S! G$ u& c' M7 e( J4 p: n6 W6 C+ g) n

$ q1 ^) U9 y% l7 }$ N4 A. X2 A一个通用的规则:保持表上的所有选项。当你设计索引的时候,不要只想着已有查询需要的索
1 B  F$ b0 f6 @( M- s! p6 S2 k& R9 q
引,也要想着优化查询。如果看到需要某个索引,但是一些查询会因它而受到损害,就要问问自己是否应该改变这些查询。应该一起优化查询和索引,以找到最佳的折中。没有必要闭门造车,以得到最好的索引。
2 Z) F' r) S( ~/ _* i+ ^
+ l3 h5 R& `6 I5 B( ]% G; H/ m0 E0 Z9 p
+ J; W) ^% o! |1 i. B2 H, C
一个在多列上面的索引,为了是这个索引生效,必须满足最左原则。4 }8 k4 @! N$ ]6 }
+ `3 q" z# V0 C* l) j" u, ?$ Z
例如inex(a,b,c),这个时候如果只是用了a,c。没有使用b这个时候就不会使用索引。怎么处理5 f6 e; d& b0 G6 Z1 ~5 O
8 w' v5 ^3 |6 g+ r$ T
这里如果b是一个可以枚举的类型那么可以使用in(…),将b全部列出。这样相当于b没有起到筛选的作用,但是却可以是索引发挥作用。这个方法也不能滥用,因为会出现n*n的结果,如果枚举数相乘过大,应该选择其他方式
5 x+ U/ z) |. Z% f4 t0 U+ g
5 f9 ?5 ?6 @) G$ ?+ r! f( N1 A# ~4 a- F! b: d1 |
  T8 N0 V8 G$ j$ ~4 a4 f
避免多个范围条件,只能对其中一个使用索引
# ~, y& _$ O' g8 p/ {  i; X( P

4 w) M! t2 y: v- n) ~: k
+ O  E& C) w/ ~  _2 b$ ~% ^& ^' o索引和表维护# D9 T  ?6 \' d0 C$ f+ c
- o% B  Q4 `2 [! q3 q
表维护的主要目标:查找和修复损坏,维护精确的索引统计,并且减少碎片.
5 r. K7 w  I$ @( l6 U  K# M3 M1 t0 @8 r5 y6 O
check table table_name;
; Y: q5 A. F$ qrepair table table_name;
/ l. s7 c5 v: NShow index from table_name;检查索引的基数性) E+ C5 n8 _# x, O" x7 o' c% p
& g) T; t: b8 q# p
主要关注cardinality列,显示存储引擎估计的索引中唯一值的数量
- ^  K6 ?5 R% J
+ r' y5 \) k% \- T3 C5 k2 ~6 m* g* M! d
5 a( J' D6 b8 k8 ^/ k3 s. ]$ x
B-Tree索引能变成碎片,它降低了性能。碎片化的索引可能会以很差或非顺序的方式保存在磁盘上。1 {9 Q/ z' I8 q7 h/ D
: \" `* F0 y' D/ |) r& S! R4 W
表数据也能变成碎片化。两种类型:* k  b& B' p# L2 p
( @+ r9 q% p0 m8 U
1,行碎片
) E6 V: n8 f6 k+ M% E2 i! G# X# N% B3 J7 _8 O7 K% R" W9 B
当行披存储在多个地方的多个片段中时,就会是这种碎片。即使查询只从索引中找一行数据,行碎片也会降低性能。
; [. |( Q( E  w7 k4 s* A" V! M) `- N* a

* y5 Z* K4 N* T; \: O* Q# L7 v
& f0 W: o3 D9 v7 {0 f2,内部行碎片8 A( Z  i8 K% O" Q  ?
; B$ d0 k. E0 C- ^) E% b+ a
当逻辑上顺序的页面或行在磁盘上没有被顺序存储的时候,就会产生这种碎片。它影响了诸如全表扫描和
# c; N6 N4 I# g& w* |
2 L. G2 V# }2 @* y( C聚集素引范围扫描这样的操作。这些操作通常从磁盘上的顺序数据布局得益。
5 |: E8 ^3 K3 s( b: [2 B. P0 ^$ C1 ]( B9 k5 P

0 z  O- ^0 ~8 w' ~8 V. \9 o! L  C7 X
为了消除碎片,可以允许OPTIMIZE TABLE或转储并重新加载数据。$ r( _' ~% N5 w' L1 Q5 K

/ s! x$ U2 k4 l. X6 @% P* Q+ m/ d( t+ O6 K

) |+ G+ O) I0 u% bALTER TABLE <table> ENGINE=<engine>
8 R9 M2 m9 n1 k& t2 I  ^4 a2 `* S3 C+ Z8 m1 x+ I1 o* O

8 {9 S3 x0 V8 P- Y0 x# w7 L" M" a4 N
加速ALTER TABLE
- h( `% }5 t) I' `- n2 p
; S3 M3 h, l( c9 @
; V; ?( ~: G& y) r2 x4 k
* Q: Q$ w4 Y- PMySQL的ALTER TABLE的性能在遇到很大的表的时候会出问题。MySQL执行大部分更改操作都是新建一个需2 ^$ y6 K( T! F2 d0 e( V2 ^
( U0 v+ C. T8 a/ N
要的结构的空表,然后把所有老的数据插入到新表中,最后删除旧表.这会耗费很多时间,尤其是在内存紧张,
% ^5 z& s9 _% {
4 h1 l: b" Z1 A4 L而表很大并含有很多索引的时候.许多人都遇到过ALTER TABLE操作需要几小时或几天才能完成的情况。
3 H4 J3 |1 _/ W- t6 ^3 i4 V! [" `$ `6 z0 v' V6 ]+ I" x$ Q
传统:/ _) D- {( Y. Z. u& @1 m

# }6 s, s) z, x: A- ]ALTER TABLE table_name MODIFY COLUMN col TINYINT(3) NOT NULL DEFAULT 5;2 z) g! v+ t7 o0 b6 v7 |5 ^
理论上,MySQL能跳过构建一个新表的方式。列的默认值实际保存在表的.frm文件中,因此可以不接触表而更# |2 X+ G- H4 E
改它。MySQL没有使用这种优化,然而,任何MODIFY COLUMN都会导致表重建。3 K" T! p" k, V. B

- @6 O( U+ Y7 w: \变化:! U) X) C' b6 ~: T& b3 b

7 u: I* A2 F9 c0 oALTER TABLE table_name ALTER COLUMN col SET DEFAULT 5;
/ f4 j5 y6 T( T$ J这个命令更改了.frm文件并且没有改动表。它非常快。
2 C0 _5 G' c% W7 `还有一个CHANGE COLUMN
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2025-11-26 07:17 , Processed in 0.040630 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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