召隆企博汇论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 2819|回复: 1

MySQL索引详解和优化技巧

[复制链接]

24

主题

5

回帖

199

积分

公司现有员工

积分
199
发表于 2019-12-9 11:33:39 | 显示全部楼层 |阅读模式
索引(MySQL中也叫“键(Key)")在数据越大的时候越重要。规模小、负载轻的数据库即使没有索引,也能: \' n1 L$ ]9 u- F/ H- B0 g
有好的性能,但是当数据增加的时候,性能就会很快下降。理解索引如何工作的最简单的方式就是把索引看成+ k1 \% U. q, q# y) {0 O, f3 t( I
一本书。为了找到书中一个特定的话题,你须要查看目录,它会告诉你页码。索引会让查询锁定更少的列1 g8 R* E5 K5 I( C0 e" m- k& k
在InnoDB中,只有事务提交后才会解锁# U  T* k) N# J- @

' X) d& G" G$ _* ]  x索引包含了来自于表中某一列或多个列的值。如果索引了多列数据,那么列的顺序非常重要,因为MySQL只
# e0 ^3 F! H' ~# O( m能高效地搜索索引的最左前缀(Leftmost Prefix)。如你所见,创建一个双列索引和两个单列索引是不一样的。
. q( Y+ D& K* F7 W+ U/ R
, l" M$ l: g. j! Z  z" D6 kB-TREE
$ |" N; g1 {* f. u8 d1 H能使用B-Tree索引的查询类型。B一Tree索引能很好地用于全键值、键值范围或键前缀查找。它们只有在查找
" i, l9 f, h: R9 m3 E* G; J使用了素引的最左前缀(Leftmost Prcfix)的时候才有用。上节中的索引对于以下类型的查询有用。+ m: }% I  \3 N" a

  1. 8 }" @5 g6 U" e9 {' J
  2. CREATE TABLE People(
    - Z5 g# X- J5 N% k
  3. last_name varchar(50)   not  null* j4 Z& d2 _5 Q7 C
  4.           first_name  varchar(50)     not   null& l0 H( P$ {% D# G; D4 o
  5.           dob  date      not    null
    2 V* D1 J* _( _2 v7 a; Y4 u2 b
  6.       gende       enum('m','f')    not    null
    . Z- X/ [3 n! C" J/ s$ h9 Y' S
  7.         key(last_name,first_name,dob)
复制代码
匹配全名1 j; R- Q! N# v- L5 N8 q" u8 Q: r
全键值匹配指和索引中的所有列匹配。例如,索引可以帮你找到一个叫CubaAllen并且出生于1960-01-01。( B$ b7 ^2 t  L) X3 r% Y
的人。
7 M; V: P% T- f% m! F' ?匹配最左前缀
2 f% j4 K4 k0 e8 IB-Tree索引可以帮你找到姓为Allen的所有人。这仅仅适用了索引中的第一列。
0 c+ r$ Z$ [( F' H8 Y" Z匹配列前缀3 u0 V8 G8 ]1 i+ g9 V( @5 L
可以匹配某列的值的开头部分。这种索引能帮你找到所有姓氏以J开头的人。这只会使用索引的第1列。
. {3 o" }7 _4 w" Y4 d匹配范围值* J4 G7 z: `9 F: i6 |+ c
这种索引能帮你找到姓大干Allen并且小干Barrymore的人。这也只会使用索引第一列.
' i% `$ X4 u3 q8 L4 S6 s1 {0 x精确匹配一部分并且匹配某个范围中的另一部分
) ^1 P  V! w7 r2 Y; E这种索引能帮你找到姓为Allen并且名字以字母K(Kim、Karl等)开头的人。它精确匹配了last, |. v6 \$ t6 S
列并且对first name列进行了范囤查询。- t2 y' c  z  J( Q  x* I
name
# y5 c9 W2 u8 Y2 f4 e0 A只访问索引的查询6 C* m; r- K. P, @( P% }( @
B-Tree索引通常能支持只访问索引的查询,它不会访问数据行。
& e# v. b9 ?) r  H+ i1 n2 M. d% l* X8 t% g; u: ~/ o
由于树的节点是排好序的,它们可以用于查找(查找值)和ORDER BY查询(以排序的方式查找值)。通常来说,- ?! g1 v' H) v* l8 p
如果B-Tree能以某种特殊的方式找到某行,那么它也能以同样的方式对行进行排序。因此,上面讨论的所有查9 m$ L* \" A+ |0 A
找方式也可以同等地应用于ORDER BY。( c) K% a8 u: L/ j

! y, A+ q' j9 `5 e" L0 O/ q" h下面是B-Tree索引的一些局限:
, q% c0 u' L5 D0 m& ~2 `6 N- S" W1 i0 |6 T# N2 G& N; d$ l
1,如果查找没有从索引列的最左边开始,它就没什么用处。例如,这种索引不能帮你找到所有叫Bill的人,
9 |0 U6 l1 k) c" _) p也不能找到所有出生在某天的人,因为这些列不在索引的最左边。同样,你不能使用该索引查找某个姓
$ Q6 y4 {5 ]- p. ~  h3 J9 G, N2 T氏以特定字符结尾的人。
  {$ n( q& u' q; S3 Y5 [, o( Y2 m3 N4 }" s6 o9 P, G1 D* ?# N
2,不能跳过索引中的列。也就是说,不能找到所有姓氏为Smith并且出生在某个特定日期的人。如果不定. ~$ M& d/ H+ ~' C3 X
义first_name列的值,MySQL就只能使用索引的第一列。
1 j/ u3 K/ u* r% F
- _) L/ C8 Z" Z3 a# C7 _# B& d3,存储引擎不能优化访问任何在第一个范围条件右边的列.比如,如果查询是where last_name='Smith' AND first_name LIKE 'J%' and dob ='1967-12-23',访问就只能使用索引的头两列,因为LIKE是
" Q6 J& R. X7 q. Z2 w6 S范围条件(但是服务器能把其余列用于其他目的)。对于某个只有有限值的列,通常使用等干条件,而+ a) Y! J/ S1 X9 d$ _2 M9 c
不是范围条件来绕过这个问题。本章稍后的索引案例中我们会举出详细的例子。5 A; L6 J" U- H* v% P
2 A  q8 A: a, r5 m( l8 A( {
哈希索引,空间索引和全文索引等,暂时没有设计
5 h, H0 o% g  C! z
  u8 F9 p, L1 A9 k高性能索引策略& P. N# C. O! |: z

) ?6 r; }5 M/ O+ o% [4 t- W1,隔离列,意思就是不要对查询条件中列进行计算等操作
1 m) d; b+ a& f, U2,前缀索引,针对blob和text,较长的varchar类型,使用前缀索引9 U9 H4 H: Z  O9 q  R* d& v$ t  |
Select count(distinct 列) /count(*) from table;8 _4 @5 I+ M& Z- L
看看这个值时多少,如0.03120 _5 z! j( S" @" D0 ^
那么就是说,如果前缀的选择率能够接近0.0312,基本就可以了。可以在同一个查询中对不同长长度进行计算
/ ?! O+ ?, L( P: d  L, c,这对于大表很有用。
0 {" p. F! I% S% v$ J7 I( {0 ^. YSelect  count(distinct left(列,3)) /count(*)  as  sel1,
' T) ^) e9 t6 _" r% q7 C count(distinct left(列,4)) /count(*)  as  sel1  ,2 _+ z! B+ A% l+ p
count(distinct left(列,5)) /count(*)  as  sel1,
% E7 A& _" o* U; F" m/ ] count(distinct left(列,6)) /count(*)  as  sel1,
8 n8 p& r  Y, q count(distinct left(列,7)) /count(*)  as  sel1   from table;
  A, U& f* Q/ H& {找到接近0.0312即可。
( i0 P' M. `: T/ n1 @& u$ r
, V# Y# @, I! XAlter table table_name add key (列(7))
1 n, Q" z' }5 v2 l3,覆盖索引
5 Z# _/ H7 B% W2 h9 {$ Y包含或者覆盖所有满足查询的数据索引叫做覆盖索引- _2 u* o& `3 y# x: H/ `0 H
explain时,extra中的会显示using index
! t' U9 S) F5 Z' \0 P这里一个重要的原则是4 l3 X- V; m% }. V/ P/ f" g
select后面的列不能使用*,要使用单独的需要查找的列,使用带索引的列
, d) D5 [# s/ A2 ~% c' v4 J如select id from table_name;
0 V$ W7 P9 m- M5 h& b) f1 e) q: B% @9 I- k
很容易把Extra列的“使用索引(Using Index)”和type列的“索引(index)”弄混淆。然而,它们完全不
$ y1 m! w2 {6 i& k/ s一样。type列和覆盖索引没有任何关系,它显示了查询的访问类型,或者说是查询查找数据行的类型。/ Q1 w  A/ v- K. x0 H+ ^
2 c4 \% O5 `1 g
  1. Explain Select * from table_name where col ='nam' and col1 like '%name%';
    1 @% I! U+ M# n! l' i$ `
  2. Extra:using where
复制代码
该索引不能覆盖查询的原因:
# f& k2 p% ]# j$ N1 r1,8 H0 X1 D2 w) n3 G
没有索引覆盖查询,因为从表中选择了所有的列,并且没有索引覆盖所有列。MySQL理论上有一个捷径可以使用,但是,WHERE子句只提到了索引覆盖的列,因此MysQL可以使用索引找到col并检查col1是否匹配,这只能通过读取整行进行。
! a3 O* t# z+ F; u" Q2,7 V) M( r5 o( m, n/ B( [( M
MySQL不能在索弓l中执行LIKE操作。这是低层次存储引擎API的限制,它只允许在索引进行简单比较。MysQL能在索引中执行前缀匹配的LIKE模式是因为能把它们转化为简单比较,但是查询中前导的通配符是存储引擎无法转化匹配的。因此,MySQL服务器自己将不得不提取和匹配行的数据,而不是索引值。
1 s% d- D. A" i* q有办法可以解决这个问题,那就是合并索引及重写查询。可以把索引进行延伸,让它覆盖(artist,title,prod_id)并且按照下面的方式重写查询:3 y% t5 \, ~  e, d& v

3 o- H) A) `" U. T( z. N4,为排序使用索引扫描" w7 Q$ J' j' Q, ^% W
mysql有两种产生排序结果的方式:使用文件排序(fileSort),或者扫描有序索引。9 D6 G1 t; x  F# K. C" J
explain输出type为index,表示mysql会扫描索引
, A: q, W! D% i, a0 P+ T+ R4 c( E5 _( I" P$ A; V
扫描索引本身是很快的,因为它只需要从一条索引记录移到另外一条记录。然而,如果MySQL没有使用索引覆盖查询,就不得不查找在索引中发现的每一行。这基本是随机I/O的,因此以索引顺序读取数据通常比顺序扫描表慢得多,尤其对于I/O密集的工作负载.
) {% _. l5 w) c( U: \" ~
3 I$ L, X- V  T* y, x3 s2 @MySQL能为排序和查找行使用同样的索引。如果可能,按照这样一举两得的方式设计索引是个好主意。
( f2 Q" f# \4 O; c/ k" A  P/ D8 P
' z- l1 O+ D! u! ?% y7 ]2 e按照索引对结果进行排序,只有当索引的顺序和ORDER BY子句中的顺序完全一致,并且所有列排序的方向(升序或降序)一样才可以。如果查询联接了多个表,只有在ORDER BY子句的所有列引用的是第一个表才可以。查找查询中的ORDER BY子句也有同样的局限:它要使用索引的最左前级。在其他所有情况下,MySQL使用文件排序。
# ]5 C# T1 \, X' V2 a3 k4 a/ P+ s
ORDER BY无须定义索引的最左前级的一种情况是前导列为常量(也就是说第一个索引不能是范围查询,如果是组合索引应该以此为常量)。如果WHERE子句和JOIN子句为这些列定义了常量,它们就能弥补索引的缺陷。
6 h5 v  d* ^, q, D  }- [2 C
- a  ?$ D, t) W! E3 I" b3 D) O使用join可能情况会有不同3 L0 N$ @2 u+ o, I0 _% s* [5 F1 `# I

# q% ]- t2 V* _: Q5,压缩索引(myisam)
, }& u: f- n9 U6,多余和重复索引(应该避免)- ~- Q2 J1 ~3 r; T
( z) N# b) L9 `9 c  ~4 z9 w( g
多余索引(Redundant Index)和重复索引有一些不同。如果列(A,B)7 ]( V7 F! [9 L) F+ R) m
上有索引,那么另外一个列(A)上的' ?0 e; {  t2 h7 j+ ]
索引就是多余的。这就是说,(A,B)上的索引能被当成(A)上的索引。(这种多余只适合于B一Tree索引。)
# m( b4 c, t. w+ q" j然而,(B,A)上的索引不会是多余的,(B)上的索引也不是,因为列B不是列(A,B)的最左前缀。还有,不同类型的索引(例如哈希或全文索引)对于B一Tree索引不是多余的,无论它们针对的是哪一列。
: S" z: c2 |% O  f2 f
7 P/ o6 k8 o. Q$ b要点:+ q0 r* x3 D9 P: l
在任何可能的地方,都要试着扩展索引(之前是一个列A上面有索引,现在两个列A,B上建立索引),而不是新增索引。通常维护一个多列索引要比维护多个单列索引容易。如果不知道查询的分布,就要尽可能地使索引变得更有选择性,因为高选择性的索引通常更有好处.2 {0 e+ V8 y( {0 e
; P. l+ p3 ?: u5 p2 r: h  o1 b
即使InnoDB使用了索引,它也能锁定不需要的行,这个问题在它不能使用索引找到并锁定行的时候会更严重:如果没有索引,mysql不管是否需要行,都会进行全表扫描并锁定每一行
+ r  \- P6 |8 h0 L  y' d; \3 W/ r4 I, a' j

9 v+ A# w  d. h2 U& X/ J7 T' A( [. L$ L8 O3 I  A5 G& D! T9 b( O$ {
回复

使用道具 举报

24

主题

5

回帖

199

积分

公司现有员工

积分
199
 楼主| 发表于 2019-12-9 11:34:27 | 显示全部楼层
创建索引时,  g& I( e& E8 X
, b' J  [8 C. k6 R4 _/ f0 ~: r/ t
拥有唯一值的列选择性最高,那些具有很多相同值的不适合创建索引
: Z$ K1 C- {7 P2 s1 V5 d- h. V0 e$ f% l7 ^; m8 F

" a) O' S* T' {- ~* ?+ s8 o4 N
1 a; i& R. u7 b# h& K7 {& n一个通用的规则:保持表上的所有选项。当你设计索引的时候,不要只想着已有查询需要的索  z3 |. O  v2 `4 k
/ D% b9 U0 z) R6 o$ B) \% A
引,也要想着优化查询。如果看到需要某个索引,但是一些查询会因它而受到损害,就要问问自己是否应该改变这些查询。应该一起优化查询和索引,以找到最佳的折中。没有必要闭门造车,以得到最好的索引。
8 K8 r! }: X5 b8 ~+ ?
/ Z5 d! L; w# W6 t3 D) m) ^% Z3 Q8 L
7 I8 z; u2 X' L1 r: E1 ?$ Z! k0 {* D
一个在多列上面的索引,为了是这个索引生效,必须满足最左原则。6 z* g) Y1 q; q* Z6 @7 B: i  n1 `

9 M$ Z: @3 r/ b7 Q( @例如inex(a,b,c),这个时候如果只是用了a,c。没有使用b这个时候就不会使用索引。怎么处理1 d# u5 W% y- ~5 N; U3 j5 o3 E

, ]" A' P* k) u( |这里如果b是一个可以枚举的类型那么可以使用in(…),将b全部列出。这样相当于b没有起到筛选的作用,但是却可以是索引发挥作用。这个方法也不能滥用,因为会出现n*n的结果,如果枚举数相乘过大,应该选择其他方式; X+ h  p& a6 h" ^  j0 ?
  W/ u2 v1 q! B: _1 t( j. d/ O

0 _+ ?: P, g* W+ K) S
2 g7 U# l7 t: ^% A& L/ C0 w3 a避免多个范围条件,只能对其中一个使用索引
8 P/ I/ u, V' E9 Q- ]- K/ D8 b" }; A( m' O$ Y( m$ l
' j  D4 {. x" `

1 D' E5 R7 J  P" x) p5 ~( u索引和表维护5 k0 P2 D/ }$ @9 f6 J
/ Q6 |5 q( h, y- w" h- P/ V) x
表维护的主要目标:查找和修复损坏,维护精确的索引统计,并且减少碎片.
, G0 q& b4 m6 _6 |: }$ s/ s4 W8 R. C. {- ~
check table table_name;1 ?2 T2 d) X* o
repair table table_name;3 \2 q) W) r: u- a9 J' [
Show index from table_name;检查索引的基数性
# W; k$ @8 u' y. m, x) w+ C5 U& ?8 ]1 d7 F
主要关注cardinality列,显示存储引擎估计的索引中唯一值的数量, O% h+ j- a: \' q# {

+ Y+ `* m% l1 @1 _8 K) ^1 G$ i+ Y' S  q- z/ k: f& ^  u( d
4 I: s) m# O/ @, b: a2 k
B-Tree索引能变成碎片,它降低了性能。碎片化的索引可能会以很差或非顺序的方式保存在磁盘上。, m( r6 I1 q7 @6 Q1 T2 f6 Q: f

4 j+ @( p+ q# b5 ]! {. s* r: a表数据也能变成碎片化。两种类型:' ~8 }! l. t, f

! [8 d+ W6 ^. [% z- K$ y1,行碎片1 t* i% |4 J' m

- b0 b9 V+ `' k当行披存储在多个地方的多个片段中时,就会是这种碎片。即使查询只从索引中找一行数据,行碎片也会降低性能。8 c3 J! U" \3 ?- ^3 A7 b0 R
! Y; V8 h2 V9 ~6 S9 t2 F' O1 }. b

4 G6 E& I6 u. t, r/ M' V0 j7 w$ H$ L7 t9 y- _) Z/ {, `8 K! T  r
2,内部行碎片
. a3 s0 n% w, m6 W7 _. w* R( U4 L. z- S6 a
当逻辑上顺序的页面或行在磁盘上没有被顺序存储的时候,就会产生这种碎片。它影响了诸如全表扫描和/ m# l" j% y" N4 e# r7 _! D( b

7 C3 F" L: {& m0 w# U" p8 ?4 T聚集素引范围扫描这样的操作。这些操作通常从磁盘上的顺序数据布局得益。: R! y% I3 |% L/ l$ y& R: ]

2 q  S" x& u/ S; E7 W( d+ R" S
* I  q8 K' F3 L! W! L9 c! C' a& R0 i$ A* [# g0 `
为了消除碎片,可以允许OPTIMIZE TABLE或转储并重新加载数据。* x) V7 G. Q2 J0 S8 D) Z5 _

! D% o9 z) t8 ~  O
# D& v7 T/ }4 T1 b4 \- l% x+ U. T' N3 e. \& m2 y, ^
ALTER TABLE <table> ENGINE=<engine>1 z) M4 [- s& g5 z# v

) r# [  s& s" x- _: L$ D" X3 x" j
% h# {4 F$ r1 a* [% T' M
加速ALTER TABLE
. Q0 T9 V+ U$ X) z8 @5 v! j1 K$ @5 E2 e$ k* w! Z" X1 B* v+ y) e
4 Q) R; u: N2 j% m

4 Z* F8 Q: N$ `. j* s. G! }6 |MySQL的ALTER TABLE的性能在遇到很大的表的时候会出问题。MySQL执行大部分更改操作都是新建一个需! p: m/ Y' D! }. O4 v
, Q3 e: P* Q2 e; j$ ^: }+ ~& E
要的结构的空表,然后把所有老的数据插入到新表中,最后删除旧表.这会耗费很多时间,尤其是在内存紧张,
4 m* H. w1 B; F- Q! o. w# }' G0 T  h" e7 F3 X
而表很大并含有很多索引的时候.许多人都遇到过ALTER TABLE操作需要几小时或几天才能完成的情况。
- k: a, p$ G2 N: c4 `* c
! l) B/ s, T- J& I& G, {传统:! G; ]- g' X' G" [

* O1 o2 v4 R, VALTER TABLE table_name MODIFY COLUMN col TINYINT(3) NOT NULL DEFAULT 5;4 x( w3 i/ F1 H8 i& `; n
理论上,MySQL能跳过构建一个新表的方式。列的默认值实际保存在表的.frm文件中,因此可以不接触表而更
, I" H. h8 B& \8 Y/ w% N7 @改它。MySQL没有使用这种优化,然而,任何MODIFY COLUMN都会导致表重建。+ Q2 \' ~! f% M1 C1 V

! X- u$ h; e7 I变化:3 x* Q( D& ^7 k; ~; z
) l4 z: C4 {8 ~+ H" M& S  C3 D1 B0 c
ALTER TABLE table_name ALTER COLUMN col SET DEFAULT 5;
( c& P8 I0 c- g. j8 G- b& D# F这个命令更改了.frm文件并且没有改动表。它非常快。$ r8 a$ k2 k% }! L: i
还有一个CHANGE COLUMN
回复 支持 反对

使用道具 举报

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

本版积分规则

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

GMT+8, 2026-2-11 14:01 , Processed in 0.038605 second(s), 26 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

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