召隆企博汇论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2873|回复: 1

MySQL索引详解和优化技巧

[复制链接]

24

主题

5

回帖

199

积分

公司现有员工

积分
199
发表于 2019-12-9 11:33:39 | 显示全部楼层 |阅读模式
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能
9 a0 F( S! C2 r& L3 Q) O$ u2 }0 h有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成/ N' @/ U) f6 m4 d8 e
一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列
: [* Q5 m+ r2 l在InnoDB中,只有事务提交后才会解锁- D$ h! S( U; v) Y+ G- g! m$ J
; X1 m! O6 z7 K) C' i6 {
索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只; t( A3 v! Y" S
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。
; X" K2 b% X" @7 p# k, k0 z; n& P) P8 b% y0 k
B-TREE
& E" W1 n- d+ W; v) k4 D) l% [9 h能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找9 x+ P. w$ }; ]; {* b# D5 h
使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。! g; @0 F5 _& b$ J

  1. - N) T2 L  L8 M0 t6 R% f% B! D
  2. CREATE TABLE People(3 l& }$ x# b0 r0 |
  3. last_name varchar(50)   not  null) q9 Z2 E0 z0 I8 D! ^4 n
  4.           first_name  varchar(50)     not   null; Q( E9 [8 w4 `2 T9 r# V' t, p, p
  5.           dob  date      not    null
    * I5 S/ w8 Z+ T1 p" a/ C: f
  6.       gende       enum('m','f')    not    null
    ) J0 l+ ?0 y8 w) r4 L
  7.         key(last_name,first_name,dob)
复制代码
匹配全名" q) m1 E9 _! V3 b0 C. h( C! ^0 d# I& M
全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。: \( P7 l7 Q$ b9 y- p
的人。# Y/ N. M+ }# k% D. }: n
匹配最左前缀
/ t% Y  I* d) I: \! aB-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。9 o2 u; G/ x) ]' Z( n% S8 E6 r
匹配列前缀
& Q  K: h7 ]2 h, }4 ?1 @% @可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。
7 r! Z' f5 S$ C% z! F* a匹配范围值$ _6 _& }/ D3 Q4 N  [6 Q0 p
这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.4 ~+ K' Y) \. ^- ^5 ^$ B- h
精确匹配一部分并且匹配某个范围中的另一部分
4 K' _$ c7 P! k* p; A这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last
% E$ t. \' h8 h3 h  i! z4 L- W) u列并且对first name列进行了范囤查询。- Z* I. ^1 W( x$ f& t
name3 h6 W5 v* C; G5 U5 Q* U6 j
只访问索引的查询) T* j: C9 [# J
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。
/ }/ ^, b+ W( L, W; K6 |
& i+ j8 {9 ]2 e% ~7 ?1 I由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,0 d/ b! |6 P) r$ h2 u! ?* z% K
如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查
, S- e8 x: g- W5 k- b) O找方式也可以同等地应用于ORDER BY。! L* H/ ]& T7 t, b9 g

  v2 C. A$ n! I. {6 o下面是B-Tree索引的一些局限:( t: G0 f  @" O) t
" C% i% t% J6 s* Z# v
1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,* y& H# q: r% h6 Y9 R( @# r
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓
' @6 Q" u, T9 ~- K4 }) Y, E氏以特定字符结尾的人。
* V. f3 I/ L. s$ C! J/ [3 h: o. D7 i" M( a3 y$ W9 X3 W- ^2 }8 q( a
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定+ L' @% c" p  _3 M* Y( M1 T9 y" Z7 P
义first_name列的值,MySQL就只能使用索引的第一列。
$ W0 F. N) |! H7 b3 t
7 }, U* F5 M% E3 _7 s; ?3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是0 g  S2 ^) |3 q( q
范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而& Y, ~* N' J. |! m
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。
' h% B* m. \) [0 o' N6 c- q
# x+ I% X1 D6 X: u' A/ ?/ ?哈希索引,空间索引和全文索引等,暂时没有设计
# X! [+ n9 C$ D$ I) P* ~( Z2 w1 h# w. J5 j  X: l9 x  w' x
高性能索引策略
* Y, W' g8 \7 H' V9 w: S: g) G  ~( ]) T: z0 h! O' N% n5 w
1,隔离列,意思就是不要对查询条件中列进行计算等操作: m5 I+ ~$ h4 h" g0 i
2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引
, a+ C; [' G" A2 J$ t% aSelect count(distinct 列) /count(*) from table;/ U1 e/ z* l" {% u& z5 E5 e  b
看看这个值时多少,如0.03128 o2 S8 x! \6 t
那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算) J& c5 t0 U8 U! o* A1 j! H/ i
,这对于大表很有用。& h% D' c: b* w
Select  count(distinct left(列,3)) /count(*)  as  sel1,1 f9 _$ R; {2 l6 ?/ B& L4 ~
count(distinct left(列,4)) /count(*)  as  sel1  ,
" n9 h) t5 E  O; i, C' W# D count(distinct left(列,5)) /count(*)  as  sel1,8 G* M6 A7 I) ^+ Z$ g* V
count(distinct left(列,6)) /count(*)  as  sel1,- k6 y( ?3 g" D3 l
count(distinct left(列,7)) /count(*)  as  sel1   from table;9 s8 H6 z( A3 T: x% S, _
找到接近0.0312即可。, g" h2 |9 H, T/ U

* M) W7 C/ G2 G% k1 M* X) @Alter table table_name add key (列(7))( ~! D" E/ X. U& B, Y( X( l0 C9 \
3,覆盖索引1 j1 ?& \( O3 \7 X
包含或者覆盖所有满足查询的数据索引叫做覆盖索引
4 \4 C! \- C) n1 b0 u: texplain时,extra中的会显示using index, ]2 c# o0 y2 H9 P$ m# w9 G
这里一个重要的原则是* \3 m" P; a! F/ G
select后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列, I8 c7 q- y$ e+ w5 S
如select id from table_name;5 ]8 o. i( q6 {7 I* [

5 ^9 C% g; v. y  D# H  @6 ]6 H很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
. J; N# m  K- n( |6 r一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。+ X# v+ W* R" M3 k3 g3 L* I

' [" |% F4 }+ W' f, t# Q
  1. Explain Select * from table_name where col ='nam' and col1 like '%name%';' I1 H+ r2 r, u7 a4 m/ T
  2. Extra:using where
复制代码
该索引不能覆盖查询的原因:& A6 G7 J4 k" x# }5 w
1,
& e  h) X0 m! z, T' @5 o8 |# F没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。6 v! t. d* g7 V7 w
2,+ x0 i7 E4 u# ~  S4 H5 ^$ ~
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。7 K8 U4 F+ w! n& R
有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:
2 @$ S4 r5 c8 K* b4 }6 `4 Q# {6 N& g' s  D3 F
4,为排序使用索引扫描+ W! @  Q. \4 P" }5 J1 w
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。
( V8 l9 q# I: W8 N+ |explain输出type为index,表示mysql会扫描索引
' l% X) p- D5 |. Y( B$ c4 u. X& h9 s6 ]3 g
扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.
* s4 x( b1 w2 L" U" h* \# A1 O4 S3 [+ l
MySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。+ B; u1 e" V& c1 N! ^

$ q+ e, p  N6 N8 z' Z按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。7 G; \/ T" A; d# J

- c! I, Y! @% b* uORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。
4 q9 o5 Q5 ]7 w  f7 X8 i/ q" W2 O3 ~" p2 g" b; V
使用join可能情况会有不同4 a' U0 s2 V% g* N8 I3 f

9 @  L  c  _0 x5,压缩索引(myisam)3 S# }6 K! a4 k; w; |
6,多余和重复索引(应该避免)
: x$ C6 z& |. T+ D5 [' J
) u) `9 ^. {6 x, S5 x多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)
) F3 h* ]( t! z6 Q7 H1 K上有索引,那么另外一个列(A)上的
- K# l8 \3 W# v* r/ j索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
8 @0 n$ w# Z/ _4 e7 T然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。
+ `& D* b3 z* ~3 H7 T- `7 l4 q
& g: f! d3 I) [  J5 s7 ^; e& {' c要点:) K2 P6 r4 j- \& q# X
在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.& v! Q! E, c4 ?& Y

! y( j4 a% F1 {4 R& t即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行. i0 C% d5 j) {6 L

0 r" B. C" ]$ [+ ]" E6 P/ l* ^; q
1 |6 S$ L6 p5 b$ D( J1 a- K3 W! `$ b! j7 z
回复

使用道具 举报

24

主题

5

回帖

199

积分

公司现有员工

积分
199
 楼主| 发表于 2019-12-9 11:34:27 | 显示全部楼层
创建索引时,
0 m4 u; c3 B2 A& A9 G8 \$ n- Y, W
拥有唯一值的列选择性最高,那些具有很多相同值的不适合创建索引
' `0 }# Y' V4 I) p% ~* v# n/ y2 a0 K' c$ y0 q( J
: ~! O3 ~9 I& ~/ q+ ~8 Q1 d* z5 y
' s& l' c' B& P. D- g3 o
一个通用的规则:保持表上的所有选项。当你设计索引的时候,不要只想着已有查询需要的索" K7 O3 [& A/ r. G% P  j2 g6 `, h
3 t8 F, y$ f3 m4 }% z1 J
引,也要想着优化查询。如果看到需要某个索引,但是一些查询会因它而受到损害,就要问问自己是否应该改变这些查询。应该一起优化查询和索引,以找到最佳的折中。没有必要闭门造车,以得到最好的索引。+ f3 j8 T% Q6 a" J$ _
) l: |7 ]" n+ f5 p3 m- c9 }3 S

1 a7 L' ]5 E5 n2 ]$ B/ a0 A% }$ h% W/ B
( a. w, g# W# r4 }一个在多列上面的索引,为了是这个索引生效,必须满足最左原则。: F2 A+ J# u+ B7 ?( }! t" P

2 o+ ^  M/ M, N, u- m  q! Y例如inex(a,b,c),这个时候如果只是用了a,c。没有使用b这个时候就不会使用索引。怎么处理
/ {2 f0 L  R5 L7 j+ G% Q& H, q
; {5 d' }1 ?& x6 E0 J- t0 J. @这里如果b是一个可以枚举的类型那么可以使用in(…),将b全部列出。这样相当于b没有起到筛选的作用,但是却可以是索引发挥作用。这个方法也不能滥用,因为会出现n*n的结果,如果枚举数相乘过大,应该选择其他方式
; B2 y/ j& @8 g- L6 w' V8 T, |4 |6 A: W$ P; |2 J

* [8 n5 V: o$ z/ V) _; A) z5 Q
9 y, |4 C" y. B) }: `; S3 G) G& U避免多个范围条件,只能对其中一个使用索引
* L) F/ A# W6 \2 P( N
# r9 d+ [* {8 [9 j+ x+ @3 r
! ~9 P6 m5 F6 c
2 h4 E- |: ?. h/ [9 B; d索引和表维护. j1 u: U' _' h

( ~5 v8 K2 K' ^3 W0 e1 M表维护的主要目标:查找和修复损坏,维护精确的索引统计,并且减少碎片.' ~4 W. b* B/ S
$ j* [: w2 t0 W  {) G
check table table_name;
0 G3 H9 V4 S. [8 ^repair table table_name;
: F4 k' |5 h( \Show index from table_name;检查索引的基数性& Q8 P: _: ?& Y* U, ?! ~

; V( P! s5 `* y8 w, M1 v0 ~0 r0 Y主要关注cardinality列,显示存储引擎估计的索引中唯一值的数量! y  C9 I- ~& i" Q- b

. g* r$ E8 r+ B! @! K# E) g2 p' N$ e! Q$ Q* k
. d; m4 e2 c: w5 O/ O$ U& M
B-Tree索引能变成碎片,它降低了性能。碎片化的索引可能会以很差或非顺序的方式保存在磁盘上。
8 M5 S+ X* X/ M' K8 P5 t* J$ y1 S9 |
表数据也能变成碎片化。两种类型:2 s6 H+ J- r) V# J; r: i
# |! r+ [+ T6 w6 U+ l* v
1,行碎片3 G( K: v  R, |9 a
+ O/ q1 _: s2 L$ V/ A! w7 h1 H
当行披存储在多个地方的多个片段中时,就会是这种碎片。即使查询只从索引中找一行数据,行碎片也会降低性能。
% H7 Z3 X$ R+ a3 b8 Q: [0 G/ E
1 o' v, m8 }+ `1 V( z! B9 z, c6 X2 R+ G

: a& N/ c5 ^' K2,内部行碎片
' C, I  @6 y& N% K6 F' }' C$ |+ x( q7 R$ F
当逻辑上顺序的页面或行在磁盘上没有被顺序存储的时候,就会产生这种碎片。它影响了诸如全表扫描和
1 l/ A9 j" S2 M( f2 w
4 \3 ~9 K" @# l1 ?, T聚集素引范围扫描这样的操作。这些操作通常从磁盘上的顺序数据布局得益。' a2 G: i$ _2 w% @9 }

4 V$ A; e4 t- S, {! Z; h1 ^  k) ^, ~/ o8 l- ~/ @
+ M' l& T  u) R+ ?
为了消除碎片,可以允许OPTIMIZE TABLE或转储并重新加载数据。
+ P9 U( [2 t  O$ C7 a- g' k6 c7 X' e! J1 \; R6 _" _
8 F, L5 q+ m; v" B

# \3 b, L8 e, P! t: `1 c, _ALTER TABLE <table> ENGINE=<engine>
. ?+ S' p% I: `# E  z7 E* e) P7 V/ W- d/ t

; c' I9 u  ^8 r. p" `$ K$ T$ Y0 ~/ X7 D
加速ALTER TABLE) K4 D, e" d& g+ F; M' Q# ?/ @( k1 o) W/ F

( i7 u2 V$ b5 E. S' J1 z; l8 k- D; k$ k5 d

7 J% i2 e3 _; Z' p5 x6 t" l- C% AMySQL的ALTER TABLE的性能在遇到很大的表的时候会出问题。MySQL执行大部分更改操作都是新建一个需) _1 W% `* A' S  G% p% }

6 U, n7 o/ H( {3 [要的结构的空表,然后把所有老的数据插入到新表中,最后删除旧表.这会耗费很多时间,尤其是在内存紧张,5 {7 n+ s# y+ m4 C3 j
  V1 j: X! L. D/ `  I) u! h, A
而表很大并含有很多索引的时候.许多人都遇到过ALTER TABLE操作需要几小时或几天才能完成的情况。5 b  R* |0 j9 i7 a2 S$ x

* ]8 m; V1 w* @% C( k) G* D7 `  Q传统:
: l5 p/ d( f& k* z
# [/ g' K8 G$ s4 [6 H+ b. GALTER TABLE table_name MODIFY COLUMN col TINYINT(3) NOT NULL DEFAULT 5;
; b4 S7 G; ^+ ?3 T  m! {/ ~# L4 s( ^理论上,MySQL能跳过构建一个新表的方式。列的默认值实际保存在表的.frm文件中,因此可以不接触表而更  t( X$ C9 g( v& v8 X1 ^, {
改它。MySQL没有使用这种优化,然而,任何MODIFY COLUMN都会导致表重建。* T9 C5 ~! S* H  h
5 s! X5 R) n. ^2 H5 H
变化:
0 L& v( N0 K4 X( Z/ M* o! i9 a
" N/ ~: {1 o& c+ Q5 X% |; [( vALTER TABLE table_name ALTER COLUMN col SET DEFAULT 5;- r% H5 W) W  m
这个命令更改了.frm文件并且没有改动表。它非常快。, E, \6 w' `+ i! i( O
还有一个CHANGE COLUMN
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-3-20 15:14 , Processed in 0.039808 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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