高性能MySQL-笔记

高性能MySQL

一、MySQL结构

每一个客户端链接会在服务器进程中拥有一个线程,该链接的查询只会在单独的线程中执行。MySQL会解析查询,并建立内部数据结构,而后对其进行各类优化。对于SELECT语句,解析查询以前,服务器会先检查缓存,若是可以在其中找到对应的查询,服务器就再也不执行查询解析,而是直接返回缓存中的结果。mysql

并发控制

MySQL在两个层面实现并发控制:服务器层与存储引擎层。算法

在处理并发读或写时,能够经过实现一个由两种锁组成的系统来解决问题。这两种锁一般被称为共享锁和排他锁,或者称为读锁和写锁。读锁是共享的,或者说是相互不阻塞的,多个客户能够在同时读取同一数据。而写锁是排他的,同一时刻只能有一个用户可以写入,并防止其余用户读取正在写入的数据。sql

锁粒度是指加锁的对象的大小。显然,锁的粒度越小,并发控制效率越高。锁的各类操做,包括得到锁、检查锁和释放锁等,都会增长系统开销。所以,若是系统花费大量时间来管理锁,而不是用来获取数据,就会影响系统性能。数据库

有两种常见的缩策略,表锁和行级锁。表锁开销较小,可是并发控制很差。行级锁能够很好地实现并发控制,可是开销比较大。缓存

事务

事务将几个操做做为一个总体,要么所有执行,要么所有放弃。事务的四大特性ACID安全

  1. 原子性:整个事务要么提交要么回滚,必须做为一个总体。(几个操做做为一个总体)
  2. 一致性:数据库老是从一个一致状态转换到另外一个一致状态。(不增就不减)
  3. 隔离性:事务提交以前所作的修改不可见。
  4. 持久性:事务提交以后,将修改持久化到数据库中。

事务处理也会使系统作更多额外工做,用户能够根据是否须要进行事务处理,选择合适的存储引擎。服务器

下面是四种事务的隔离级别:session

  1. READ UNCOMMITTED (未提交读):没有提交、但已经被修改的数据能够被读到,也叫脏读
  2. READ COMITTED (提交读):一个事务开始时,只能看见已经提交的修改,而且所作的修改对其余事务不可见。这种存在的一个问题叫不可重复读,就是指事务A读取某条记录以后,事务B对其进行了修改,这时当A再次读取该数据的时候就会发现与以前读取的结果不同。
  3. REPEATABLE READ (可重复读):事务A对数据库全部行作了修改时,事务B对向数据库中插入了一行新的数据。这时A发现还有没有修改的记录,就行发生幻觉同样,叫作幻读。MySQL默认级别。
  4. SERIALIZABLE (可串行化):事务按照串行的方式执行,并在每行数据上加锁,可能产生大量的超时和锁争用问题。

死锁指的是多个事务在同一资源上相互占用,并请求对方占用的资源,致使恶性循环的现象。数据库系统中实现了各类死锁检测和死锁超时机制。数据结构

在MySQL中默认是自动提交事务的,每一个查询操做被看成一个事务。可使用set autocommit来设置是否自动提交。能够经过set session transaction isolation level来设置隔离级别。在同一事务中使用多种存储引擎是不可靠的。并发

多版本并发控制MVCC

MySQL中大多事务型存储引擎实现的都不是简单的行级锁,通常同时实现了多版本并发控制。MVCC的实现是经过保存数据在某个时间点的快照来实现的。即,不论须要执行多长时间,每一个事务看到的数据都是一致的。典型的实现有乐观并发控制和悲观并发控制

InnoDB的MVCC经过在每行记录的后面保存两个隐藏的列来实现。这两个列,一个用来保存过时(被删除)的版本号,一个用来保存建立的版本号。每一个事务开始时,会使版本号递增。事务开始时的版本号被用做事务的版本号:

  1. 在查询的时候,会检查版本号,并返回小于等于当前版本号,而且删除版本号大于当前事务版本号的记录。
  2. 删除时将当前版本号做为删除版本号。
  3. 插入时将当前系统版本号做为行版本号。
  4. 更新时,插入一行新纪录,并保存当前系统版本号做为行版本号,同时保持当前系统版本号做为以前记录的删除版本号。

MySQL存储引擎

.frm文件用来保存表的定义。

MySQL默认存储引擎,采用MVCC来支持高并发,而且实现了四个标准的隔离级别。基于聚簇索引创建。

MyISAM不支持事务和行级锁,并且没法在崩溃以后安全恢复;它将表存储在两个文件中:数据文件和索引文件,分别以.MYD.MYI为拓展名。它对整张表加锁,而不是某行。若是建立并导入数据以后,不会再进行修改,可使用压缩表来减小空间占用和IO,从而提高查询性能。

还有一些其余的存储引擎。

除非某些InnoDB不具有的特性,而且没有其余方法能够替代,不然都应该优先优先选择InnoDB引擎。除非万不得已,不然不要混合使用多种存储引擎,不然可能带来一些复杂的问题及潜在的BUG.

修改存储引擎:

  1. Alter table tbl_name engine = Innodb:须要执行很长时间,MySQL会按行将数据从原表复制到一张新的表中,在复制期间可能会小号系统的IO能力,同时在原表上加锁。
  2. 导入导出:用mysqldump将数据导出到文件,燃火修改文件中create table语句的存储引擎选项,注意要同时修改表名。
  3. CREATE & SELECT:数据量不大的时候,能够先建立一张使用新的存储引擎的表,而后利用INSERT SELECT将原表中的数据插入到新表中。当数据量比较大的时候,可使用between语句来分批次操做完成。

二、Shcema与数据类型优化

2.1 选择合适的数据类型

2.1.1 选择的数据类型原则:

  1. 更小的一般更好:占用更好的磁盘、内存和缓存,处理时所需的时间周期更小。
  2. 简单就好:简单的数据类型操做须要更少的CPU周期。如,整数比字符操做代价更低。
  3. 进来避免null:null的列使得索引、索引统计和值比较都更复杂。

2.1.2 整数类型

MySQL整数能够指定宽度,如int(11),对大多数应用这是没有意义的:它不会限制值的合法范围,只是规定了MySQL的一些交互工具用来显示字符的个数。对于存储和计算来讲,int(20)和int(1)是相同的。

2.1.3 实数类型

由于须要额外空间和计算开销,因此尽可能只在对小数进行计算的时候才使用decimal。在数据量比较大的时候,能够考虑使用bigint代替decimal,将存储的单位根据小数的位数乘以相应的倍数便可。

2.1.4 字符串类型

CHAR适合存储短的短的字符串,或者全部的值都接近同一长度。对于常常变动的数据,CHAR也比VARCHAR好,由于定长的CHAR不容易产生碎片。对于很是短的字符串,CHAR也比VARCHAR更好,由于VARCHAR还须要1或2个额外的字节存储字符串长度。

可使用枚举替代经常使用的字符串类型,枚举能够把一些不重复的字符串存储成预约义的集合。枚举字段是按照内部存储的整数而不是字符串进行排序的。枚举很差的地方是当向枚举中增长字段的时候,须要使用ALTER TABLE语句来进行修改。因此,对未来可能会变的字符串,使用枚举不是个好的主意。

2.1.5 时间和日期类型

若是须要将时间保存到毫秒级别,可使用BIGINT.

2.1.6 位数据类型

可使用BIT列在一列中存储一个或多个true/false值。BIT(1)定义一个包含单个位的字段,bit(2)存储两个位。bit列最多存储64个位。

MySQL将BIT看成字符串类型,而不是数字类型。当检索bit(1)时,结果是一个包含二进制0或1值的字符串,而不是ASCII码的"0"或"1"。而后,在数字上下文场景中检索时,结果将是为字符串转换成的数字.

2.2 范式和反范式

所谓的范式就是,好比,若是咱们须要学生和学校的记录,若是咱们将学生和学校放在不一样的表中就符合范式,若是放在同一表中就是反范式的。

范式化具备一些好处,好比:操做更快;每次只要修改少许的数据;表更小,适合放在内存中,操做更快。缺点是一般须要表关联。

不过实际咱们并不彻底遵照范式和反范式的规则。

2.3 缓存表和汇总表

缓存表表示存储那些能够简单地从schema其余表获取数据的表。汇总表保存的是使用GROUP BY语句聚合数据的表,使用汇总表的缘由是,实时计算和统计值是很昂贵的操做,由于要么须要扫描表中的大部分数据,要么只能在某些索引上才能有效运行。

计数器表是用来统计某个操做的次数的表,咱们能够在一个表中定义一个名为cnt的字段来表示操做的次数,而后每次执行了操做以后将其加1。可是,加1须要更新操做来完成,每次更新的时候要获取记录的锁,所以并发效率不高。解决这个问题,咱们能够再增长一个字段slot做为随机的槽,每次执行操做的时候,咱们使用随机数选择某个slot,并对其进行+1更新(只用锁住部分数据,所以效率比较高)。最后统计的时候将所有记录加起来便可。

三、建立高性能的索引

3.1 基础

数据库的索引相似于书的索引,实际的查找某个值的时候,先按照值进行查找,而后返回包含该值的数据行。索引能够包含一个或多个列的值,若是索引包含多个列,那么列的顺序也很重要——索引对多个列排序的依据是CREATE TABLE时定义索引的顺序,因此MySQL只能高效地使用索引的最左前缀列。

在MySQL中,索引是在存储引擎层而不是服务器层实现的。因此,没有统一的标准:不一样存储引擎工做方式不一样。MySQL支持的索引类型以下:

3.1.1 B-Tree索引

一般人们所说的索引。实际上不少存储引擎使用的是B+Tree. B-Tree索引适用于全键值、键值范围或键前缀查找。类型,以多列索引key(last_name, first_name, dob)为例:

  1. 全值匹配:指定查询的人的fitst_name, last_name和dob;
  2. 匹配最左前缀:查找指定了last_name的记录;
  3. 匹配列前缀:匹配某一列的值的开头部分,好比last_name以J开头;
  4. 精确匹配某一列并范围匹配另外一列:查找last_name为Allen,而且first_name以k开头的;
  5. 只访问索引的查询:B-Tree一般能够支持只访问索引的查询,即查询只须要访问索引,而无需访问数据行。

B-Tree的一些限制:

  1. 若是不是按照从最左列开始查找,则没法使用索引。例如没法查找只指定了first_name或者dob的记录;
  2. 不能跳过索引中的列:不能在查找的时候只指定了last_name和dob,那么dob不会使用索引。
  3. 若是查询的时候有某个列的查询范围,则其右边的全部列都没法使用索引优化查找。好比对last_name使用了like,那么first_name和dob将不会使用索引。

3.1.2 哈希索引

基于哈希表实现,只有精确匹配索引全部列的查询才有效。由于它对每行中的全部索引列计算出一个哈希码,做为哈希表的键(原理是基于拉链法的解决碰撞的策略)。在MySQL中只有Memory引擎显式地支持哈希索引,Memory引擎同时也支持B-Tree索引。

哈希索引只须要存储对应的哈希值,因此索引的结构十分紧凑,这让哈希索引的查找速度很是快。然而,它也有自身的限制:

  1. 哈希索引只包含哈希值和行指针,而不存储字段值,因此不能用索引中的值来避免读取行
  2. 哈希索引数据并非按照索引值顺序存储的,因此也就没法用于排序
  3. 哈希索引不支持部分列匹配查找,由于它用全部索引列来计算获得哈希值。
  4. 索引列只支持等值比较,理由同上;
  5. 哈希索引数据查找很是快,除非有不少哈希冲突;
  6. 若是哈希冲突比较高,一些索引维护操做的代价也会很高。

3.1.3 空间数据索引(R-Tree)

MyISAM表支持空间索引,能够用做地理数据存储。

3.1.4 全文索引

它查找的是文本中的关键词,而不是直接比较索引中的值。全文索引相似于搜索引擎作的事情,而不是简单的where条件匹配。在相同的列上建立全文索引和基于B-Tree的索引不会冲突。

其余索引,还有分型树索引。

3.2 索引的有点

索引的优势有:

  1. 索引大大减小了服务器须要扫描的数据量;
  2. 索引能够大大帮助服务器避免排序和临时表;
  3. 索引能够将随机IO变为顺序IO。

对于小型的表,使用全表扫描更高效;对中到大型的表,使用索引很是有效。对于特大型的表,创建和使用索引的代价会随之增加。这种状况下可使用分区来查出一组数据,而不是一条一条地匹配。

3.3 高性能索引的策略

3.3.1 独立的列

若是查找中的列不是独立的,则MySQL不会使用索引。独立的列是指索引列不能是表达式的一部分,也不能是函数的参数。好比

select * from actor where actor_id + 1 = 5;
select * from actor where to_days(current_date) - to_days(col_day) <= 10;
复制代码

3.3.2 前缀索引和索引的选择性

索引很长的字符串会让索引变得大且慢。一般能够只索引开始部分的字符,这样能够节约索引空间,从而提升索引的效率。缺点是会下降索引的选择性。索引的选择性是指不重复的索引值和记录总数的比,显然越大越好。因此,咱们须要选择足够长的前缀来保证选择性,同时又不能太长以下降索引空间。

咱们可使用语句

select count(distinct left(col_name, 3)) / count(*) from tbl_name;
复制代码

来统计使用3个字符的前缀选择性,同理能够计算出4个,5个等的状况。最后,选择一个合理的前缀长度便可。选择了长度以后能够像下面这样设置指定长度的索引:

alter table add key(col_nane(4));
复制代码

3.3.3 多列索引

常见的错误是,为每一个列建立独立的索引,或者按照错误的顺序建立多列索引。

若是一张表在col1和col2列上面存在索引,若是咱们使用col1 and col2做为where的条件,那么索引会作相角操做,若是使用col1 or col2,索引会作联合操做. 相交操做一般意味着须要一个包含全部相关列的多列索引,而不是独立的单列索引。联合操做则会消耗CPU和内存在算法的缓存、排序和合并上。若在explain中看到有合并索引,应先检查查询和表结构,看看是否是最优的。也能够经过optimizer_switch来关闭索引合并功能,或使用igonre index提示优化器忽略掉某些索引。

3.3.4 选择合适的索引顺序

若是要对多个列创建一个索引,除了上面的问题以外,还应该考虑所建的索引中列的顺序。好比,对col1, col2两列数据创建索引,那么咱们的顺序应被指定为(col1, col2)仍是(col2, col1)呢。咱们能够依然可使用上面的选择性来解决这个问题,咱们能够将选择性比较高的列做为索引的第一列,另外一列做为第二列。

3.3.5 聚簇索引

聚簇索引不是一种单独的索引类型,而是一种数据存储方式。“聚簇”表示数据行和相邻的键值紧凑地存储在一块儿。由于没法同时把数据行存放在两个不一样的地方,因此一个表只能有一个聚簇索引。

InnoDB经过主键汇集数据,若是没有主键就选择一个惟一的非空索引,若是没有这样的索引,就隐式定义一个主键做为聚簇索引。

聚餐的优势:

  1. 将相关数据保存在一块儿;
  2. 数据访问更快。由于数据和索引保存在一块儿。
  3. 使用覆盖扫描的查询能够直接使用页结点中的主键值。

缺点:

  1. 限制了提升IO密集型应用的性能,但若是数据所有放在内存中,则访问顺序就没那么重要了,聚簇的优点也没了;
  2. 插入速度严重依赖于插入顺序,按主键的顺序插入是最快的方式,否则就应该在加载完后用opeimize table从新组织一下表;
  3. 代价更高:限制innodB将被更新的行移动到新的位置;
  4. 当主键被更新或者新数据插入致使行移动的时候,可能面临“页分裂”问题。
  5. 可能致使全表扫描变慢,尤为树数据比较稀疏,且数据不连续时;
  6. 二级索引可能比想象更大,因其包含了引用行的主键列;
  7. 二级索引须要两次查找,而不是一次。

关于聚簇索引和非聚簇索引的存储方式的区别:

假设有数据以下:

若是是聚簇的方式来存储,那么它的一级索引是下面的样子:

也就是它们使用主键的值做为汇集数据,而后每一个叶子包含了每行的所有记录。聚簇索引的二级索引是下面的样子:

注意将二级索引中存储的值和最上面的表中的数据进行对比。从中能够看出,实际上它的二级索引是先用二级索引的值找到一级索引,而后使用一级索引来查找整个记录。

非聚簇的存储方式是下面的样子:

以上是非聚簇的一级索引的例子,非聚簇的二级索引的状况与之相同。即它们都是先用指定的值找到行号,而后使用行号来查找完整记录。

最好避免随机的聚簇索引,特别是对于IO密集型的应用。由于随机插入的时候,须要为新的行寻找合适的位置——一般是已有数据的中间位置——而且分配空间。这回增长不少额外的工做,并致使分布不够优化。最好使用自增的主键。

3.3.6 覆盖索引

若是一个索引包含了全部须要查询的字段的值,就称之为覆盖索引。覆盖索引就是从索引中直接获取查询结果,要使用覆盖索引须要注意select查询列中包含在索引列中;where条件包含索引列或者复合索引的前导列;查询结果的字段长度尽量少。

使用延迟关联解决索引没法覆盖问题:下面的解决方法对效率的提高不是绝对的!

SELECT * FROM products WHERE actor = 'SEAB CARREY' AND title like '%APPOLO%'
复制代码

上面的SQL中要查询所有的列,而咱们没有覆盖所有列的索引,所以没有覆盖索引。另外,like操做没法使用索引,由于like操做只有在匹配左前缀时才能使用索引。

咱们能够像下面这样解决问题:

SELECT * FROM products 
    JOIN (SELECT prod_id FROM products 
          WHERE actor = 'SEAB CARREY' AND title like '%APPOLO%')
AS t1 ON (t1.prod_id = products.prod_id)
复制代码

这里,须要先创建(actor, title, prod_id)索引。咱们先在子查询中找到匹配的prod_id,而后跟外层中数据进行匹配来获取全部列值。当符合where条件的数据数量远小于actor过滤出的数据数量的时候,它的效率尤为高。由于,根据子查询的where过滤出数据以后才与外层查询关联,然后者使用actor读取出数据以后,再用title进行关联。前者须要读取的数据量更少。

3.3.7 按索引扫描来排序

生成有序结果的两种方式:排序,按索引顺序扫描。当explain出的type为index时,说明使用索引扫描来进行排序。MySQL可使用一个索引既知足排序,又知足查找。只有当索引的列顺序和ORDER BY子句顺序一致,且列的排序方向都同样时,才能用索引对结果作排序。

下面是一些例子,假设索引是(col1, col2, col3),那么:

...where col1 = 1 order by col2, col3;(√)
...where col1 = 1 order by col2;(√)
...where col1 > 1 order by col1, col2;(√)

...where col1 > 1 order by col2, col3;(X)
...where col1 = 1 order by col2 desc, col3 asc;(X)
...where col1 = 1 order by col2, col4;(X)
...where col1 = 1 order by col3;(X)
...where col1 = 1 and col2 in(1,3) order by col3;(X)
复制代码

3.3.8 冗余和重复索引

重复索引是指在相同的列上按照相同的顺序建立的相同类型的索引。常见的错误有:

  1. 使用主键和惟一约束时与已有的因此冲突,由于主键和惟一约束是经过索引来实现的,若是再定义索引就会冗余;
  2. 若建立了索引(A,B)再建立索引(A)则冗余,而索引(B,A)和(B)不是,由于(B)不是最左前缀。

3.3.9 索引与锁

Inn哦DB只有在访问行的时候才会对其加锁,而索引可以减小访问行的次数,因此索引能减小锁的数量。

4 、慢查询优化

4.1 慢查询基础

能够经过下面两个步骤来分析慢查询:

  1. 确认应用程序是否在检索超过需求的数据,这一般意味着访问了太多的行或列;
  2. 确认MySQL服务器层是否在分析大量的超过需求的数据行。

4.1.1 请求了超过需求的数据

典型的请求查过须要的数据的场景:

  1. 查询了不须要的记录,若是只须要指定行的记录,可使用limit语句来只返回部分记录;

  2. 多表关联的时候返回了所有的列,好比下面的语句会返回tbl1和tbl2的所有记录:

    SELECT * FROM tbl1 INNER JOIN tbl2 ...;
    复制代码

    能够改为下面的样子(若是只须要tbl1的记录的话)

    SELECT tb1.* FROM tbl1 INNER JOIN tbl2 ...;
    复制代码
  3. 总数取出所有的列。缺点是没有办法使用覆盖索引完成优化,并且会为服务器带来额外的IO、内存和CPU消耗。

  4. 重复查询相同的记录。最好将这些数据缓存起来。

4.1.2 扫描了额外的记录

能够经过explain输出的列type中的值来获得访问类型。

4.2 重构查询的方式

  1. 分解成多个简单查询:大查询会锁住更多的记录,阻塞不少小的查询,可将大的查询分解成小的查询,来下降对服务器的应用,还能够经过设置时间间隔来说一次的压力分解到更长的时间中去;
  2. 分解关联查询

4.3 优化特定类型的查询

4.3.1 优化COUNT查询

  1. 统计全部行数时,最好使用COUNT(*),语义清晰,性能更好
  2. 能够经过相减的方式来下降扫描的行数

4.3.2 优化关联查询

  1. 确保ON或者USING子句中的列上面有索引
  2. 确保GROUP BY和ORDER BY的表达式中只涉及到一个表中的列,这样MySQL才可能使用索引优化
相关文章
相关标签/搜索