召隆企博汇论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2898|回复: 1

MySQL索引详解和优化技巧

[复制链接]

24

主题

5

回帖

199

积分

公司现有员工

积分
199
发表于 2019-12-9 11:33:39 | 显示全部楼层 |阅读模式
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能  z! L- |9 j, h# ]1 f8 F1 ?! ^8 e0 p
有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成
  K) p; G4 u; Z) E一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列& M7 N3 C: e$ b
在InnoDB中,只有事务提交后才会解锁' X& g5 W" A) T4 t2 A( s
* g8 d: N  z1 Q$ K6 Z4 Z8 c8 w
索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只; o0 j4 C9 s1 w6 a
能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。& U9 m$ ?7 Z1 Z/ ~: i: o' D
/ f  S3 ]: o  Z3 r3 l  X4 |
B-TREE
, E; U) ^9 E2 ^5 N' s0 ^能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找
1 X  M4 X: ?* ^0 r$ N: @6 c! b使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。, ^) |6 H4 ]6 G; Y
  1. 9 }+ F4 f4 f' i2 L' n3 ~- b6 |
  2. CREATE TABLE People(# W) v4 {( Q: E1 Y1 H) i( `
  3. last_name varchar(50)   not  null
      X$ }! s- |5 }2 a& H
  4.           first_name  varchar(50)     not   null6 \! r0 z# n: k6 N
  5.           dob  date      not    null# ~/ V( f& A+ z: Y4 L
  6.       gende       enum('m','f')    not    null6 X7 d4 {# L$ W, e3 e. f
  7.         key(last_name,first_name,dob)
复制代码
匹配全名
& V7 g2 H$ W8 D* D3 B全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。/ _: g! {' \* ^1 ^# U
的人。
- i. p5 N/ k* J! n$ I匹配最左前缀# k- R; E" m  A! R
B-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。
2 ^0 R, N; {4 Q( S! M% ~匹配列前缀
7 t# j+ K$ a! n: W+ \2 y可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。' ]* d# A0 w% Z- ~/ T$ h
匹配范围值+ }5 Q; ^% _1 q7 T% t! k
这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.
! @( t' a  K/ u3 R# l精确匹配一部分并且匹配某个范围中的另一部分5 o4 u. Y+ e2 D# R
这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last
. n9 z6 g! w0 o列并且对first name列进行了范囤查询。
: X1 d- O' n4 Z; V  d3 Zname
+ s& s) x! s3 J只访问索引的查询4 H5 b4 b, N+ S2 o4 h
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。/ ~: R( C9 B8 ]; Y

0 [3 P4 _6 {9 @% ?+ G  b9 Y9 [由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,7 G% N8 y. H6 p
如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查
% P8 `% S7 d" Y/ `2 n. ?找方式也可以同等地应用于ORDER BY。6 e+ J  B5 [6 J- j5 M
$ \* E6 U$ \7 T9 m) u
下面是B-Tree索引的一些局限:
1 H% d' ?% R4 S/ _$ T) F) T" s
) g( J' s( E% T3 s1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,: s9 l2 Z  S- o$ F0 W9 Q! C* W
也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓
' @0 |4 n9 i/ l8 |- I; V3 f+ J" g氏以特定字符结尾的人。6 P) A8 u( G: b
. i3 ]" ~$ q- C
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定
: X: K. s8 M6 w8 \, B9 z, U9 u/ e; |' p义first_name列的值,MySQL就只能使用索引的第一列。
6 S0 y& x3 R% j7 n5 U) V$ J3 R& ]# v4 p' n- ?; L5 f- j
3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是' A) n7 f# }2 k1 @+ G' q
范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而
1 H6 L# q; s$ l% S9 v. c不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。
2 s! }1 a# i3 C: B+ x* N/ Q) t
/ V+ k6 n* A' B+ a0 q4 x* l. ^哈希索引,空间索引和全文索引等,暂时没有设计
$ |$ P4 b+ Z: y, E
$ ^: B& t6 N/ w  R6 J0 G5 x$ T高性能索引策略
( O2 @* z( P5 U
* k4 \, G+ P4 K; ?1,隔离列,意思就是不要对查询条件中列进行计算等操作
7 ~2 ?& B, A, n& ?2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引
2 N7 W! W, W* j7 R8 v0 \7 FSelect count(distinct 列) /count(*) from table;  Y* Y6 u" g" N1 h, r3 {, u* C
看看这个值时多少,如0.0312+ H6 ~% ]- Y8 m! j' u
那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算$ L8 {1 a; D0 N4 J4 B
,这对于大表很有用。2 w9 z  D3 W) H5 M
Select  count(distinct left(列,3)) /count(*)  as  sel1,
/ Q& {' f9 Y1 p! G- H count(distinct left(列,4)) /count(*)  as  sel1  ,
7 B+ G5 N: y. G- G' l count(distinct left(列,5)) /count(*)  as  sel1,
% ^0 w7 H" H% O) f3 `! I count(distinct left(列,6)) /count(*)  as  sel1,) L/ F+ c7 S  N( g( _9 j
count(distinct left(列,7)) /count(*)  as  sel1   from table;
0 L2 Z8 X: p9 G$ T! w6 U找到接近0.0312即可。
7 j; U9 L0 H: _: w- h6 P1 L) m( f6 J. }3 ~9 a  L3 L" T+ V
Alter table table_name add key (列(7))1 [; l. B6 r/ X
3,覆盖索引
1 r. p5 n5 \6 X包含或者覆盖所有满足查询的数据索引叫做覆盖索引
' c# n$ R% _% pexplain时,extra中的会显示using index! I, n9 O* J/ f) p
这里一个重要的原则是8 ^' v' B0 g* X. T* z( V
select后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列
" c) x, {+ W; r; m& Q9 _+ W如select id from table_name;; X$ G2 \) I- a/ C- s! ]
0 k7 Q0 ?% W9 U, e
很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
+ }% e5 m# n7 Z8 t5 t" Q; k% m一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。
" ?1 Y6 @* ]( z2 R  s. Z0 P2 I/ u) `' h1 C
  1. Explain Select * from table_name where col ='nam' and col1 like '%name%';
    " b6 C5 l* o0 i2 ^
  2. Extra:using where
复制代码
该索引不能覆盖查询的原因:
0 n& \  Y* v6 z/ J' @2 {1,4 y+ t8 B7 ^7 y0 S, B
没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。7 m/ h3 j) g' O  F3 l9 `
2,
/ |6 T% X9 [1 X/ MMySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。/ K9 M0 x3 ]3 h( y) j( ~' r2 h
有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:9 R/ \3 s  I3 e5 t  T7 f
6 A; x- _6 P3 [
4,为排序使用索引扫描+ r- _  i/ ]8 f2 H0 G9 j
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。7 v( z$ L4 x4 I' O3 z' k+ B
explain输出type为index,表示mysql会扫描索引
0 N: p1 g- ~( B. ~4 q9 a3 f5 i6 V: t0 K
扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.
6 B/ B! W, Q+ O7 F
: ~) \, l$ n1 W/ |* iMySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。
1 [* N* C/ c: Z3 o9 W9 q3 T
0 {2 {6 a7 z; C$ C  b按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。$ w' B2 |. {/ d' W- o
; P0 m: J5 P5 F& j5 l
ORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。
3 C, S0 |, D! u$ M7 |$ z7 H6 `+ R) w; o  ]0 E8 d' I) E
使用join可能情况会有不同
+ ~. p3 d0 W7 U; L) @: t
7 N8 O6 r. S2 q* N( I5,压缩索引(myisam)
8 S( J9 J( |( @' [0 E% O8 z6,多余和重复索引(应该避免)
' o% }9 ^: a  U+ h
+ E# s4 B2 d% s4 {& M* Z多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)8 F( V9 B, x7 W# W% S5 A8 O( C* C' A
上有索引,那么另外一个列(A)上的
+ N# K/ c7 s5 x索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
/ [5 h% v2 K! [: q然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。
. N* u2 d0 }* }% C7 ?( \& e4 b+ j1 \5 l6 v3 @$ W! o4 X8 o
要点:3 y  Y: f& m  [1 [
在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.4 E4 M& [& V7 R$ m( o0 t( q8 a

# O* Q% V" m2 c+ b; P& R即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行4 \  Q9 P, J3 K. S5 d
3 X8 x- r% a, z$ J

/ |" L$ I, g% C+ q& a3 m" K" K$ i' j! b1 a) X6 J# ^  b2 t
回复

使用道具 举报

24

主题

5

回帖

199

积分

公司现有员工

积分
199
 楼主| 发表于 2019-12-9 11:34:27 | 显示全部楼层
创建索引时,
% `5 i7 {" E3 W. @" G( O$ ?- v" K. C' p" y  a  `& P4 p, V# f; |3 \
拥有唯一值的列选择性最高,那些具有很多相同值的不适合创建索引1 v; w) J" g! `  R2 A5 R5 k
8 I* l) t; L4 K! p: H
$ T) l4 m9 N& X1 P8 O* w

- ~2 z0 e* S- B$ T4 [* C一个通用的规则:保持表上的所有选项。当你设计索引的时候,不要只想着已有查询需要的索
, H6 y+ @1 L1 R
4 p) O/ A5 i" J9 ^5 t' z引,也要想着优化查询。如果看到需要某个索引,但是一些查询会因它而受到损害,就要问问自己是否应该改变这些查询。应该一起优化查询和索引,以找到最佳的折中。没有必要闭门造车,以得到最好的索引。* Q$ w" h: ~$ @2 H

2 _* ^5 C. M1 _  U. N! x  C. }( V) ?$ j( h$ C! Z
& s; c! o) \- H4 F8 C: }
一个在多列上面的索引,为了是这个索引生效,必须满足最左原则。
1 c* P" i: W' F; D) J% g, l
- U. N$ C" x/ X7 M) l例如inex(a,b,c),这个时候如果只是用了a,c。没有使用b这个时候就不会使用索引。怎么处理$ p6 N2 v/ |* Q5 ]
* r# ^% W+ A, a7 W. X* }0 L* W
这里如果b是一个可以枚举的类型那么可以使用in(…),将b全部列出。这样相当于b没有起到筛选的作用,但是却可以是索引发挥作用。这个方法也不能滥用,因为会出现n*n的结果,如果枚举数相乘过大,应该选择其他方式  x5 h" C' |+ U) Y( [

; H$ r/ q' r+ c: x& e7 B4 Y) R( ~: L' Q3 X; T" T; ~  J
5 S) J9 B& ^8 |1 E" v  P# X- u
避免多个范围条件,只能对其中一个使用索引' p! W8 }/ J. _9 e, w- m5 E
- p* v! C1 @3 g9 c8 N
% o& ~( \% B% w6 s3 l! F

( @  U' C" @! b- X% o, P索引和表维护7 _$ i+ S' N1 _% X

; D7 n% D! L6 C5 Y5 B表维护的主要目标:查找和修复损坏,维护精确的索引统计,并且减少碎片.
& ?5 I! n; t* O
/ Y4 r) O3 R; \  |8 y& qcheck table table_name;
: \# N: i7 ^3 Z3 w: T* Z) t4 H1 G( irepair table table_name;$ N' U  A7 ]$ K( w
Show index from table_name;检查索引的基数性
" g6 Q6 v8 F% n6 y( ^- o/ Y$ V( d9 a
主要关注cardinality列,显示存储引擎估计的索引中唯一值的数量1 ^& J. M$ U; M$ U$ f$ @7 M3 g& G

! j- R: b# [" V6 c% j6 a7 o% q) l6 I/ V$ n. T

* B+ N. j" B3 M% J! |B-Tree索引能变成碎片,它降低了性能。碎片化的索引可能会以很差或非顺序的方式保存在磁盘上。
7 u, K% O3 C- o" S% X% ^+ X3 R! X! ~, o
表数据也能变成碎片化。两种类型:
: y. B8 ~8 }3 {& z2 K3 J
" ^7 k- t0 a/ F& W$ p1,行碎片
9 x! R  c6 \: S0 ?, I" p' l( [/ b& v! m
当行披存储在多个地方的多个片段中时,就会是这种碎片。即使查询只从索引中找一行数据,行碎片也会降低性能。$ W( r$ k) T  h: j* o

7 t$ V. r+ v  ?
# f3 w, L: b4 \8 A+ G1 M% K7 G( k. p  l+ Z) a3 f8 D. K' a
2,内部行碎片
% D! g) h& X, k9 }' I" b
9 ~. n: X+ b) g+ w1 W) w当逻辑上顺序的页面或行在磁盘上没有被顺序存储的时候,就会产生这种碎片。它影响了诸如全表扫描和9 j$ i- M+ y( z$ |
) l: o/ Q, H0 a* }# v
聚集素引范围扫描这样的操作。这些操作通常从磁盘上的顺序数据布局得益。
* s# N* E4 i( o) o
& T2 R. |) r( t% E' v1 C3 x3 |( A" [7 H# v3 k

( n! H9 L% m8 V9 ?8 g' F为了消除碎片,可以允许OPTIMIZE TABLE或转储并重新加载数据。
8 w. m3 k& A/ L! O' s/ I
2 _+ U/ k1 Q& |& W
7 C$ @7 U) ]# N3 g( f
8 V8 e2 O. S7 D6 z: W0 W2 x$ XALTER TABLE <table> ENGINE=<engine>
4 B+ ?# A+ ?* b' P& K: C
" D& v' I5 }4 H# L# Q7 J( E1 H5 Z

$ Z" `/ N9 B1 }- H加速ALTER TABLE0 P# C! q( V$ b/ x2 v$ d! Y; A& @
0 C  v) |1 _% j4 ~' x# W

; n1 s! ]: B2 \0 b4 q$ X, w3 `( k; {1 S8 D- ~/ k: @
MySQL的ALTER TABLE的性能在遇到很大的表的时候会出问题。MySQL执行大部分更改操作都是新建一个需
! _' L% W% L  A5 [8 R/ h% w& [4 G6 l9 X
要的结构的空表,然后把所有老的数据插入到新表中,最后删除旧表.这会耗费很多时间,尤其是在内存紧张,
- U) {6 O1 D, v4 G1 t! ^) z: z9 z) X, `$ \# h+ \
而表很大并含有很多索引的时候.许多人都遇到过ALTER TABLE操作需要几小时或几天才能完成的情况。
- v, b3 }" P+ c0 D
$ R! o8 B$ q1 P  T: z传统:) T/ ^$ h" T/ c) R
! O% M9 I0 Q9 k
ALTER TABLE table_name MODIFY COLUMN col TINYINT(3) NOT NULL DEFAULT 5;
- Y/ @9 k/ b6 R: k6 `+ b理论上,MySQL能跳过构建一个新表的方式。列的默认值实际保存在表的.frm文件中,因此可以不接触表而更3 P* K( c. T% o
改它。MySQL没有使用这种优化,然而,任何MODIFY COLUMN都会导致表重建。4 P2 h% H9 e4 x

7 F# ?3 ~4 O( Z9 z/ E6 [( i变化:2 q* p9 d( |. H4 c+ `3 U8 A4 @

9 ?3 ~$ I1 W, y' DALTER TABLE table_name ALTER COLUMN col SET DEFAULT 5;
/ z" {) @3 A. F' E" {; `6 \+ \8 }这个命令更改了.frm文件并且没有改动表。它非常快。3 N- O6 \8 R  \# v) o2 q
还有一个CHANGE COLUMN
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-3-26 01:52 , Processed in 0.036248 second(s), 25 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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