温斯顿吴 赌场式交易员

《高性能MySQL》读书笔记

2017-04-05

第1章 MySQL架构与历史

MySQL最重要、最与众不同的特性是它的存储引擎架构,这种架构将查询处理与数据的存储/提取相分离,使得可以在使用时根据不同的需求来选择数据存储的方式。

MySQL逻辑架构

           客户端
 ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ ↓ 
+--------------------------+
-        连接/线程处理       -   // 连接处理、授权认证、安全
+--------------------------+
     ↓                ↓
+---------+      +---------+
- 查询缓存 -   ←  -  解析器  -   // 查询解析、优化、缓存、内置函数、存储过程、触发器、视图
+---------+      +---------+
                      ↓
+--------------------------+
-          优化器           -
+--------------------------+

+--------------------------+
-         存储引擎          -   // 数据的存储和提取
+--------------------------+
  • 服务器通过API与存储引擎进行通信,这些API屏蔽了不同存储引擎之间的差异。存储引擎API包含几十个底层函数,不同存储引擎不会去解析SQL,而只是简单地响应上层服务器的请求。
  • 每个客户端连接都会在服务器进程中拥有一个线程,这个连接的查询只会在这个线程中执行,该线程只能轮流在某个CPU核心中运行。MySQL基于线程池来管理线程(创建、缓存、销毁)。
  • 当客户端连接到MySQL服务器时,服务器会基于用户名、主机信息、密码等进行认证。一旦客户端连接成功,服务器会继续验证该客户端是否具有执行某个特定查询的权限。
  • MySQL会解析查询并创建查询解析树,然后对其进行各种优化,如重写查询、决定表的读取顺序、选择合适的索引等。用户可以通过使用关键字提示优化器,从而影响它的决策过程。
  • 优化器不关心表使用的是什么存储引擎,但存储引擎对于优化查询是有影响的:优化器会请求存储引擎提供容量或某个具体操作的开销信息,以及表数据的统计信息等。
  • 对于SELECT语句,在解析查询之前,服务器会先检查查询缓存,如果找到对应的查询,就不必再执行查询解析、优化和执行的整个过程,而是直接返回查询缓存中的结果集。

并发控制

加锁本身也需要消耗资源,锁策略就是在锁的开销和安全性之间寻求平衡。每种MySQL存储引擎都可以实现自己的锁策略和锁粒度。

表锁:会锁定整张表,在对表进行写操作之前,需要先获得写锁,获得写锁后将会阻塞其他用户对该表的读写操作。只有没有写锁时,其他用户才能获取读锁,读锁之间是不相互阻塞的。写锁比读锁有更高的优先级,因此一个写锁请求可能会被插入到读锁队列的前面。

虽然不同的存储引擎都有自己的锁实现,MySQL自身仍然会在服务器层使用表锁并忽略存储引擎的锁机制,例如当执行ALTER TABLE时,服务器会使用表锁。

行级锁:行级锁只在存储引擎层实现,MySQL服务器层没有实现。

事务

ACID:atomicity(原子性)、consistency(一致性)、isolation(隔离性)、durability(持久性)。

隔离级别:SQL标准定义了4种隔离级别,每一种级别都规定了在一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。

  1. READ UNCOMMITED 事务中的修改,即使没有提交,对其他事务也是可见的。(因此会产生脏读)
  2. READ COMMITTED 一个事务只能看见已经提交的事务所做的修改。
  3. REPEATABLE READ 这是MySQL默认的事务隔离级别,保证在同一个事务中多次读取同样记录的结果是一样的。理论上该级别无法避免幻读的问题,InnoDB通过多版本并发控制解决了幻读的问题。
  4. SERIALIZABLE 强制事务串行执行,会在读取的每一行数据上都加锁,可能导致大量的超时和锁争用问题。

可以在配置文件中设置整个数据库的隔离级别,也可以只改变当前会话的隔离级别: SET SESSION TRANSACTION LEVEL READ COMMITTED;

死锁:当多个事务试图以不同的顺序锁定资源时,就可能产生死锁。(两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环)对于事务型的系统,死锁发生后,只有部分或者完全回滚其中一个事务,才能打破死锁。InnoDB目前处理死锁的方式是:在检测到死锁循环依赖后,将持有最少行级排它锁的事务进行回滚。

事务日志:事务日志可以帮助提高事务的效率,存储引擎在修改表的数据时只需要修改表数据的内存拷贝,同时把该修改行为持久化到硬盘中的事务日志中,相比于将修改的数据本身持久化到磁盘,事务日志采用的是追加的方式,因此是在磁盘上的一小块区域内顺序地写入,而不是随机的I/O操作。事务日志持久化后,内存中被修改的数据在后台可以慢慢刷回磁盘,如果在数据没有写回磁盘时系统崩溃了,存储引擎在重启时能够自动恢复这部分数据。目前大多数存储引擎都是这样实现的。

MySQL默认采用自动提交模式,如果不是显式地开始一个事务,则每个查询都会被当做一个事务执行提交操作。可以在当前连接中设置AUTOCOMMIT变量来禁用自动提交模式(禁用后,需要显式地执行COMMIT提交或者ROLLBACK回滚)。对于非事务型的表,修改AUTOCOMMIT不会有任何影响,这类表相当于一直处于AUTOCOMMIT启用的状态。 此外,有一些命令,例如ALTER TABLE,在执行之前会强制执行COMMIT提交当前的活动事务。

如果在事务中混合使用了事务型和非事务型的表,当事务需要回滚时,非事务型表上的变更将无法撤销,这将导致数据库处于不一致的状态。在非事务型表上执行事务相关操作时,MySQL通常并不会报错,只会给出一些警告。

显式锁定:MySQL也支持LOCK TABLES和UNLOCK TABLES语句,这是在服务器层实现的,但它们不能代替事务处理,如果应用到事务,还是应该选择事务型存储引擎。 (建议:除了在事务中禁用了AUTOCOMMIT时可以使用LOCK TABLE之外,其他任何时候都不要显式地执行LOCK TABLES,不管使用的是什么存储引擎,因为LOCK TABLE和事务之间相互影响时,问题会变得非常复杂)

InnoDB也支持通过特定的语句进行显式锁定,这些语句不属于SQL规范,如: SELECT … LOCK IN SHARE MODE SELECT … FOR UPDATE

多版本并发控制

可以认为MVCC是行级锁的一个变种,但是它在很多情况下都避免了加锁操作,因此开销更低(更高并发)。其实现原理是通过保存数据在某个时间点的快照来实现的,根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。 InnoDB的MVCC是通过在每行记录后面保存两个隐藏的列来实现的,这两个列一个保存行的创建版本号,一个保存行的过期版本号,每开始一个新的事务,系统版本号就会自动递增。事务开始时刻的版本号会作为事务的版本号用来和查询到的每行记录的版本号进行比较。 MVCC只在REPEATABLE和READ COMMITED两个隔离级别下工作,其他两个隔离级别和MVCC不兼容。 (在不同隔离级别下,每种查询的具体行为,略)

MySQL的存储引擎

MySQL将每个数据库保存为数据目录下的一个子目录,创建表时,MySQL会在数据库子目录下创建一个和表同名的.frm文件来保存表的定义。 可以使用SHOW TABLE STATUS命令或者查询INFOMATION_SCHMA中对应的表来显示某个表的相关信息。 InnoDB在95%的情况下都是最好的选择。 (InnoDB、MyISAM、其他内建及第三方引擎概述,略)

MySQL时间线

(即版本变更历史,略)

MySQL的开发模式

(开发过程和发布模型,略)

第2章 MySQL基准测试

基准测试是针对系统设计的一种压力测试,通常的目标是为了掌握系统的行为

基准测试的策略

两种主要策略:针对系统的整体测试,单独测试MySQL。

测试指标

  1. 吞吐量:单位时间内的事务处理数;
  2. 响应时间(延迟):完成测试任务所需的时间;(95%法则)
  3. 并发性:测试应用在不同并发下的性能;
  4. 可扩展性:线性扩展;

基准测试方法

(常见的测试设计错误,略)

基准测试工具

ab、http_load、JMeter mysqlslap、MySQL Benchmark Suite、Super Smack… (工具介绍,略)

基准测试案例

(略)

第3章 服务器性能剖析

性能:完成某件任务所需要的时间度量,性能即响应时间。 性能剖析(profiling):测量服务器的时间花费在哪里。 性能剖析的步骤:测量任务所花费的时间,然后对结果进行统计和排序,将重要的任务排到前面。

对应用程序进行性能剖析

使应用的可测量,性能剖析的代码会导致服务器变慢,可以采用随机采样

<?php
$profiling_enabled = rand(0,100) > 99;
...

(PHP程序的性能测试,推荐facebook的xhprof,详略)

剖析MySQL查询

在MySQL中可以通过设置long_query_time为0来捕获所有的查询,目前查询的相应时间单位已经精确到微秒级。

在MySQL中,慢查询日志是开销最低、精度最高的测量查询时间的工具。对CPU的开销很少,但是可能消耗大量的磁盘空间。

也可以通过tcpdump抓取TCP包,然后使用pt-query-digest –type=tcpdump选项来解析并分析查询。

(使用pt-query-digest解析慢查询日志,略)

剖析单条查询:

SET profiling = 1;
SELECT * ...
SHOW PROFILES;               # 所有查询的统计
SHOW PROFILE FOR QUERY 1;   # 单条查询的详情,给出查询执行的每个步骤及花费的时间

也可以不使用SHOW PROFILE命令,而是直接查询INFOMATION_SCHEMA中对应的表,这样可以自定义输出数据的格式(按特定字段排序等)。

可以使用SHOW STATUS命令返回查询计数器(但无法给出消耗了多少时间)。最有用的计数器是句柄计数器、临时文件、表计数器等。

FLUSH STATUS;
SELECT * FROM ...
SHOW STATUS WHERE Variable_name LIKE 'Handler%' OR Variable_name LIKE 'Created';

SHOW STATUS本身也会创建一个临时表,而且也会通过句柄操作访问此临时表,也会影响到SHOW STATUS结果中对应的数字。

Performance Schema:5.5中新增,目前还在快速开发中

诊断间歇性问题

(案例,略)

其他剖析工具

(略)

第4章 Schema与数据类型优化

选择优化的数据类型

选择数据类型的几个简单原则:

  1. 更小的通常更好;
  2. 简单就好:整型操作比字符型操作代价低;使用整型存储IP地址;
  3. 尽量避免NULL:可为NULL的列需要更多的存储空间,在索引优化时也更复杂(需要记录额外的字节);(InnoDB使用单独的bit存储NULL值,这对于稀疏数据有很好的空间效率)

TIMESTAMP只使用DATATIME一半的存储空间,且会根据时区自动更新,但是它允许的范围要小很多(1970年开始)。

MySQL中的INTEGER、BOOL等只是基本类型的别名,如果用这些名字建表,SHOW CREATE TABLE仍然显示基本类型。

MySQL可以为整数类型指定宽度,例如INT(11),但这实际并不会限制值的合法范围,作用只是规定了MySQL的一些交互工具用来显示字符的个数,对于存储和计算来说,INT(1)和INT(20)是相同的(都是11位)。其他整型数同理。

TINYINT的存储空间为8位,存储值范围为-128~127,TINYINT UNSIGNED的存储范围是0~255;

VARCHAR需要使用1或2个额外字节记录字符串的长度:如果列的最大长度小于等于255,则只使用1个字节,否则使用2个字节,所以一个VARCHAR(10)的列需要11个字节的存储空间,VARCHAR(1000)的列则需要1002个字节。

比较适合使用VARCHAR类型的情况:

  1. 字符串的最大长度比平均长度大很多;
  2. 列的更新很少;(更新导致长度变化,会有额外的更新操作)
  3. 使用了像UTF-8这样的复杂字符集,每个字符使用不同的字节数进行存储;

InnoDB可以把过长的VARCHAR存储为BLOB;

MySQL把BLOB和TEXT值当做独立的对象处理,当其太大时,InnoDB会使用专门的“外部”存储区域来进行存储,此时每个值在行内需要1~4个字节存储一个指针,然后在外部存储区域存储实际的值。

如果使用完全随机的字符串作为标识列,例如使用MD5值,会导致INSERT以及UPDATE语句变得很慢,原因可能有:

  1. 插入值会随机地写到索引的不同位置,使得INSERT语句更慢,会导致页分裂,磁盘随机访问,产生聚簇索引碎片;
  2. SELECT会更慢,因为逻辑上相邻的行会分布在磁盘和内存的不同地方;
  3. 随机值导致缓存对所有类型的查询语句效果都很差;(局部性原理失效)

(其他类型,略)

MySQL schema设计中的陷阱

  1. 太多的列:存储引擎API需要在服务器层和存储引擎层之间通过行缓冲格式拷贝数据,然后在服务器层将缓冲内容解码成各个列,这个操作的代价非常高,而转换的代价依赖于列的数量。
  2. 太多的关联:对于MySQL来说,EAV(实体-属性-值)设计模式是一个糟糕的设计模式,MySQL限制了每个关联操作最多只能有61张表。经验表明,单个查询最好在12个表以内做关联。

范式和反范式

(范式、反范式各自的优缺点,略) 完全的范式化和完全的反范式化schema都是实验室里才有的东西,在实际应用中经常需要混用。

缓存表和汇总表

缓存表:存储那些可以比较简单地从schema其他表获取数据的表(存在逻辑上冗余的数据); 汇总表:保存使用GROUP BY语句聚合数据的表;

MySQL原生不支持物化视图,但是可以通过插件实现。

使用随机方式实现计数器表:如下的网站点击统计计数器表存在并发问题:

CREATE TABLE hit_counter(
  cnt int unsigned not null
) ENGINE=InnoDB;

当网站发生点击时:

UPDATE hit_counter SET cnt = cnt + 1;

要获得更高的并发更新性能,可以将数据器保存在多行中,每次随机选择一行进行更新,修改计数器表如下:

CREATE TABLE hit_counter(
  slot tinyint unsigned not null primary key,
  cnt int unsigned not null
)ENGINE=InnoDB;

当发生更新操作时,选择一个随机的行进行更新:

UPDATE hit_counter SET cnt = cnt + 1 WHERE slot = RAND() * 100;

如下获取统计结果:

SELECT SUM(cnt) FROM hit_counter;

加快ALTER TABLE操作的速度

常规的方法是建另一张结构符合要求的表,并插入旧表的数据,然后切换主从,或者切换新旧表。 修改表的.frm文件是很快的,因此可以为想要创建的表结构创建一个新的.frm文件,然后用它替换掉已经存在的表的.frm文件。(详略)

在有索引的情况下快速导入表数据

常规的方法:

  1. 先删除索引:如ALTER TABLE t.data DISABLE KEYS; # 对唯一索引无效
  2. 导入数据
  3. 再创建索引:如ALTER TABLE t.data ENABLE KEYS;

hack方法是直接修改.MYD、.frm、.MYI等文件,详略。

第5章 创建高性能索引

索引基础

在MySQL中,索引是在存储引擎层而不是服务器层实现的,并没有统一的索引标准,不同的存储引擎索引工作的方式是不一样的。

索引类型

  1. B-Tree索引 意味着所有的索引都是按顺序存储的,并且每一个叶子页到根的距离相同。 B-Tree索引可应用于如下类型的查询:
    • (1)全值匹配:和索引中的所有列进行匹配;
    • (2)匹配最左前缀:如当索引有多列时,只使用索引的第一列;
    • (3)匹配列前缀:只使用索引第一列的值的开头部分;
    • (4)匹配范围值:只使用索引的第一列;
    • (5)精确匹配某一列,并范围匹配另一列:只使用索引的第一列及第二列的开头部分;
    • (6)只访问索引的查询:查询只需要访问索引,而无须访问数据行,即覆盖索引
    • (7)可用于ORDER BY查询;

B-Tree索引的限制:

  • (1)如果不是按照索引的最左列开始查找,则无法使用索引;
  • (2)不能跳过索引中的列;
  • (3)如果查询中有某个列的范围查询(包括LIKE等),则其右边所有列都无法使用索引优化查找;

2.哈希索引 基于哈希表实现,对于每一行数据存储引擎都会对所有的索引列计算一个哈希码,只有精确匹配索引所有列的查询才有效。 只有Memory引擎显式支持哈希索引。(详略)

InnoDB有一个特殊的功能:自适应哈希索引,当InnoDB注意到某些索引值被使用得非常频繁时,就会在内存中基于B-Tree索引之上再创建一个哈希索引,这样就让B-Tree索引也具有哈希索引的一些优点,比如快速的哈希查找。

3.空间数据索引(R-Tree) MyISAM表支持空间索引,可以用作地理数据存储。

4.全文索引 查找的是文本中的关键词,而不是直接比较索引中的值。适用于MATCH AGAINST操作,而不是普通的WHERE条件操作。

索引的优点

  1. 减少服务器需要扫描的数据量;
  2. 避免排序和临时表;
  3. 将随机I/O变为顺序I/O;

高性能的索引策略

  1. 如果查询中的列不是独立的,而是表达式的一部分,MySQL将不会使用索引;
     # 如下情况下不会使用索引
     SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
    
  2. 索引的选择性越高则查询的效率越高;(索引选择性 = 列取值基数/表记录总数)
     # 计算列的选择性
     SELECT COUNT(DISTINCT city)/COUNT(*) FROM sakila.city_demo;
    
  3. 可以使用前缀索引来替代对很长的字符列做索引; ``` # 计算不同前缀长度的索引的选择性 SELECT COUNT(DISTINCT LEFT(city,3))/COUNT() AS sel3, COUNT(DISTINCT LEFT(city,4))/COUNT() AS sel4, … COUNT(DISTINCT LEFT(city,7))/COUNT(*) AS sel7, FROM sakila.city_demo;

# 添加前缀索引 ALTER TABLE sakila.city_demo ADD KEY(city(7));

MySQL不能使用前缀索引做ORDER BY和GROUP BY查询,也无法使用前缀索引做覆盖扫描。


4. 在多个列上建立独立的单列索引大部分情况下并不能提高MySQL的查询性能:
(1)当出现服务器对多个索引做相交操作时(通常有多个AND条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的多列索引;
(2)当服务器需要对多个索引做联合操作时(通常有多个OR条件),通常需要消耗大量的CPU和内存资源用在缓存、排序和合并操作上。而且优化器不会把这些计算到“查询成本”中,优化器只关心随机页面读取,这会导致查询的成本被低估,`甚至该执行计划还不如直接走全表扫描`。这样的查询通常通过改写成UNION查询来优化:

# 假设actor_id和film_id这两列上都有独立的索引 SELECT film_id,actor_id FROM sakila.film_actor WHERE actor_id = 1 OR film_id = 1;

改写成:

SELECT film_id,actor_id FROM sakila.film_actor WHERE actor_id = 1 UNION ALL SELECT film_id,actor_id FROM sakila.film_actor WHERE actor_id <> 1 AND film_id = 1;


5. 多列索引的列顺序非常重要,在一个多列的B-Tree索引中,索引总是先按照最左列进行排序,其次是第二列,等等。
经验:一般的选择是将选择性最高的列放到索引最前列,但是避免随机IO和排序通常更为重要,所以要优先结合值的分布情况及频繁执行的查询场景来考虑。


## 聚簇索引
聚簇索引并不是一种单独的索引类型,而是`一种数据存储方式`。InnoDB的聚簇索引实际上是在同一个结构中保存了B-Tree索引(主键)和数据行。
`聚簇`表示数据行和键值紧凑地存储在一起,一个表只能有一个聚簇索引。

如果没有定义主键,InnoDB会选择一个唯一的非空索引代替,如果没有这样的索引,InnoDB会隐式定义一个主键来作为聚簇索引。

聚簇索引的优点:
* (1)可以将相关数据保存在一起;
例如根据用户ID来聚集数据,这样只要从磁盘读取少数的数据页就能获取某个用户的全部记录,如果没有使用聚簇索引,则每条记录都可能导致一次磁盘I/O。
* (2)数据访问更快:
因为索引和数据保存在同一个B-Tree中,因此从聚簇索引中获取数据通常比在非聚簇索引中查找更快。
* (3)使用覆盖索引扫描的查询可以直接使用页节点中的主键值。

聚簇索引的缺点:
* (1)如果数据全部都放在内存中,则访问顺序就不那么重要了,聚簇索引也就没什么优势了;
* (2)插入速度严重依赖于插入顺序;如果不是按照主键顺序插入数据,在插入完成后最好执行OPTIMIZE TABLE重新组织表;
* (3)更新聚簇索引列的代价很高,因为会强制InnoDB将每个被更新的行移动到新的位置;
* (4)当行的主键要求必须将一行插入到某个已满的页中时,存储引擎会将该页分裂成两个页面来容纳该行,即造成“页分裂”,导致表占用更多的磁盘空间。
* (5)聚簇索引可能导致全表扫描变慢,尤其是行比较稀疏,或者由于页分裂导致数据存储不连续时;
* (6)非聚簇索引会更大,因为在非聚簇索引的叶子节点中包含了引用行的主键列;
* (7)非聚簇索引访问需要两次索引查找,而不是一次;(非聚簇索引的叶子节点保存的不是指向行的物理位置的指针,而是行的主键值),对于InnoDB,自适应哈希索引能够减少这样的重复工作。


## InnoDB和MyISAM的数据分布对比
InnoDB中聚簇索引的每一个叶子节点都包含了主键值、事务ID、用于事务和MVCC的回滚指针以及所有的剩余列。如果主键是一个列前缀索引,InnoDB也会包含完整的主键和剩下的其他列。
(详略)

使用InnoDB时应该尽可能地按主键顺序插入数据,并且尽可能地使用单调增加的聚簇键的值来插入新行。


## InnoDB顺序主键的高并发问题
在高并发情况下,在InnoDB中按主键顺序插入可能会导致明显的争用,当前主键的上界会成为“热点”,导致锁竞争。
此外AUTO_INCREMENT锁机制(表锁)也会导致竞争。可以通过修改innodb_autoinc_lock_mode来优化:
innodb_autoinc_lock_mode = 0 ("traditional" lock mode:全部使用表锁)
innodb_autoinc_lock_mode = 1 (默认)("consecutive" lock mode:可`预判行数`时使用新方式,不可时使用表锁) 
innodb_autoinc_lock_mode = 2 ("interleaved" lock mode:全部使用新方式,不安全,不适合replication)


## 覆盖索引
覆盖索引:索引包含,或者说覆盖所有需要查询的字段的值。
MySQL只能使用B-Tree索引做覆盖索引。

使用了覆盖索引的查询,在EXPLAIN的Extra列可以看到`Using index`的信息。

## 使用索引排序
只有当索引的列顺序和ORDER BY子句的顺序完全一致,并且所有列的排序方向都一样时,MySQL才能使用索引对结果做排序。
如果查询关联了多张表,则只有当ORDER BY子句引用的字段全部为第一个表时,才能使用索引做排序。

有一种情况下ORDER BY子句可以在不满足索引的最左前缀要求的情况下使用索引进行排序:前导列在where中被设置为常量。

假设有如下索引:

PRIMARY KEY(rental_id), UNIQUE KEY rental_date (rental_date,inventory_id,customer_id), KEY idx_fk_inventory_id(inventory_id), KEY idx_fk_customer_id(customer_id), KEY idx_fk_staff_id(staff_id),


以下查询可以使用索引做排序:

# 因为索引前导列在WHERE中被设置为常量 …WHERE rental_date = ‘2005-05-25’ ORDER BY inventory_id DESC;

# ORDER BY使用的两列就是索引的最左前缀 …WHERE rental_date > ‘2005-05-25’ ORDER BY rental_date,inventory_id;


以下查询不能使用索引做排序:

# 排序方向不同 …WHERE rental_date = ‘2005-05-25’ ORDER BY inventory_id DESC,customer_id ASC;

# 使用了不在索引中的列 …WHERE rental_date = ‘2005-05-25’ ORDER BY inventory_id,staff_id;

# WHERE和ORDER BY中的列无法组合成索引的最左前缀 …WHERE rental_date = ‘2005-05-25’ ORDER BY customer_id;

# 在索引列的第一列上使用的是范围条件,所以无法使用索引的其余列 …WHERE rental_date > ‘2005-05-25’ ORDER BY inventory_id,customer_id;

# 在前置索引列上使用了范围查找 …WHERE rental_date = ‘2005-05-25’ AND inventory_id IN(1,2) ORDER BY customer_id;


## 压缩索引
MyISAM支持索引压缩,略。


## 冗余和重复索引
重复索引是指在相同的列上按照相同的顺序创建相同类型的索引。应该避免这样创建重复索引。

## 未使用的索引
使用pt-index-usage读取查询日志,统计各种索引的使用情况。

## 索引和锁
InnoDB只有在访问行的时候才会对其加锁,而索引能够减少InnoDB访问的行数,从而减少锁的数量。

Extra中的"Using where"表示在存储引擎层未能实现过滤,InnoDB检索到数据并返回服务器层以后,MySQL服务器才能应用WHERE子句,这种情况下会对这些不满足WHERE条件的数据行也加锁(因为这种情况下存储引擎的操作是`从索引的开头开始`获取满足条件的记录)。

即使使用了索引,InnoDB也可能锁住一些不需要的数据,如果不能使用索引查找和锁定行,MySQL会做全表扫描并锁住所有的行,而不管是不是需要。

执行:

SET AUTOCOMMIT = 0; BEGIN; SELECT actor_id FROM sakila.actor WHERE actor_id < 5 AND actor_id <> 1 FOR UPDATE;

如上查询语句在存储引擎层的操作是“从索引的开头开始获取满足条件actor_id < 5的记录”,此时再执行如下语句,将被挂起:

SET AUTOCOMMIT = 0; BEGIN; SELECT actor_id FROM sakila.actor WHERE actor_id = 1 FOR UPDATE;


InnoDB在二级索引上使用共享锁(读锁),但在访问主键时使用排它锁(写锁)。


## 案例学习
(略)


## 维护索引和表
当碰到古怪的问题,比如不应该发生的主键冲突等等,可以通过CHECK TABLE来检查是否发生了表损坏。该命令通常能够找出大多数表和索引的错误。
可以执行REPAIR TABLE来修复损坏的表。
也可以通过一个不做任何数据操作的ALTER操作来重建表,以达到修复的目的:

ALTER TABLE innodb_tbl ENGINE=INNODB;

(详略)





# 第6章 查询性能优化

## 常见的导致查询过慢的原因
1. 查询不需要的记录;如误以为MySQL会只返回需要的数据,实际上却是先返回全部结果集,再进行计算。
2. 多表关联时返回全部列,如:

SELECT * FROM sakila.actor INNER JOIN sakila.film_actor USING(actor_id) INNER JOIN sakila.film USING(film_id) WHERE sakila.film.title = ‘***’;

该查询将会返回三个关联表的全部数据列,正确的方式应该是只取需要的列:

SELECT sakila.actor.* FROM sakila.actor…;

3. 总是取出全部列:SELECT * FROM ...
4. 重复查询相同的数据,而不是使用缓存;


## MySQL使用WHERE条件的不同方式
从好到坏依次为:
1. 在索引中使用WHERE条件来过滤不匹配的记录,这是在存储引擎层完成的;
2. 使用索引覆盖扫描(Using index)返回记录,直接从索引中过滤不需要的记录并返回命中的结果,在MySQL服务器层完成,无需再回表查询记录;
3. 从数据表中返回数据,然后过滤不满足条件的记录(Using Where),MySQL需要先从数据表读出记录然后过滤;

## 切分查询
将大查询切分成小查询,以避免一个大的语句一次锁住很多数据、占满整个事务日志,如:

DELETE FROM messages WHERE created < DATA_SUB(NOW(),INTERVAL 3 MONTH);

可以如下优化:

rows_affected = 0 do{ rows_affected = do_query(‘DELETE FROM messages WHERE created < DATA_SUB(NOW(),INTERVAL 3 MONTH) LIMIT 10000’) // 一次删除10000行 }while rows_affected > 0

每次删除数据后都暂停一下再做下一次删除,这样可以将服务器上原本一次性的压力分散到一个很长的时间段中,从而避免长时间持有锁。


## 分解关联查询
当查询关联多个表时,可以对每一个表进行一次单表查询,然后在应用程序中对数据进行关联操作,如:

SELECT * FROM tag JOIN tag_post ON tag_post.tag_id = tag.id JOIN post ON tag_post.post_id = post.id WHERE tag.tag=’mysql’

可以分解为:

SELECT * FROM tag WHERE tag=’mysql’; SELECT * FROM tag_post WHERE tag_id=1234; SELECT * FROM post WHERE post.id in (123,456,789);

好处:
1. 方便缓存;
2. 可以减少对锁的竞争;
3. 更容易对数据库进行拆分,更容易做到高性能和可扩展;
4. 能够使用IN替代关联查询,从而让MySQL按照ID顺序进行查询,这会比随机的关联更高效;
等等


## 查询执行的基础
MySQL客户端和服务器端的通信协议是半双工的,所以无法将一个消息分成小块来独立发送。一旦一端开始发送消息,另一端要接收完整消息才能响应它。
客户端使用一个单独的数据包将查询传给服务器,数据包的大小限制取决于max_allowed_packet配置。
服务器响应的数据一般由多个数据包组成,当服务器开始响应客户端请求时,客户端必须完整地接收整个返回结果,而不能简单地只取前面几条需要的结果。
大部分连接MySQL的库函数都可以获得全部结果集并缓存到内存里,也可以逐行获取需要的数据,MySQL通常要等所有数据都已经发送给客户端后才能释放连接。
如下PHP代码:

$link = mysql_connect(‘localhost’,’user’,’password’); $result = mysql_query(‘SELECT * FROM HUGE_TABLE’,$link); while($row = mysql_fetch_array($result)){ // … }

看起来好像是只有当需要获取数据时才通过循环从服务器取出数据,实际上在调用mysql_query时PHP就已经将整个查询结果缓存到内存中,while循环只是从缓存中逐行取出数据。用mysql_unbuffered_query代替mysql_query则不会缓存结果。

## MySQL查询的状态
每一个MySQL连接在任意时刻都有一个状态,用来表示MySQL当前正在做什么,可以通过SHOW FULL PROCESSLIST命令来查看状态。
具体的状态Sleep、Locked、Query等等,略。


## 查询优化处理
查询优化处理的作用是将SQL转换成一个执行计划,MySQL会按照这个执行计划和存储引擎进行交互,这个过程包含多个子阶段:
1. 语法解析器和预处理:通过关键字解析SQL语句,生成一个解析树,并进行语法验证。预处理器则会检查数据表和列是否存在,并验证权限。
2. 查询优化器:一条查询可以有多种执行方式,优化器的作用是找到这其中最好的执行计划。优化基于`查询成本`来进行成本估算,可以如下查看查询成本:

SELECT SQL_NO_CACHE COUNT(*) FROM…; SHOW STATUS LIKE ‘Last_query_cost’; // 返回值表示优化器认为查询需要随机查找的页数

优化器的成本估算并不等于实际执行查询的成本,所以最终选择的不一定是最优执行计划。(比如优化器并不会考虑其他并发的查询)
MySQL能够执行的常见的优化包括:
* (1)重新定义关联表的顺序;
* (2)将外连接转化为内连接;
* (3)使用等价变换规则,例如5=5 AND a>5会被改写为a>5
* (4)优化COUNT()、MIN()、MAX():如要找某一列的最小值,只要查询对应B-Tree索引最左端的记录
* (5)预估并转化为常数表达式:当检测到一个表达式可以转化为常数时,就会一直把该表达式作为常数进行优化处理;
* (6)覆盖索引扫描;
* (7)子查询优化:转换子查询的形式,减少多个查询多次对数据进行访问;
* (8)提前终止查询;如下查询在优化阶段就已经终止:

SELECT film.film_id FROM sakila.film WHERE film_id = -1;

* (9)等值传播:如果两个列通过等式关联(USING),MySQL会把其中一个列的WHERE条件传递到另一个列上;
* (10)对IN的优化;在MySQL中,IN列表中的数据会先进行排序,然后通过二分查找法确定列表中的值是否满足条件;而不是像其他DB那样转换为OR查询。
...
详略。

可以通过执行EXPLAIN EXTENDED之后再执行SHOW WARNINGS来查看重构出的查询。

## MySQL执行关联查询的方式
MySQL认为任何一个查询都是一次关联,包括单表查询,甚至子查询(读取临时表也是一次关联)。
当前MySQL执行关联查询的策略很简单:对任何关联查询都执行`嵌套循环`关联操作。

多表关联查询时,优化器会通过评估不同关联顺序的成本来选择一个代价最小的关联顺序。关联顺序的可能值是关联表数量的阶乘,优化器实际使用贪婪搜索方式查找最优关联顺序。

## 排序优化
当不能使用索引生成排序结果时,MySQL需要自己进行排序,如果数据量小则在内存中进行,如果数据量大则需要使用磁盘,这两种情况在MySQL中都称为文件排序(filesort)。
具体排序算法略(分部分,快排)。

## 查询执行
最终交给引擎的执行计划是一个数据结构,而不是像其他DB那样的字节码。


## MySQL查询优化器的局限性
使用WHERE ... IN(SELECT ...)查询,性能会非常糟

SELECT * FROM sakila.film WHERE film_id IN ( SELECT film_id FROM sakila.film_actor WHERE actor_id = 1);

上面的查询`并不会`优化成如下的方式(以便利用IN()的高效列表查询):

SELECT GROUP_CONCAT(film_id) FROM sakila.film_actor WHERE actor_id = 1; – Result:1,2,3,4,5… SELECT * FROM sakila.film WHERE film_id IN ( 1,2,3,4,5… );


实际上MySQL会将外层表压到子查询中:

SELECT * FROM sakila.film WHERE EXISTS(SELECT * FROM sakila.film_actor WHERE actor_id = 1 AND film_actor.film_id = film.film_id);

MySQL会先对film表进行全表扫描(因为此时在子查询中用到了外层表的film_id字段),然后根据返回的film_id逐个执行子查询。如果外层的表是一个非常大的表,这个查询的性能将会非常糟糕。

可以如下改写:

SELECT film.* FROM sakila.film INNER JOIN sakila.film_actor USING(film_id) WHERE actor_id = 1;


要不要使用子查询,没有绝对的答案,应该以测试为准,详略。

UNION查询的优化限制,略

MySQL无法利用多核特性来并行执行查询。

MySQL不支持`松散索引扫描`,即无法按照不连续的方式扫描一个索引,即使需要的数据是索引某个片段中的少数几个,MySQL仍然要扫描这个片段中的每一个条目。
5.6以后的版本,关于松散索引扫描的一些限制会通过`索引条件下推`的方式解决。


## 查询优化器的提示
可以使用优化器提供的几个提示来控制最终的执行计划(假如对优化器选择的执行计划不满意的话)。
详略。


## 优化COUNT()查询
如果在COUNT()的括号中指定了列或者列的表达式,则统计的就是这个表达式`有值的结果数`(不含NULL)。
COUNT(*)并不会扩展成所有列,而是直接统计所有的行数。

MyISAM的COUNT()函数非常快(通过存储引擎直接获得值)有一个前提条件,即不能有任何WHERE条件。

## 在同一个查询中统计同一个列的不同值的数量

SELECT SUM(IF(color = ‘blue’, 1, 0)) AS blue, SUM(IF(color = ‘red’, 1, 0)) AS red FROM items;

或者

SELECT COUNT(color = ‘blue’ OR NULL) AS blue, COUNT(color = ‘red’ OR NULL) AS red FROM items;


## 优化关联查询
1. 确保ON或者USING子句中的列上有索引;
2. 一般只需要在关联顺序中的第二个表的相应列上创建索引;
3. 确保GROUP BY和ORDER BY中的表达式只涉及到一个表中的列,这样MySQL才有可能使用索引来优化这个过程;

## 优化子查询
尽可能使用关联查询代替子查询。

## 优化GROUP BY和DISTINCT
MySQL使用同样的方式优化这两种查询,在内部处理的时候相互转化这两类查询。它们都可以使用索引来优化。
当无法使用索引时,GROUP BY使用两种策略来完成:临时表或者文件排序。
通常使用查找表的标识列分组的效率会比其他列更高。
尽可能将WITH ROLLUP功能转移到应用程序中处理。

## 优化LIMIT分页
对于偏移量非常大的查询应该尽可能地使用索引覆盖扫描进行优化:

SELECT film_id,descriptionn FROM sakila.film ORDER BY title LIMIT 50000,5;

上面的语句将查询50000条记录,然后只返回最后5条。最好改写成下面形式:

SELECT film.film_id, film.descriptionn FROM sakila.film INNER JOIN( SELECT film_id FROM sakila.film ORDER BY title LIMIT 50000,5 ) AS lim USING(film_id);


也可以在应用程序中记录上次查询位置,或者计算值的上下边界(值需要连续)来优化。

## 优化SQL_CALC_FOUND_ROWS
使用LIMIT语句时,如果加上SQL_CALC_FOUND_ROWS提示,就可以获得去掉LIMIT以后满足条件的总的行数。但是,加上这个提示后MySQL将扫描所有满足条件的行,然后再抛弃掉不需要的行,而不是在满足LIMIT的行数后就终止扫描。

## 优化UNION查询
MySQL总是通过创建并填充临时表的方式来执行UNION查询。一般会将WHERE、LIMIT、ORDER BY等子句下放到UNION的各个子查询中以便优化器可以充分利用这些条件进行优化。

## 使用自定义变量

SET @last_week := CURRENT_DATE - INTERVAL 1 WEEK; SELECT … WHERE col <= @last_week;

使用自定义变量的查询无法使用查询缓存;
不能在使用常量或者标识符的地方使用自定义变量,例如表名、列名、LIMIT子句;
自定义变量的生命周期是一次连接(使用连接池或者持久化连接就会有坑);
自定义变量是动态类型的;
(高级用法,略)

## 案例学习
(略)




# 第7章 MySQL高级特性

## 分区表
分区表是一个独立的逻辑表,但是底层由多个物理子表组成(意味着可以分布在不同的物理设备上)。对分区表的请求都会通过句柄对象转化成对存储引擎的接口调用,对应用来说是透明的。
MySQL中索引也是按照分区的子表定义的,而没有全局索引。

一个表最多只能有1024个分区。
如果分区表字段中有主键或者唯一索引的列,那么所有的主键列和唯一索引列都必须包含进来。
分区表中无法使用外键约束。

分区表上操作的逻辑:
SELECT:分区层先打开并锁住所有的底层表,优化器先判断是否可以过滤部分分区,然后再调用对应的存储引擎接口访问各个分区的数据;
INSERT:分区层先打开并锁住所有的底层表,然后确定哪个分区接收这条记录,再将记录写入对应底层表;
DELETE:分区层先打开并锁住所有的底层表,然后确定数据对应的分区,最后对相应底层表进行删除操作;
UPDATE:分区层先打开并锁住所有的底层表,先确定需要更新的记录在哪个分区,然后取出数据并更新,再判断更新后的数据应该放在哪个分区,最后对底层表进行写入操作,并对原数据所在的底层表进行删除操作。
虽然每个操作都会先打开并锁住所有的底层表,但这并不意味着分区表在处理过程中是锁住全表的,如果存储引擎能够实现行级锁,则会在分区层释放对应表锁。

分区表达式要返回一个整数:
```sql
CREATE TABLE sales(
  order_date DATETIME NOT NULL,
  ...
)ENGINE=InnoDB PARTITION BY RANGE(YEAR(order_date))(
  PARTITION p_2010 VALUES LESS THAN(2010),
  PARTITION p_2011 VALUES LESS THAN(2011),
  PARTITION p_2012 VALUES LESS THAN(2012),
  PARTITION p_catchall VALUES LESS MAXVALUE
);

在数据量超大时,B-Tree索引就无法起作用了,除非是索引覆盖查询,否则需要根据索引扫描的结果回表查询所有符合条件的记录,如果数据量巨大,将产生大量随机I/O,从而导致数据库的响应时间大到不可接受的程度。

分区可能遇到的问题:

  1. NULL值会使分区过滤无效; 在旧版本中可以创建一个“无用”的第一个分区来处理特殊值的情况。5.5以后的版本可以直接基于列进行分区:
    PARTITION BY RANGE COLUMNS(order_date);
    
  2. 分区列和索引列不匹配;
  3. 选择分区的成本可能很高;
  4. 打开并锁住所有底层表的成本可能很高;
  5. 维护分区的成本可能很高;
  6. 所有分区都必须使用相同的存储引擎;
  7. 某些存储引擎不支持分区;

对于访问分区表,很重要的一点是要在WHERE条件中带入分区列(有时看似多余也要带上),这样就可以让优化器能够过滤掉无须访问的分区。假如没有这些条件,MySQL就需要让对应存储引擎访问这个表的所有分区。

可以通过EXPLAIN查看查询是否使用了分区过滤:

EXPLAIN PARTITIONS SELECT * FROM sales_by_day WHERE day > '2011-01-01';

如果结果中partitions部分没有展示所有的分区,说明有分区过滤。

虽然在创建分区时可以使用表达式,但在查询时只能根据列来过滤分区。

合并表

合并表是一种早期的、简单的分区实现,和分区表相比有一些不同的限制,并且缺乏优化。 合并表允许用户单独访问各个子表。 是一种将被淘汰的技术。

视图

MySQL中视图有两种实现方式:合并算法、临时表算法。 如果视图中包含GROUP BY、DISTINCT、聚合函数、UNION、子查询等,只要无法在原表记录和视图记录中建立一一映射,都将使用临时表算法来实现视图(未来版本中可能改变)。

可以通过EXPLAIN查看视图的实现方式:

EXPLAIN SELECT * FROM <VIEW>;

如果结果中select_type为DERIVED,则是采用临时表算法实现的。

也可以在创建视图的时候指定算法:

CREATE ALGORITHM=TEMPTABLE VIEW v1 AS SELECT * FROM sakila.actor;

可更新视图:指可以通过更新这个视图来更新视图涉及的相关表(更新、删除、写入)。被更新的列必须来自同一个表中。

所有使用临时表算法实现的视图都无法被更新。

外键约束

InnoDB是目前MySQL中唯一支持外键的内置存储引擎。 (详略)

在MySQL内部存储代码

触发器、存储过程、函数,略。

游标

(略)

绑定变量

MySQL在使用绑定变量时可以更高效地执行大量的重复语句:

  1. 在服务器端只需要解析一次SQL语句;
  2. 某些优化器的工作只需要执行一次,因为它会缓存一部分执行计划;
  3. 以二进制的方式只发送参数和句柄比每次都发送ASCII码文本效率更高。而且可以分块传输。
  4. 更安全;

绑定变量有一些限制:

  1. 绑定变量是会话级别的;
  2. 如果只执行一次SQL,那么使用绑定变量开销更大;
  3. 绑定变量SQL总数的限制是一个全局限制;

插件

(略)

字符集和校对

字符集是一种从二进制编码到某类字符符号的映射,校对是一组用于某个字符集的排序规则。

每种字符集都可能有多种校对规则,并且都有一个默认的校对规则。

只有基于字符的值才有字符集的概念,对于其他类型的值,字符集只是一个设置,指定用哪一种字符集来做比较或其他操作。

MySQL服务器、每个数据库、每个表都有自己的字符集默认值。最靠底层的设置将影响最终创建的对象。

当服务器和客户端通信时,它们可能使用不同的字符集,这时服务器端将进行必要的翻译转换工作:

  1. 服务器端总是假设客户端按照character_set_client设置的字符来传输数据和SQL语句;
  2. 当服务器收到客户端的SQL语句时,先将其转换成字符集character_set_connection,也会使用这个设置来决定如何将数据转换成字符串;
  3. 当服务器端返回数据或错误信息给客户端时,会将其转换成character_set_result; 即:
    客户端 -->SQL
      --> 从character_set_client转换成character_set_connection
     --> 处理SQL语句
       --> 从character_set_connection转换成character_set_result
         --> 查询结果
    

    可以通过SET NAMES或SET CHARACTER SET来改变设置。

全文索引

(略)

分布式事务

(略)

查询缓存

(略)

第8章 优化服务器设置

MySQL配置的工作原理

MySQL从命令行参数或者配置文件中获取配置信息。服务器会读取配置文件的内容,删除所有注释和换行,然后和命令行选项一起处理。

在不同的操作系统上MySQL配置文件的位置不同,可以使用如下方式查看当前使用的配置文件的路径:

$ which mysqld
/usr/local/bin/mysqld

$ /usr/local/bin/mysqld --verbose --help | grep -A 1 'Default options'
Default options are read from the following files in the given order:
/etc/my.cnf /etc/mysql/my.cnf /usr/local/etc/my.cnf ~/.my.cnf

MySQL配置文件中下划线和横线等价。

不同的配置项有不同的作用域(服务器、会话、对象)。除了在配置文件中设置变量,有很多变量也可以在服务器运行时修改(MySQL关闭时可能会丢失)。

如果在服务器运行时修改了变量的全局值,这个值对当前会话和其他任何已经存在的会话都不起作用。

不同配置项可能有不同的单位值。

在配置文件中不能使用表达式。

可以为配置项赋予默认值DEFAULT,这将使配置项使用上一级的配置。

常用变量: key_buffer_size:为键缓冲区分配指定空间(使用时才会分配); table_cache_size:可以缓存的表的数量,当线程打开新表时会检查,如果已满,则删除不常使用的表; thread_cache_size:缓存的线程数; query_cache_size:用来缓存查询; read_buffer_size; read_rnd_buffer_size; sort_buffer_size;

应该始终通过监控来确认生产环境中变量的修改(基准测试是不够的,不是真实的工作负载)。

不要根据比率来调优,比如如果键缓存的命中率应该高于某个百分比,如果命中率过低,则应该增加缓存的大小。这是非常错误的意见。缓存命中率跟缓存是否过大或过小没有关系。

没有适合所有场景的最佳配置文件。

配置文件示例

[mysqld]
datadir                  = /var/lib/mysql
socket                   = /var/lib/mysql/mysql.sock
pid_file                 = /var/lib/mysql/mysql.pid
user                     = mysql
port                     = 3306
default_storage_engine   = InnoDB
 # InnoDB
innodb_buffer_pool_size  = <value>  # 缓存行、自适应哈希索引、插入缓存、锁等
innodb_log_file_size     = <value>
innodb_file_per_table    = 1
innodb_flush_method      = 0_DIRECT
 
 # MyISAM
key_buffer_size          = <value>

 # Logging
log_error                = /var/lib/mysql/mysql-error.log
slow_query_log           = /var/lib/mysql/mysql-slow.log

 # Other
tmp_table_size           = 32M
max_heap_table_size      = 32M
query_cache_type         = 0
query_cache_size         = 0
max_connections          = <value>
thread_cache             = <value>
table_cache              = <value>
open_files_limit         = 65535

[client]
socket                   = /var/lib/mysql/mysql.sock
port                     = 3306

很大的缓冲池会有一些问题,例如预热和关闭(涉及脏数据写回)都会花费很长的时间。 (详略)

第9章 操作系统和硬件优化

(详略)

第10章 复制

一台主库的数据可以同步到多台备库上,备库本身也可以被配置成另外一台服务器的主库。(通过在主库上记录二进制日志,在备库上重放日志的方式来实现异步的数据复制) 复制通常不会增加主库的开销,主要是启用二进制日志带来的开销。每个备库也会对主库增加一些负载,尤其是备库请求从主库读取旧的二进制日志文件时。

总的来说复制有三个步骤:

  1. 在主库上把数据更改记录到二进制日志中;
  2. 备库将主库上的日志复制到自己的中继日志中;
  3. 备库读取中继日志的事件,将其重放到备库数据上;

即,主库上的数据更改会记录到二进制日志中,而备库上的I/O线程会不断地去请求主库的二进制日志,并把结果记录到备库上的中继日志中。然后备库上的SQL线程会不断地读中继日志并回放。

(详细配置过程,略)

5.0以前只支持基于语句的复制(逻辑复制),备库实际是把主库上执行过的SQL再执行一遍。

第11章 可扩展的MySQL

可扩展性表明了当需要增加资源以执行更多工作时系统能够获得等同提升的能力。缺乏扩展能力的系统在达到收益递减的转折点后将无法进一步增长。

(详略)

第12章 高可用性

高可用性意味着更少的宕机时间,100%的可用性是不可能达到的。 (详略)

第13章 云端的MySQL

(略)

第14章 应用层优化

Web服务器问题

Apache不适合用作通用Web服务器(既处理动态脚本也处理静态文件):Apache对于静态文件的请求存在资源浪费,进程会复用,如果前一次处理的是动态语言脚本的请求,在请求结束后并不会释放所有的内存给操作系统,这样会造成一个占用内存很多的进程来为一个很小的请求服务的情况。同样的,这些被复用的进程也可能会保持大量MySQL连接,从而浪费MySQL资源。 总之,不要使用Apache来做静态内容服务,或者至少和动态服务使用不同的Apache实例。

使用缓存代理服务(Squid、Varnish),防止所有请求到达Web服务器。

打开gzip压缩。

不要为用于长距离连接的Apache配置启用Keep-Alive选项,因为这会使得重量级的Apache进程存活很长时间。

缓存

在实践中发现从Nginx的内存中获取内容比从缓存代理磁盘中获取数据要快。

(讲的太乱,也有一点过时,没有介绍redis,略)

第15章 备份与恢复

(使用阿里云,略)

第16章 MySQL用户工具

(略)

附录A MySQL分支与变种

(略)

附录B MySQL服务器状态

PERFORMANCE_SCHEMA库、INFORMATION_SCHEMA库、SHOW命令。 (详略)

附录C 大文件传输

(略)

附录D EXPLAIN

EXPLAIN命令用于查看查询优化器选择的查询计划。被标记了EXPLAIN的查询会返回关于执行计划中每一步的信息,而不是执行它。(实际上,如果查询在from子句中包括子查询,那么MySQL实际上会执行子查询) EXPLAIN只是一个近似结果。

EXPLAIN的结果

id 标识SELECT所属的行,如果在语句中没有子查询或者联合查询,那么只会有一行(因为只有1个SELECT),且id值为1.

select_type

  1. SIMPLE 意味着该查询不包含子查询和UNION,如果查询有任何复杂的子部分,则最外层部分标记为PRIMARY(id为1的查询)。
  2. SUBQUERY 包含在SELECT列表中的子查询(即不是位于FROM子句中的查询);
  3. DERIVED 包含在FROM子句中的子查询;
  4. UNION 在UNION查询中的第二个和随后的SELECT被标记为UNION;
  5. UNION RESULT 用来从UNION的匿名临时表检索结果的SELECT;

table 表示正在访问哪个表(包括匿名临时表,比如derived1),可以在这一列从上往下观察MySQL的关联优化器为查询选择的关联顺序。

type 访问类型,决定如何查找表中的行,从最差到最优排列如下:

  1. ALL 全表扫描;
  2. index 按索引次序全表扫描,主要优点是避免了排序,缺点是当随机访问时开销非常大;如果在Extra列中有Using index,说明MySQL正在使用覆盖索引,即只扫描索引的数据。
  3. range 有限制的索引扫描(带有between或where >等条件的查询); 注意,当使用IN()、OR()来查询时,虽然也显示范围扫描,但是其实是相当不同的访问类型,在性能上有重要的差异。
  4. ref 索引查找,返回所有匹配某个值的行,这个值可能是一个常数或者来自多表查询前一个表里的结果值;
  5. eq_ref 也是索引查找,且MySQL知道最多只返回一条符合条件的记录(使用主键或者唯一性索引查询),MySQL对于这类访问优化的非常好;
  6. const、system 当MySQL能对查询的某部分进行优化并将其转换为一个常量时,它就会使用这些访问类型;
  7. NULL 意味着MySQL能在优化阶段分解查询语句,在执行阶段甚至用不着再访问表或者索引;

possible_keys 显示查询可以使用哪些索引。

key 显示MySQL决定采用哪个索引来优化对表的访问。

key_len 显示MySQL在索引里使用的字节数。

ref 显示之前的表在key列记录的索引中查找值所用的列或常量。

rows MySQL估计为了找到所需的行而要读取的行数。

filtered 在使用EXPLAIN EXTENED时才会出现,查询结果记录数占总记录数的百分比。

Extra

  1. Using index:表示将使用覆盖索引;
  2. Using where:意味着MySQL服务器将在存储引擎检索后再进行过滤;
  3. Using temporary:意味着MySQL在对查询结果排序时会使用一个临时表;
  4. Using filesort:意味着MySQL会对结果使用一个外部索引排序,而不是按索引次序从表里读取行;
  5. Range checked for each record …:意味着没有好用的索引;

附录E 锁的调试

(略)

在MySQL上使用Sphinx

(略)


抖音
文章目录