mysql 培训-优化篇

91
MYSQL 培训 - 优化篇 HXP_20110515 化篇

Upload: sunmonth

Post on 01-Jul-2015

953 views

Category:

Technology


5 download

DESCRIPTION

Mysql 培训-优化篇

TRANSCRIPT

Page 1: Mysql 培训-优化篇

MYSQL培训 -优化篇HXP_20110515

化篇优

Page 2: Mysql 培训-优化篇

化篇优

1

2

3

4

MYSQL 架构组成

MYISAM 表锁优化建议

MYSQL 数据库 QUERY 的优化

QUERY CACHE 优化

6

常用存储引擎优化 MYSQL 可扩展设计的基本原

5

Page 3: Mysql 培训-优化篇

1. MYSQL 架构组成

• 1.MYSQL 物理文件组成• 1.> 日志文件• 2.> 数据文件• 3.>REPLICATION 相关文件• 4.> 其他文件• 2.MYSQL SERVER 系统架构• 1.> 逻辑模块组成• 2.> 各模块工作配合

Page 4: Mysql 培训-优化篇

日志文件• 1. 错误日志: error.log• 需在启动时打开— log-error 选项。默认放在数据目录下,以 HOSTNAME.ERR 命名。可以通过

命令• --log error=filename 修改其存放路径和文件名

• 2. 二进制日志: binary log• MYSQL 会将所有修改数据库的 QUERY 以二进制形式记录到日志文件中。还包括每一条查询

QUERY 所执行的时间,所耗的资源,以及相关事务信息,所以 BINLOG 是事务安全的。• 它有一些其他的选项,如 MAX_BINLOG_SIZE 等。

• 3. 更新日志 :update log• 与 BINLOG 相似,在以前版本以文本文件记录,从 5.0 开始不在支持。

• 4. 查询日志 :query log• HOSTNAME.LOG

• 5. 慢查询日志: slow query log• --LOG_SLOW_QUERYS=FILENAME 默认文件名: HOSTNAME-SLOW.LOG 在数据目录下

• 6.innodb 的在线日志: innodb redo log

Page 5: Mysql 培训-优化篇

数据文件

• 1.FRM 文件• 与表相关的元数据信息都存放在 .FRM 文件中,包括表结构信息。不论是什么的存储引擎都

会有一个表名 .FRM 的文件

• 2.MYD 文件• 是 MYISAM 存储引擎专用的,存放 MYISM 表的数据。

• 3.MYI 文件• 也是 MYISAM 存储引擎专用的,主要存放 MYISAM 表的索引相关信息。对于 MYISAM 存储来

说,可以被 CACHE 的内容主要是来源于 .MYI 的文件中。

• 4.IBD 文件和 IBDATA 文件• 这两种都是存放 INNODB 数据的文件(包括索引),因为 INNODB 的数据存储方式能够通过

配置来决定是使用共享表空间存放存储数据,还是用独享表空间来存放数据。

Page 6: Mysql 培训-优化篇

Replication 相关文件

• 1.master.info 文件• 存在于 SLAVE 端的数据目录下,里面存放了该 SLAVE 的 MASTER 端的相关信

息,包括 MASTER 的主机地址、连接用户、连接密码、连接端口、当前日志位置、已经读取到的日志位置等信息。

• 2.relaylog relay log index 文件• MYSQL-RELAY-BIN.****** 文件用于存放 SLAVE 端的 I/O 线程从 MASTER 端读取

的 BINARY LOG 信息,然后由 SLAVE 端的 SQL 线程从该 relay log 中读取并解析相应的日志信息,转换成 MASTER 所执行的 QUERY 语句,接着在 SLAVE 端应用。

• MYSQL-RELAY-BIN.INDEX 文件的功能类似与 MYSQL-BIN.INDEX ,同样是记录日志存放位置的绝对路径,只不过它所记录不是 BINARY.LOG, 而是 RELAY LOG

• 3.relay-log.info 文件• 类似于 MASTER.INFO,RELAY-LOG.INFO 文件存放通过 SLAVE 的 I/O 线程写入本

地的 RELAY LOG 相关信息,以便 SLAVE 端的 SQL 线程及某些管理操作随时能够获得当前复制的相关信息。

Page 7: Mysql 培训-优化篇

其他文件

• 1.SYSTEM CONFIG FILE• MYSQL 的系统配置文件以般都是在 MY.CNF ,NUIX/LINUX 环境下一般在 /ETC 下

。• 它包含多种参数选项。

• 2.PID 文件• 是 MYSQL 在 LINUX 和 UNIX 环境下的一个进程文件,和其他 UNIX/LINUX 服务

端程序一样,它存放着自己的进程 ID.

• 3.SCOKET 文件• 也是在 Linux/UNIX 文件下才有的,客户端可以不通过 TCP/IP 网络而直接使用

UNIXSCOKET 来连接 MYSQL

Page 8: Mysql 培训-优化篇

MYSQL SERVER 系统架构

• 1. 逻辑模块组成• 2. 各模块工作配合

Page 9: Mysql 培训-优化篇

逻辑模块组成

• 在 Mysql 中,我们看作两层架构,即SQL Layer ( SQL 处理层)和 Storage Engine Layer (存储引擎层)。在MySQL 处理底层数据之前,所有的操作都是在 SQL Layer 层完成的,如:权限判断、 SQL 解析、查询优化、cache 处理等。经过这一层,再交由Storage Engine Layer 层处理。所以我们可以将 MySQL 看作是右图的结构。

• 但是, MySQL 的每一层也包含许多小模块,下面我们做一简单介绍

Page 10: Mysql 培训-优化篇

逻辑模块组成• 1. 初始化模块• 此模块是在 MySQL Server 启动的时候对整个系统进行初始化操作,包括 buffer 、 cache

结构的初始化和内存空间的申请、各种系统变量和存储引擎的初始化工作等。

• 2. 核心 API• 此模块主要是为了提供一些非常高效的底层操作功能的优化实现,包括各种底层数据结构的实现、特殊算法的实现、字符串与数字处理以及最重要的内存管理工作等。核心 API 的所有源代码都集中在 mysys 和 string 文件夹下面。

• 3. 网络交互模块• 底层网格交互模块抽象出底层网络交互所使用的 api ,实现底层网络数据的接收与发送,以方

便其他模块调用和对这一部分的维护。源代码在 vio 文件夹下面。

• 4. Client & Server 交互协议模块• 任何 C/S 结构的软件系统,都有自己独有的信息交互协议, MySQL 也是如此。这些协议都是

建立在 OS七层模型之上的,如 TCP/IP 和 Unix Socket 。

Page 11: Mysql 培训-优化篇

逻辑模块组成• 5. 用户模块• 此模块主要实现权限控制和授权管理。如判断某个用户能不能进入某个库内进

行相关操作。

• 6. 访问控制模块• 此模块与用户模块主要的区别是它限定了用户的访问控制权限,如

select 、 update 等。这两个模块共同组成了整个系统的权限安全管理功能。

• 7. 连接管理、连接线程和线程管理

• 连接管理模块主要负责监听 MySQL Server 的各种请求,接收和转发连接请求到线程管理模块。每一个连接上MySQL 的客户端请求都会被分配或创建一个独享的线程为其服务。而连接线程的主要工作就是负责MySQL Server 和客户端的连接通信,接受客户端请求、传递 Server 端结果信息等。线程管理模块则负责管理和维护这些线程,包括线程创建、线程的 cache 等。

• 8. Query 解析和转发模块• 此模块主要负责将接收到的 SQL 进行语义和语法的分析,然后按照分类有针对性地转发给各个对应的处理模块。

Page 12: Mysql 培训-优化篇

逻辑模块组成• 9. Query Cache 模块• 此模块非常重要,它可以将传递到 MySQL Server 的所有 Select 类 SQL 语句的

结果集缓存到内存中,当 SQL发生变化时, cache 自动失效。这个模块在读写比例非常高的系统中对性能提高的作用是非常显著的。当然它也非常消耗内存。

• 10. Query 优化器模块• 此模块主要负责对接收到的 SQL 进行算法优化,得到一个最优策略,告诉后面的

程序如何取这个语句的结果。

• 11. 表变更管理模块• 此模块主要负责完成一些 DML 和 DDL 类 query 的操作。• DML(Data Manipulation Language) : insert, update, delete, select• DDL(Data Definition Language) : create, drop, alter

• 12. 表维护模块• 此模块主要负责表的状态检查、错误修复以及优化和分析。

Page 13: Mysql 培训-优化篇

逻辑模块组成

• 13. 表管理器• 此模块与表维护模块的功能完全不同。上次我们提到的 .frm 文件就是由这个模块进行

管理和维护的。它还负责 table级别的锁管理。

• 14. 系统状态管理模块• 此模块主要负责当客户端请求系统状态时,将各种状态信息返回给客户。如 show

status 、 show variables 等。

• 15. 日志记录模块• 此模块主要负责整个系统的逻辑层的日志的记录,包括 Error Log 、 Binary Log 、

Slow Query Log 等。

Page 14: Mysql 培训-优化篇

逻辑模块组成

• 16. 复制模块• 此模块主要负责主从配置中的数据同步,故又可分为 Master 模块和 Slave 模块。• Master 模块主要负责在 Slave 端读取 Binary Log 以及与 Master 端的 I/O 线程交互等工

作。• Slave 模块主要负责从 Master 端请求和接受 Binary Log ,并写入本地的 Relay Log 中

的 I/O 线程,然后将其转化为 SQL 应用于 Slave 端。

• 17. 存储引擎接口模块• 目前所有的数据库产品中,基本上只有 MySQL实现了底层数据存储引擎的插件式

管理。这个模块实际上是一个抽象类,将各种数据处理高度抽象化。

Page 15: Mysql 培训-优化篇

各模块之间的相互配

Page 16: Mysql 培训-优化篇

MySQL 数据库锁定机制• MySQL 使用了三种锁定机制:行级锁定、页级锁定和表级锁定

• 一、行级锁定• 特点:锁定对象的颗粒度很小,也是目前各大数据库管理软件中所实现的锁定颗粒最小的

。发生锁定资源的概率最小,能够给予尽可能大的并发处理能力,提高整体性能。• 弊端:每次获取锁和释放锁需要做的事情较多,带来的消耗更大,容易发生死锁。• 用途: MyISAM 、 Memorey 、 CSV 等一些非事务性存储引擎。

• 二、表级锁定• 特点:表级锁定是 MySQL 各大存储引擎中颗粒度最大的锁定机制,实现逻辑非常简单,带来的系统负面影响最小。获取和释放锁的速度较快,避免死锁问题。

• 弊端:出现锁定资源争用的概率最高,致使并发度大打折扣。• 用途: InnoDB 和 NDB Cluster 存储引擎。

• 三、页级锁定• 特点:锁定颗粒度介于行级锁定与表级锁定之间,并发处理能力也介于二者之间,同

样会发生死锁问题。• 用途: BerkeleyDB 存储引擎• 在数据库实现锁定资源的过程中,随着锁定颗粒度越来越小,锁定相同数据量的数据所消耗

的内存数量是越来越多的,实现算法也越来越复杂。不过随着颗粒度的减小,应用程序访问请求遇到锁等待的可能性也会随之降低,系统整体并发度也会随之提升。

Page 17: Mysql 培训-优化篇

各种锁定机制分析• 1. 表级锁定• 表级锁定分两种类型:读锁定和写锁定。• 在 MySQL 中,主要通过 4 个队列来维护这两种锁定:两种存放正在锁定中的读写

信息,另外两个存放正在等待中的读写锁定信息。• > Current read-lock queue (lock->read)• > Pending read-lock queue (lock->read_wait)• > Current write-lock queue (lock->write)• > Pending write-lock queue (lock->write_wait)

• 2. 行级锁定

Page 18: Mysql 培训-优化篇

表锁定• 虽然对于使用者来说表现为两种锁定,但在 MySQL 内部却由一个枚举量

(thr_lock_type) 定义了 11 种锁定类型,具体如下:1.> IGNORE: 当发生锁请求的时候内部交互使用,在锁定结构和队列中并不会有任何信息

存储2.> UNLOCK: 释放锁定请求的交互时使用3.> READ: 普通读锁定4.> LOCK: 普通写锁定5.> READ_WITH_SHARED_LOCKS: 在 Innodb 中使用到,由如下方式产生,如:

SELECT ... LOCK IN SHARE MODE

6.> READ_HIGH_PRIORITY: 高优先级读锁定7.> READ_NO_INSERT: 不允许 Concurrent Insert 的锁定8.> WRITE_ALLOW_WRITE: 这个类型实际上就是当由存储引擎自行处理锁定的时候, MySQL允许其他线程再获取读或写锁定,因为即使资源冲突,存储引擎自己也会知道该如何处理

9.> WRITE_ALLOW_READ: 这种锁定发生在对表做 DDL(ALTER TABLE...) 的时候, MySQL可以允许其他线程获取读锁定,因为 MySQL 是通过重建整个表然后再 rename 而实现的功能,所以在整个过程中,原表仍然可以提供读服务

10.> WRITE_CONCURRENT_INSERT: 正在进行 Concurrent Insert 的时候使用的锁方式,该锁定进行的时候,除了 READ_NO_INSERT 之外的其他任何读锁定请求都不会被阻塞

11.> WRITE_DELAYED: 在使用 INSERT DELAYED 时使用的锁定类型• > WRITE_LOW_PRIORITY: 显式声明的低级别锁定方式,通过设置

LOW_PRIORITY_UPDATES=1 而产生• > WRITE_ONLY: 在操作过程中,某个操作异常中断之后,系统内部需要进行 CLOSE

TABLE 操作,在这个过程中出现的锁定类型就是 WRITE_ONLY

Page 19: Mysql 培训-优化篇

表锁定• 读锁定:• 一个新的客户端请求在申请获取读锁定资源的时候,需要满足两个条件: • 1. 请求锁定的资源当前没有被写锁定• 2. 写锁定等待队列( Pending write-lock queue )中没有更高级别的写锁定等待• 当满足了上述两个条件之后,该请求被立即通过,并将相关信息存入 Current read-lock

queue队列中,而如果有任何一个条件不满足,都会被迫进入 Pending read-lock queue中等待资源的释放

• 写锁定:• 当客户端请求写锁定的时候, MySQL首先检查在 Current write-lock queue 中是否有锁

定相同资源的信息存在。如果没有,再检查 Pending write-lock queue ,如果找到了,则自己也需要进入等待队列并暂停自身线程来等待锁定资源。如果 Pending write-lock queue 为空,再检测 Current read-lock queue ,如果有锁定存在,则同样需要进入Pending read-lock queue 中等待。当然,也可能遇到如下两种情况:

• 1. 请求锁定的类型为 WRITE_DELAYED• 2. 请求锁定的类型为 WRITE_CONCURRENT_INSERT或

WRITE_ALLOW_WRITE ,同时 Current read-lock 是 READ_NO_INSERT

Page 20: Mysql 培训-优化篇

行级锁定

• 行级锁定不是 MySQL 自己实现的锁定机制,而是由 Innodb 和 NDB Cluster 存储引擎实现的。而由于这个锁定机制是由各个存储引擎自行实现,所以具体实现算法也有差别。

• Innodb 的行锁定分为两种:共享锁和排它锁。而为了让行锁定和表锁定机制共存, Innodb 也使用了意向锁(表级锁定)的概念,于是有了意向共享锁和意向排它锁。

• 意向锁的意思就是我这个线程在遇到需要资源被锁定的情况下,再附加一个我想要的锁的意思,来等待资源的释放,再自行添加自己需要的锁

• 注意:当一个请求需要锁定资源的时候,如果资源已被共享锁锁定,只能添加共享锁;而如果资源已被排它锁锁定,则只能等待锁定释放,再添加自己使用的锁。意向共享锁可以并存,意向排它锁只能存在一个。

Page 21: Mysql 培训-优化篇

行级锁定

Page 22: Mysql 培训-优化篇

合理利用锁机制优化 MYSQL

• 1.MYISAM 表锁优化建议 _重点• 2.INNODB 行锁优化建议• 3. 系统锁定争用情况查询 _重点

Page 23: Mysql 培训-优化篇

MYISAM 表锁优化建议 _重点• 1. 缩短锁定时间• ( 1 )尽量避免大的复杂 QUERY ,将复杂 QUERY分拆成几个小的

QUERY分步进行;• ( 2 )尽可能地建立足够高效的索引,让数据检索更迅速;• ( 3 )尽量让 MYISAM 存储引擎的表只存放必要的信息,控制字段类型;

• ( 4 )利用合适的机会优化 MYISAM 表数据文件;以上四点主要是从 IO与 CPU 方面来考虑提升性能的

• 2. 分离并行的操作• ( 1 ) Concurrent_Insert=2 ,无论 MYISAM 存储引擎的表数据文件的中

间部分是否存在因为删除数据而留下的空闲空间,都允许在数据文件尾部进行 Concurrent_Insert;

• ( 2 ) Concurrent_Insert=1 ,当 MYISAM 存储引擎表数据文件中间不存在空闲空间的时候,可以从文件尾部进行 Concurrent_Insert;

• ( 3 ) Concurrent_Insert=0 ,无论 MYISAM 存储引擎的表数据文件中间部分是否存在因为删除数据而留下的空闲空间,都不允许Concurrent_Insert 。

Page 24: Mysql 培训-优化篇

MYISAM 表锁优化建议 _ 重点

• 3 、合理利用读写优先级• MYSQL 的表级锁定在默认情况下是写优先级大于读

。所以,如果系统是一个以读为主,而且要优先保证查询性能的话,可以通过设置系统参数选项low_priority_updates=1 ,将写的优先级设置为比读低,即告诉 MYSQL 尽量先处理读请求;如果系统须要有限保证数据写入的性能的话,则不用设置low_priority_updates 参数了。并发优化的另外一个方面还可以通过开启 Key Cache ,用来缓存索引来提高读取速度,如果这样还不觉得快的话,还可以通过Query Cache 功能来直接缓存 Query 的结果集。当然,还可以合理利用第三方案 Cache 软件,如Memcached ,来缓存数据,提升系统性能。

Page 25: Mysql 培训-优化篇

Innodb 行锁优化

• 缩小锁定范围• 尽量让所有检索都通过索引来完成 • 合理设计索引,让 Innodb 在索引键上面加锁

时尽可能准确,缩小锁定范围。 • 查询时尽可能减少基于范围的检索过滤,避免

锁定不必要记录 • 尽量控制事务大小,减小锁定资源量与锁定时

间 • 尽量使用较低级别的事物隔离,以减少 mysql

实现事务隔离带来的成本

Page 26: Mysql 培训-优化篇

系统锁定争用情况查询

Table_locks_immediate :产生表级锁定的次数

Table_locks_waited :出现表级锁定争用而发生等待的次数

Page 27: Mysql 培训-优化篇

系统锁定争用情况查询

Page 28: Mysql 培训-优化篇

MYSQL 数据库 QUERY 的优化

• 1. 理解 MYSQL 的 QUERY OPTIMIZE

• 2.QUERY 语句优化基本思路和原则• 3. 充分利用 EXPLAIN 和 PROFILING

• 4. 合理设计并利用索引• 5.JOIN 的实现原理与优化思路• 6.ORDER BY 、 GROUP BY 和 DISTINCT

优化• 7. 其他常用优化

Page 29: Mysql 培训-优化篇

MySQL Query Optimizer 基本工作原理

• MySQL 的 Query Tree 是通过优化实现 DBXP 的经典数据结构和 Tree 构造器而生成的,是指导完成一个 Query 语句的请求须要处理的工作步骤,我们可以简单地认为就是一个的数据处理流程,只是以Tree 的数据结构存放而已。通过 Query Tree 可以很清楚地知道一个 Query 的完成须要经过哪些步骤,每一步的数据来源在哪里,处理方式是怎样的。在整个 DBXP 的 Query Tree 生成过程中, MySQL 使用了 LEX 和 YACC 这两个功能非常强大的语法(词法)分析工具。 MySQL Query Optimizer 的所有工作都是基于这个 Query Tree 进行的。各位读者朋友如果对 MySQL Query Tree 实现生成的详细信息比较感兴趣,可以参考 Chales A. Bell 的《 Expert MySQL 》这本书,里面有比较详细的介绍。

• MySQL Query Optimizer 并不是一个纯粹的 CBO ( Cost Base Optimizer ),而是在 CBO 的基础上增加了一个被称为 Heu_ristic Optimize (启发式优化)的功能。也就是说, MySQL Query Optimizer 在优化一个 Query 认为的最优执行计划时,并不一定完全按照数据库的元信息和系统统计信息,而是在此基础上增加了某些特定的规则。 其实就是在 CBO 的实现中增加了部分RBO ( Rule Base Optimizer )的功能,以确保在某些特殊场景下控制 Query 按照预定的方式生成执行计划。

• 当客户端向MySQL 请求一条 Query ,命令解析器模块完成请求分类,区别出是 SELECT 并转发给MySQL Query Optimizer 时, MySQL Query Optimizer

• 1.首先会对整条 Query 进行优化,处理掉一些常量表达式的预算,直接换算成常量值。

• 并 2. 对 Query 中的查询条件进行简化和转换,如去掉一些无用或显而易见的条件、结构调整等。

• 3. 然后分析 Query 中的 Hint 信息(如果有),看显示 Hint 信息是否可以完全确定该 Query 的执行计划。如果没有 Hint 或 Hint 信息还不足以完全确定执行计划,则会读取所涉及对象的统计信息,

• 4.根据 Query 进行写相应的计算分析,然后再得出最后的执行计划。

Page 30: Mysql 培训-优化篇

QUERY 语句优化基本思路和原则• (1 )优化更需要优化的 Query ;

• ( 2 )定位优化对象的性能瓶颈;• ( 3 )明确优化目标;• ( 4 )从 Explain 入手;• ( 5 )多使用 Profile ;• ( 6 )永远用小结果集驱动大的结果集;• ( 7 )尽可能在索引中完成排序;• ( 8 )只取自己需要的 Columns ;• ( 9 )仅仅使用最有效的过滤条件; _举例• ( 10 )尽可能避免复杂的 Join 和子查询。

Page 31: Mysql 培训-优化篇

仅仅使用最有效的过滤条件

• 方案一:将用户 ID 和用户 nick_name 两者都作为过滤条件放在 WHERE子句中来查询, Query 的执行计划如代码示例 8-1 所示:代码 8-1

• sky@localhost : example 11:29:37> EXPLAIN SELECT * FROM group_message • -> WHERE user_id = 1 AND author='1111111111'\G • *************************** 1. row *************************** • id: 1

• select_type: SIMPLE • table: group_message • type: ref • possible_keys: group_message_author_ind,group_message_uid_ind • key: group_message_author_ind

• key_len: 98 • ref: const • rows: 1 • Extra: Using where • 1 row in set (0.00 sec)

场景:( 1 )知道用户 ID 和用户 nick_name

( 2 )信息所在表为 group_message

( 3 ) group_message 中存在用户 ID(user_id) 和 nick_name(author) 两个索引

Page 32: Mysql 培训-优化篇

仅仅使用最有效的过滤条件• 方案二:仅仅将用户 ID 作为过滤条件放在 WHERE子句中来查询,

Query 的执行计划如示例代码 8-2 所示:代码 8-2• sky@localhost : example 11:30:45> EXPLAIN SELECT * FROM gr

oup_message • -> WHERE user_id = 1\G • *************************** 1. row *************************** • id: 1 • select_type: SIMPLE • table: group_message • type: ref • possible_keys: group_message_uid_ind • key: group_message_uid_ind • key_len: 4 • ref: const • rows: 1 • Extra: • 1 row in set (0.00 sec)

Page 33: Mysql 培训-优化篇

仅仅使用最有效的过滤条件• 方案三:仅将用户 nick_name 作为过滤条件放在 WHERE子句中来

查询, Query 的执行计划如示例代码 8-3 所示:代码 8-3• sky@localhost : example 11:38:45> EXPLAIN SELECT * FROM gr

oup_message • -> WHERE author = '1111111111'\G • *************************** 1. row *************************** • id: 1 • select_type: SIMPLE • table: group_message • type: ref • possible_keys: group_message_author_ind • key: group_message_author_ind • key_len: 98 • ref: const • rows: 1 • Extra: Using where • 1 row in set (0.00 sec)

Page 34: Mysql 培训-优化篇

仅仅使用最有效的过滤条件

• 初略一看三个执行计划好像都挺好啊,每一个 Query 的执行类型都用到了索引,而且都是 "ref"类型。可是仔细一分析就会发现, group_message_uid_ind 索引的索引键长度为 4 ( key_len: 4 ),由于 user_id 字段类型为 int ,所以可以判定 Query Optimizer 给出的这个索引键长度是完全准确的。而 group_message_author_ind 索引的索引键长度为98 ( key_len: 98 ),因为 author字段定义为 varchar(32) ,所使用的字符集是 utf8 , 32×3 + 2 = 98 。而且, user_id 与 author (来源于 nick_name )全部是一一对应的,所以同一个 user_id 有哪些记录,所对应的 author 也会有完全相同的记录。这样,同样的数据在 group_message_author_ind 索引中所占用的存储空间要远远大于 group_message_uid_ind 索引所占用的空间。占用空间更大,代表访问该索引须要读取的数据量就会越多。所以,选择 group_message_uid_ind 的执行计划才是最好的。也就是说,上面的方案二才是最好的方案,使用了更多WHERE 条件的方案反而没有仅仅使用 user_id 一个过滤条件的方案优。

Page 35: Mysql 培训-优化篇

JOIN 的原理

• 在 MySQL 中,只有一种 Join 算法,就是大名鼎鼎的 Nested Loop Join ,它没有很多其他数据库所提供的 Hash Join ,也没有 Sort Merge Join 。顾名思义, Nested Loop Join 实际上就是通过驱动表的结果集作为循环基础数据,然后将该结果集中的数据作为过滤条件一条条地到下一个表中查询数据,最后合并结果。如果还有第三个表参与 Join ,则把前两个表的 Join 结果集作为循环基础数据,再一次通过循环查询条件到第三个表中查询数据,如此往复。

• 驱动表的定义:首先被访问的表又称外表

Page 36: Mysql 培训-优化篇

尽可能避免复杂的 Join 和子查询

• MySQL 在并发这一块并不是太好,当并发量太高的时候,系统整体性能可能会急剧下降,尤其是遇到一些较为复杂的 Query 的时候。这主要与 MySQL 内部资源的争用锁定控制有关,如读写相斥等。 InnoDB 存储引擎由于实现了行级锁定可能还要稍微好一些,如果使用的是 MyISAM 存储引擎,并发一旦较高,性能下降非

常明显。所以, Query 语句所涉及的表越 多,须要锁定的资源就越多。也就是说,越复杂的 Join 语句,锁定的资源也就越多,所阻塞的其他线程也就越多。相反,如果将比较复杂的 Query 语句分拆成多个较为简单的 Query 语句分步执行,每次锁定的资源也就会少很多,所阻塞的其他线程也要少一些。

Page 37: Mysql 培训-优化篇

尽可能避免复杂的 Join 和子查询

• 对于子查询,可能很多人都明白为什么会不被推荐使用。在 MySQL 中,子查询的实现目前还比较差,很难得到一个很好的执行计划,很多时候明明有索引可以利用,可 Query Optimizer 就是不用。 MySQL 官方给出的信息说,这一问题将在 MySQL 6.0 中得到较好的解决,将会引入 SemiJoin 的执行计划,可 MySQL 6.0 离我们投入生产环境使用恐怕还有很遥远的一段时间。所以,在 Query 优化的过程中,能不用子查询就尽量不要用。

Page 38: Mysql 培训-优化篇

3. 充分利用 EXPLAIN 和PROFILING

Page 39: Mysql 培训-优化篇

3. 充分利用 EXPLAIN 和PROFILING• select_type: SELECT类型,有以下几种不同的类型

• (1).SIMPLE: 简单的 SELECT (不使用 UNION或子查询)• (2).PRIMARY: 最外面的 SELECT ,如果我们使用 UNION或子查询,第

一个查询将会是这个类型• (3).UNION: 使用 UNION 查询时,除第一个语句外的所有语句会返回这个类型

• (4).DEPENDENT UNION:  UNION 中的第二个或后面的 SELECT 语句,取决于外面的查询。

• (5).UNION RESULT: UNION 的结果。• (6).SUBQUERY: 子查询中的第一个 SELECT 。• (7).DEPENDENT SUBQUERY: 子查询中的第一个 SELECT ,取决于外

面的查询。• (8).DERIVED: 衍生表会返回这个类型。如: select * from (select * from

jos_content) as A; 。

• table: 输出引用的表。

Page 40: Mysql 培训-优化篇

3. 充分利用 EXPLAIN 和PROFILING

• type: 联接类型,从这个选项我们可以初步判断查询效率,有以下几种不同的类型(按从最佳到最坏排序):

• (1).system: 表中仅有一行记录,这是 const 的一个特例。• (2).const: 表中最多有一行符合查询条件,它在查询开始时被读取。因为只有一行,这

行的列值可被优化器剩余部分认为是常数。 const 表很快,因为它们只被读取一次!(如上面的查询)

• (3).eq_ref: 对于每个来自于前面的表的行组合,从该表中读取一行。例如: select * from A,B where A.id=B.id ,如果 id 在 B 表中是 unique或 primary key ,会返回这个类型。它是说对于 A 表中的每一行,在 B 表中读取符合记录的一行。除了 const之外,这是最好的联接类型。

• (4).ref: 这个类型跟 eq_ref类似,不同的是 eq_ref 能根据 unique或主键在后面的表中选择出唯一的行,而不能确定唯一行,则使用这个类型。

• (5).ref_or_null: 该联接类型如同 ref ,但是添加了 MySQL 可以专门搜索包含 NULL值的行。在解决子查询中经常使用该联接类型的优化。

• (6).index_merge: 索引合并方法用于通过 range扫描搜索行并将结果合成一个。合并会产生并集、交集或者正在进行的扫描的交集的并集。在 EXPLAIN输出中,该方法表现为type列内的 index_merge 。在这种情况下, key列包含一列使用的索引, key_len包含这些索引的最长的关键元素。

Page 41: Mysql 培训-优化篇

3. 充分利用 EXPLAIN 和PROFILING• (7).unique_subquery: unique_subquery 是一个索引查找函数,可以完全替换子查询,

效率更高。 explain select * from jos_content where id in (select id from jos_categories);会使用这个类型。

• (8).index_subquery: 该联接类型类似于 unique_subquery 。可以替换 IN子查询,但只适合子查询中的非唯一索引。

• (9).range: 只检索给定范围的行,使用一个索引来选择行。 key列显示使用了哪个索引。 key_len包含所使用索引的最长关键元素。在该类型中 ref列为 NULL 。当使用= 、 <> 、 > 、 >= 、 < 、 <= 、 IS NULL 、 <=> 、 BETWEEN或者 IN操作符,用常量比较关键字列时,可以使用这个类型。

• (10).index: 这与 ALL相同,除了只有索引树被扫描。这通常比 ALL 快,因为索引文件通常比数据文件小。

• (11).all: 对于每个来自于先前的表的行组合,将要做一个完整的表扫描。如果表格是第一个没标记 const 的表,效果不是很好,并且在所有的其他情况下很差。你可以通过增加更多的索引来避免 ALL ,使得行能从早先的表中基于常数值或列值被检索出来。

Page 42: Mysql 培训-优化篇

3. 充分利用 EXPLAIN 和PROFILING• possible_keys: possible_keys列指出 MySQL 能使用哪个索引在该表中

找到行。注意,该列完全独立于 EXPLAIN输出所示的表的次序。这意味着在 possible_keys 中的某些键实际上不能按生成的表次序使用。

• 如果该列是 NULL ,则没有相关的索引。在这种情况下,可以通过检查 WHERE子句看是否它引用某些列或适合索引的列来提高你的查询性能。如果是这样,创造一个适当的索引并且再次用 EXPLAIN 检查查询。

• key: key列显示 MySQL 实际决定使用的键(索引)。如果没有选择索引,键是 NULL 。要想强制 MySQL 使用或忽视 possible_keys列中的索引,在查询中使用FORCE INDEX 、 USE INDEX或者 IGNORE INDEX 。对于 MyISAM 和 BDB 表,运行 ANALYZE TABLE 可以帮助优化器选择更好的索引。对于 MyISAM 表,可以使用 myisamchk –analyze 。

• key_len: 此列显示 MySQL决定使用的键长度。如果键是 NULL ,则长度为 NULL 。注意通过 key_len值我们可以确定 MySQL 将实际使用一个多部关键字的几个部分。在不损失精确性的情况下,长度越短越好。

• ref: 此列显示使用哪个列或常数与 key 一起从表中选择行。• rows: 此列显示了 MySQL 认为它执行查询时必须检查的行数

Page 43: Mysql 培训-优化篇

3. 充分利用 EXPLAIN 和PROFILING• Extra: 该列包含 MySQL 解决查询的详细信息。

• (1).Distinct: 一旦MYSQL找到了与行相联合匹配的行,就不再搜索了。• (2).Not exists: MYSQL 优化了 LEFT JOIN ,一旦它找到了匹配 LEFT JOIN标准的

行,就不再搜索了。• (3).Range checked for each: Record ( index map:# )没有找到理想的索引,因此对于从前面表中来的每一个行组合, MYSQL 检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一。

• (4).Using filesort: MYSQL需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行。

• (5).Using index: 列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候。

• (6).Using temporary: 看到这个的时候,查询需要优化了。这里, MYSQL需要创建一个临时表来存储结果,这通常发生在对不同的列集进行 ORDER BY 上,而不是 GROUP BY上。

• (7).Using where: 使用了 WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型 ALL或 index ,这就会发生,或者是查询有问题。

Page 44: Mysql 培训-优化篇

Profiling 的使用

• 要想优化一条 Query ,就须要清楚这条 Query 的性能瓶颈到底在哪里,是消耗的 CPU 计算太多,还是需要的 IO 操作太多?要想能够清楚地了解这些信息,在 MySQL 5.0 和 MySQL 5.1 正式版中已经非常容易做到,即通过 Query Profiler 功能。

• MySQL 的 Query Profiler 是一个使用非常方便的 Query 诊断分析工具,通过该工具可以获取一条 Query 在整个执行过程中多种资源的消耗情况,如 CPU 、 IO 、 IPC 、 SWAP 等,以及发生的 PAGE FAULTS 、 CONTEXT SWITCHE 等,同时还能得到该 Query 执行过程中

MySQL 所调用的各个函数在源文件中的位置。下面看看 Query Profiler 的具体用法。

Page 45: Mysql 培训-优化篇

Profiling 的使用• 1 )通过执行“ set profiling”命令,可以开启关闭 Query Profiler 功能。先开

启 profiling 参数,如示例代码 8-6 所示:代码 8-6• root@localhost : (none) 10:53:11> SET profiling=1;

• Query OK, 0 rows affected (0.00 sec) • ( 2 )在开启 Query Profiler 功能之后, MySQL 就会自动记录所有执行的

Query 的 profile 信息。下面执行 Query ,如示例代码 8-7 所示:• 代码 8-7• root@localhost : test 07:43:18> SELECT status,count(*) • -> FROM test_profiling GROUP BY status; • +---------------- +---------- + • | status | count(*) | • +---------------- +---------- + • | st_xxx1 | 27 |

• | st_xxx2 | 6666 | • | st_xxx3 | 292887 | • | st_xxx4 | 15 | • +---------------- +---------- + • 5 rows in set (1.11 sec)

Page 46: Mysql 培训-优化篇

Profiling 的使用• 通过执行 “ SHOW PROFILE” 命令获取当前系统中保存的多个

Query 的 profile 的概要信息,如示例代码 8-8 所示:代码 8-8

• root@localhost : test 07:47:35> show profiles; • +----------+------------+--------------------------------------

------------------+ • | Query_ID | Duration |Query

| • +----------+------------+---------------------------------

-----------------------+ • | 1 | 0.00183100 |show databases

| • | 2 | 0.00007000 |SELECT DATABASE()

| • | 3 | 0.00099300 |desc test

| • | 4 | 0.00048800 |show tables

| • | 5 | 0.00430400 |desc test_profiling

| • | 6 | 1.90115800 |SELECT status,count(*) FROM

test_profiling GROUP BY status | • +----------+------------+--------------------------------------------------------+ • 3 rows in set (0.00 sec)

Page 47: Mysql 培训-优化篇

Profiling 的使用• ( 4 )针对单个 Query 获取详细的 profile 信息。• 在获取概要信息之后,就可以根据概要信息中的 Query_ID 来获取某个 Query 在执行过程中详细的

profile 信息了,具体操作如示例代码 8-9 所示:• root@localhost : test 07:49:24> show profile cpu, block io for query 6; • +--------------------- +---------- +---------- +-----------

+----------- +------------ + • | Status |Duration |CPU_user |CPU_system

|Block_ops_in|Block_ops_out |

• +--------------------- +---------- +---------- +----------- +----------- +------------ +

• | starting | 0.000349 | 0.000000 | 0.000000 | 0 | 0 |

• | Opening tables | 0.000012 | 0.000000 | 0.000000 | 0 | 0 |

• | System lock | 0.000004 | 0.000000 | 0.000000 | 0 | 0 |

• | Table lock | 0.000006 | 0.000000 | 0.000000 | 0 | 0 |

• | init | 0.000023 | 0.000000 | 0.000000 | 0 | 0 |

• | optimizing | 0.000002 | 0.000000 | 0.000000 | 0 | 0 |

• | statistics | 0.000007 | 0.000000 | 0.000000 | 0 | 0 |

• | preparing | 0.000007 | 0.000000 | 0.000000 | 0 | 0 |

• | Creating tmp table | 0.000035 | 0.000999 | 0.000000

• | 0 | 0 | • | executing | 0.000002 | 0.000000 | 0.000000

| 0 | 0 | • | Copying to tmp table | 1.900619 | 1.030844 | 0.197970

| 347 | 347 | • | Sorting result | 0.000027 | 0.000000 | 0.000000

| 0 | 0 | • | Sending data | 0.000017 | 0.000000 | 0.000000

| 0 | 0 | • | end | 0.000002 | 0.000000 | 0.000000

| 0 | 0 | • | removing tmp table | 0.000007 | 0.000000 | 0.000000

| 0 | 0 | • | end | 0.000002 | 0.000000 | 0.000000

| 0 | 0 | • | query end | 0.000003 | 0.000000 | 0.000000

| 0 | 0 | • | freeing items | 0.000029 | 0.000000 | 0.000000 • | 0 | 0 |

• | logging slow query | 0.000001 | 0.000000 | 0.000000 | 0 | 0 |

• | logging slow query | 0.000002 | 0.000000 | 0.000000 | 0 | 0 |

• | cleaning up | 0.000002 | 0.000000 | 0.000000 • | 0 | 0 |

Page 48: Mysql 培训-优化篇

MYSQL 的索引

• 1. B-Tree 索引 • B-Tree 索引是 MySQL 数据库中使用最为频繁的索引类型,除了

Archive 存储引擎之外的其他所有的存储引擎都支持 B-Tree 索引。不仅在 MySQL 中是如此,在其他的很多数据库管理系统中 B-Tree 索引也同样是作为最主要的索引类型的,这主要是因为 B-Tree 索引的存储结构在数据库的数据检索中有着非常优异的表现。

• 一般来说, MySQL 中的 B-Tree 索引的物理文件大多是以 Balance Tree 的结构来存储的,也就是所有实际需要的数据都存放于 Tree 的 Leaf Node ,而且到任何一个 Leaf Node 的最短路径的长度都是完全相同的,所以把它称之为 B-Tree 索引。不过,可能各种数据库(或 MySQL 的各种存储引擎)在存放自己的 B-Tree 索引的时候会对存储结构稍作改造。如 InnoDB 存储引擎的 B-Tree 索引使用的存储结构实际上是 B+Tree ,在 B-Tree 数据结构的基础上做了很小的改造,在每一个 Leaf Node 上面除了存放索引键的相关信息之外,还存储了指向与该 Leaf Node 相邻的后一个 Leaf Node 的指针信息,这主要是为了加快检索多个相邻 Leaf Node 的效率。

Page 49: Mysql 培训-优化篇

MYSQL 的索引

• Hash 索引 • Hash 索引在 MySQL 中使用的并不是很多,目前主要是

Memory 和 NDB Cluster 存储引擎使用。所谓 Hash 索引,实际上就是通过一定的 Hash 算法,将须要索引的键值进行 Hash 运算,然后将得到的 Hash 值存入一个 Hash 表中。每次须要检索的时候,都会将检索条件进行相同算法的 Hash 运算,再和Hash 表中的 Hash 值进行比较,并得出相应的信息。

Page 50: Mysql 培训-优化篇

HASH 与 B_TREE 的比较及局限性• 既然 Hash 索引的效率要比 B-Tree 高很多,为什么大家不都用 Hash 索引而还要

使用 B-Tree 索引呢?任何事物都是有两面性的, Hash 索引也一样,虽然 Hash 索引效率高,但是 Hash 索引本身由于其特殊性也带来了很多限制和弊端,主要有以下这些。

• ( 1 ) Hash 索引仅仅能满足 "=","IN" 和 "<=>" 查询,不能使用范围查询。• 由于 Hash 索引比较的是进行 Hash 运算之后的 Hash 值,所以它只能用于等值的

过滤,不能用于基于范围的过滤,因为经过相应的 Hash 算法处理之后的 Hash 值的大小关系,并不能保证和 Hash 运算前完全一样。

• ( 2 ) Hash 索引无法被用来避免数据的排序操作。• 由于 Hash 索引中存放的是经过 Hash 计算之后的 Hash 值,而且 Hash 值的大小

关系并不一定和 Hash 运算前的键值完全一样,所以数据库无法利用索引的数据来避免任何排序运算;

• ( 3 ) Hash 索引不能利用部分索引键查询。• 对于组合索引, Hash 索引在计算 Hash 值的时候是组合索引键合并后再一起计算

Hash 值,而不是单独计算 Hash 值,所以通过组合索引的前面一个或几个索引键进行查询的时候, Hash 索引也无法被利用。

• ( 4 ) Hash 索引在任何时候都不能避免表扫描。• 前面已经知道, Hash 索引是将索引键通过 Hash 运算之后,将 Hash 运算结果的

Hash 值和所对应的行指针信息存放于一个 Hash 表中,由于不同索引键存在相同 Hash 值,所以即使取满足某个 Hash 键值的数据的记录条数,也无法从 Hash 索引中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结果

• ( 5 ) Hash 索引遇到大量 Hash 值相等的情况后性能并不一定就会比 B-Tree索引高。

Page 51: Mysql 培训-优化篇

MYSQL 的索引• Full-text 索引 • Full-text 索引也就是全文索引,目前在 MySQL 中仅有 MyISAM 存储引

擎支持它,但并不是所有的数据类型都支持。目前,仅有CHAR 、 VARCHAR 和 TEXT 这三种数据类型的列可以建 Full-text 索引。

• 一般来说, Fulltext 索引主要用来替代效率低下的 LIKE '%***%' 操作。实际上, Full-text 索引并不是只能简单地替代传统的全模糊 LIKE 操作,它能通过多字段组合的 Full-text 索引一次全模糊匹配多个字段。

• Full-text 索引和普通的 B-Tree 索引实现区别较大,虽然它同样是以 B-Tree 形式来存放索引数据的,但是它并不是通过字段内容的完整匹配,而是通过特定的算法,将字段数据进行分割后再进行的索引。一般来说 MySQL 系统会按照最小 4 个字节来分隔。在整个 Full-text 索引中,存储内容被分为两部分,一部分是分隔前的索引字符串数据集合,另一部分是分隔后的词(或者词组)索引信息。所以, Full-text 索引中,真正在 B-Tree 索引结构的叶节点中的并不是表中的原始数据,而是分词之后的索引数据。在 B-Tree 索引的节点信息中,存放了各个分隔后的词信息,以及指向包含该词的分隔前字符串信息在索引数据集合中的位置信息。

• Full-text 索引不仅能实现模糊匹配查找,还能实现基于自然语言的匹配度查找。当然,这个匹配度到底有多准确就需要读者自行验证了。 Full-text 通过一些特定的语法信息,针对自然语言做了各种相应规则的匹配,最后给出了非负的匹配值

• 此外,有一点须要注意, MySQL 目前的 Full-text 索引在中文支持方面还不太好,须要借助第三方的补丁或插件来完成,且 Full-text 的创建所消耗的资源也比较大,所以在应用于实际生产环境之前还是尽量做好评估。

Page 52: Mysql 培训-优化篇

MYSQL 的索引

• R-Tree 索引• R-Tree 索引可能是在其他数据库中很少见的一种索引类型,主要用来解决空间数据

检索的问题。• 在 MySQL 中,支持一种用来存放空间信息的数据类型 GEOMETRY ,且基于

OpenGIS 规范。在 MySQL 5.0.16 之前的版本中,仅 MyISAM 存储引擎支持该数据类型,但是从 MySQL 5.0.16版本开始, BDB 、 InnoDB 、 NDBCluster 和 Archive 存储引擎也开始支持该数据类型。当然,虽然多种存储引擎都开始支持 GEOMETRY 数据类型,但是仅仅之后的 MyISAM 存储引擎支持 R-Tree 索引。

• 在 MySQL 中采用了具有二次分裂特性的 R-Tree 来索引空间数据信息,然后通过几何对象( MRB )信息来创建索引。

• 虽然只有 MyISAM 存储引擎支持空间索引( R-Tree Index ),但是如果是精确的等值匹配,创建在空间数据上面的 B-Tree 索引同样可以起到优化检索的效果,空间索引的主要优势在于使用范围查找的时候,可以利用 R-Tree 索引,而 B-Tree 索引就无能为力了

Page 53: Mysql 培训-优化篇

如何判定是否须要创建索引

• 1. 较频繁的作为查询条件的字段应该创建索引

• 2. 唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件

• 3. 更新非常频繁的字段不适合创建索引• 4. 不会出现在 WHERE 子句中的字段

不该创建索引

Page 54: Mysql 培训-优化篇

单列索引还是复合索引• 对于这种问题,很难有一个绝对的定论,须要从多方面来分析考虑,平衡两种方案各自的优劣,然后选择一种最佳的方案。而组合索引中因为有多个字段存在,理论上被更新的可能性肯定比单键索引要大很多,这样带来的附加成本也 就比单键索引要高。但是,当WHERE 子句中的查询条件含有多个字段时,通过这多个字段共同组成的组合索引的查询效率肯定比只用过滤条件中的某一个字段创建的索引要高。因为通过单键索引过滤的数据并不完整,和组合索引相比,存储引擎须要访问更多的记录数,自然就会访问更多的数据量,也就是说需要更高的 IO 成本。

• 可能有朋友会说,那可以创建多个单键索引啊。确实可以将 WHERE 子句中的每一个字段都创建一个单键索引。但是这样真的有效吗?在这样的情况下, MySQL Query Optimizer 大多数时候都只会选择其中的一个索引,然后放弃其他的索引。即使他选择了同时利用两个或更多的索引通过 INDEX_MERGE 来优化查询,所收到的效果可能并不会比选择其中某一个单键索引更高效。因为如果选择通过 INDEX_MERGE 来优化查询,就须要访问多个索引,同时还要将几个索引进行 merge 操作,这带来的成本可能反而会比选择其中一个最有效的索引更高。

• 在一般的应用场景中,只要不是其中某个过滤字段在大多数场景下能过滤 90% 以上的数据,而其他的过滤字段会频繁的更新,一般更倾向于创建组合索引, 尤其是在并发量较高的场景下。因为当并发量较高的时候,即使只为每个 Query节省了很少的 IO 消耗,但因为执行量非常大,所节省的资源总量仍然是非常可观的。

• 当然,创建组合索引并不是说就须要将查询条件中的所有字段都放在一个索引中,还应该尽量让一个索引被多个 Query 语句利用,尽量减少同一个表上的索引数量,减少因为数据更新带来的索引更新成本,同时还可以减少因为索引所消耗的存储空间。

Page 55: Mysql 培训-优化篇

MYSQL 的索引限制• 在使用索引的同时,还应该了解 MySQL 中索引存在的限制,以便在索引应用中尽可能地避开限制所带来的问题。下面列出了目前 MySQL 中与索引使用相关的限制。

• ( 1 ) MyISAM 存储引擎索引键长度的总和不能超过 1000字节;

• ( 2 ) BLOB 和 TEXT 类型的列只能创建前缀索引;• ( 3 ) MySQL 目前不支持函数索引;• ( 4 )使用不等于( != 或者 <> )的时候, MySQL 无法使用

索引;• ( 5 )过滤字段使用了函数运算(如 abs ( column ))

后, MySQL 无法使用索引;• ( 6 ) Join 语句中 Join 条件字段类型不一致的时候, MySQL

无法使用索引;• ( 7 )使用 LIKE 操作的时候如果条件以通配符开始

(如 '%abc...' )时, MySQL 无法使用索引;• ( 8 )使用非等值查询的时候, MySQL 无法使用 Hash 索引

Page 56: Mysql 培训-优化篇

6.ORDER BY 、 GROUP BY 和DISTINCT 优化

• 1.Order by 的实现与优化:• 2.Group by 的实现与优化• 3.Distinct 的实现与优化

Page 57: Mysql 培训-优化篇

Order by 的实现与优化:•

在 MySQL 中, Order by 的实现有两种,一是通过有序的索引直接取得有序的数据直接返回客户端;二是通过 MySQL 的排序算法进行排序后在将排序后的数据返回到客户端。

•       经实践证明利用索引实现数据排序的方法是 MySQL 中实现结果集排序的最佳做法,可以完全避免因为排序计算所带来的资源消耗。所以,在我们优化 Query 语句中的 ORDER BY 的时候,尽可能利用已有的索引来避免实际的排序计算,可以很大幅度的提升 ORDER BY 操作的性能 。

•     对于采取排序算法 ,MySQL 有两种方式能够实现• 1. 取出满足过滤条件的用于排序条件的字段以及可以直接定位到行数据的行

指针信息,在 SortBuffer 中进行实际的排序操作,然后利用排好序之后的数据根据行指针信息返回表中取得客户端请求的其他字段的数据,再返回给客户端;2. 根据过滤条件一次取出排序字段以及客户端请求的所有其他字段的数据,并将不需要排序的字段存放在一块内存区域中,然后在 Sort Buffer 中将排序字段和行指针信息进行排序,最后再利用排序后的行指针与存放在内存区域中和其他字段一起的行指针信息进行匹配合并结果集,再按照顺序返回给客户端。

•        这两种方式略有不同,能够看出来第二种方式优于第一种方式,第二种方式是典型的以内存为代价换取效率的提升。

Page 58: Mysql 培训-优化篇

Group by 的实现与优化• 由于 GROUP BY 实际上也同样需要进行排序操作,

而且与 ORDER BY 相比, GROUP BY 主要只是多了排序之后的分组操作。当然,如果在分组的时候还使用了其他的一些聚合函数,那么还需要一些聚合函数的计算。所以,在 GROUP BY 的实现过程中,与ORDER BY 一样也可以利用到索引。

•           在 MySQL 中, GROUP BY 的实现同样有多种(三种)方式,其中有两种方式会利用现有的索引信息来完成 GROUP BY ,另外一种为完全无法使用索引的场景下使用。下面我们分别针对这三种实现方式做一个分析。

• 1. 使用松散( Loose )索引扫描实现 GROUP BY

• 2. 使用紧凑( Tight )索引扫描实现 GROUP BY• 3. 使用临时表实现 GROUP BY

Page 59: Mysql 培训-优化篇

Distinct 的实现与优化

• Distinct 实际上和 Group by 的操作非常相似,只不过是在 Group by 之后的每组中只取出一条记录而已。所以, Distinct 的实现和 Group by 的实现也基本差不多,没有太大的区别。同样可以通过松散索引扫描或者是紧凑索引扫描来实现,当然,在无法仅仅使用索引即能完成Distinct 的时候, MySQL 只能通过临时表来完成。但是,和 Group by 有一点差别的是, Distinct 并不需要进行排序。也就是说,在仅仅只是 Distinct 操作的Query 如果无法仅仅利用索引完成操作的时候, MySQL 会利用临时表来做一次数据的“缓”,但是不会对临时表中的数据进行 filesort 操作。当然,如果我们在进行 Distinct 的时候还使用了 Group by并进行了分组,并使用了类似于 max 之类的聚合函数操作,就无法避免 filesort 了

Page 60: Mysql 培训-优化篇

MYSQL 的其他常用优化

• 1.网络连接与连接线程• 2.TABLE CACHE 的相关优化• 3.SORT BUFFER 和 JOIN BUFFER

Page 61: Mysql 培训-优化篇

1.网络连接与连接线程

• 查看连接线程相关的系统变量的设置值• MYSQL>show variables like ‘thread%’

• 系统被连接的次数及当前系统中连接线程的状态值

• Mysql>show status like ‘connections’

• mysql>show status like ‘%thread’

Page 62: Mysql 培训-优化篇

2.TABLE CACHE 的相关优化

• 查看表 CACHE 的设置和当前系统中的使用状况

• Mysql>show variables like ‘table_cache’

• Mysql>show status like ‘open_tables’

Page 63: Mysql 培训-优化篇

3.SORT BUFFER 和 JOIN BUFFER

• Mysql>show variables like ‘%buffer%’

Page 64: Mysql 培训-优化篇

3.SORT BUFFER 和 JOIN BUFFER• 用 set SESSION命令设置会话级变量的新值• mysql> set SESSION sort_buffer_size=7000000;

Query OK, 0 rows affected (0.00 sec)• --修改会话级变量对当前会话来说立刻生效

Page 65: Mysql 培训-优化篇

QUERY CACHE 优化

• 定义• 顾名思义, MySQL Query Cache 就是用

来缓存和 Query 相关的数据的。具体来说, Query Cache 缓存了我们客户端提交给 MySQL 的 SELECT 语句以及该语句的结果集。大概来讲,就是将 SELECT 语句和语句的结果做了一个 HASH 映射关系然后保存在一定的内存区域中。

Page 66: Mysql 培训-优化篇

QUERY CACHE 优化• 在大部分的 MySQL 分发版本中, Query Cache 功能默认都是打开的,我们可以

通过调整 MySQL Server 的参数选项打开该功能。主要由以下 5 个参数构成:• query_cache_limit :允许 Cache 的单条 Query 结果集的最大容量,默认

是 1MB ,超过此参数设置的 Query 结果集将不会被 Cache • query_cache_min_res_unit :设置 Query Cache 中每次分配内存的最小空间大

小,也就是每个 Query 的 Cache 最小占用的内存空间大小 • query_cache_size :设置 Query Cache 所使用的内存大小,默认值为 0 ,

大小必须是 1024 的整数倍,如果不是整数倍, MySQL 会自动调整降低最小量以达到 1024 的倍数

• query_cache_type :控制 Query Cache 功能的开关,可以设置为 0(OFF),1(ON)和 2(DEMAND) 三种,意义分别如下: – 0(OFF) :关闭 Query Cache 功能,任何情况下都不会使用 Query

Cache – 1(ON) :开启 Query Cache 功能,但是当 SELECT 语句中使用的

SQL_NO_CACHE 提示后,将不使用 Query Cache

– 2(DEMAND) :开启 Query Cache 功能,但是只有当 SELECT 语句中使用了 SQL_CACHE 提示后,才使用 Query Cache

• query_cache_wlock_invalidate :控制当有写锁定发生在表上的时刻是否先失效该表相关的 Query Cache ,如果设置为 1(TRUE) ,则在写锁定的同时将失效该表相关的所有 Query Cache ,如果设置为 0(FALSE)则在锁定时刻仍然允许读取该表相关的 Query Cache 。

Page 67: Mysql 培训-优化篇

QUERY CACHE 优化

• Query Cache 如何处理子查询的?这是我遇到的最为常见的一个问题。其实 Query Cache 是以客户端请求提交的 Query 为对象来处理的,只要客户端请求的是一个 Query ,无论这个 Query 是一个简单的单表查询还是多表 Join ,亦或者是带有子查询的复杂 SQL ,都被当作成一个 Query ,不会被分拆成多个 Query 来进行 Cache 。所以,存在子查询的复杂 Query 也只会产生一个 Cache 对象,子查询不会产生单独的 Cache内容。 UNION[ALL] 类型的语句也同样如此。

• Query Cache 是以 block 的方式存储的数据块吗?不是, Query Cache 中缓存的内容仅仅只包含该 Query 所需要的结果数据,是结果集。当然,并不仅仅只是结果数据,还包含与该结果相关的其他信息,比如产生该 Cache 的客户端连接的字符集,数据的字符集,客户端连接的 Default Database 等。

• Query Cache 为什么效率会非常高,即使所有数据都可以 Cache 进内存的情况下,有些时候也不如使用 Query Cache 的效率高?Query Cache 的查找,是在 MySQL 接受到客户端请求后在对 Query 进行权限验证之后, SQL 解析之前。也就是说,当 MySQL 接受到客户端的 SQL 后,仅仅只需要对其进行相应的权限验证后就会通过 Query Cache 来查找结果,甚至都不需要经过 Optimizer 模块进行执行计划的分析优化,更不许要发生任何存储引擎的交互,减少了大量的磁盘 IO 和 CPU 运算,所以效率非常高。

Page 68: Mysql 培训-优化篇

QUERY CACHE 优化• 客户端提交的 SQL 语句大小写对 Query Cache 有影响吗? 有影响

有,由于 Query Cache 在内存中是以 HASH 结构来进行映射, HASH 算法基础就是组成 SQL 语句的字符,所以必须要整个 SQL 语句在字符级别完全一致,才能在 Query Cache 中命中,即使多一个空格也不行。

• 一个 SQL 语句在 Query Cache 中的内容,在什么情况下会失效?为了保证 Query Cache 中的内容与是实际数据绝对一致,当表中的数据有任何变化,。包括新增,修改,删除等,都会使所有引用到该表的 SQL 的 Query Cache 失效

• 为什么我的系统在开启了 Query Cache 之后整体性能反而下降了?当开启了 Query Cache 之后,尤其是当我们的 query_cache_type 参数设置为 1 以后, MySQL 会对每个 SELECT 语句都进行 Query Cache 查找,查找操作虽然比较简单,但仍然也是要消耗一些 CPU 运算资源的。而由于 Query Cache 的失效机制的特性,可能由于表上的数据变化比较频繁,大量的 Query Cache 频繁的被失效,所以 Query Cache 的命中率就可能比较低下。所以有些场景下, Query Cache 不仅不能提高效率,反而可能造成负面影响。

Page 69: Mysql 培训-优化篇

QUERY CACHE 优化• 如何确认一个系统的 Query Cache 的运行是否健康,命中率如何,设置量是否足够?MySQL 提供了一系列的 Global Status 来记录 Query Cache 的当前状态,具体如下:

• Qcache_free_blocks :目前还处于空闲状态的 Query Cache 中内存 Block 数目

• Qcache_free_memory :目前还处于空闲状态的 Query Cache 内存总量 • Qcache_hits : Query Cache 命中次数 • Qcache_inserts :向 Query Cache 中插入新的 Query Cache 的次数,也

就是没有命中的次数 • Qcache_lowmem_prunes :当 Query Cache 内存容量不够,需要从中删除老的 Query Cache 以给新的 Cache 对象使用的次数

• Qcache_not_cached :没有被 Cache 的 SQL 数,包括无法被 Cache 的 SQL 以及由于 query_cache_type 设置的不会被 Cache 的 SQL

• Qcache_queries_in_cache :目前在 Query Cache 中的 SQL 数量 • Qcache_total_blocks : Query Cache 中总的 Block 数量 • 可以根据这几个状态计算出 Cache 命中率,计算出 Query Cache 大小设置是否足够,总的来说,我个人不建议将 Query Cache 的大小设置超过 256MB ,这也是业界比较常用的做法。

Page 70: Mysql 培训-优化篇

QUERY CACHE 优化

• MySQL Cluster 是否可以使用 Query Cache ?可以使用其实在我们的生产环境中也没有使用 MySQL Cluster ,所以我也没有在 MySQL Cluster 环境中使用 Query Cache 的实际经验,只是 MySQL 文档中说明确实可以在 MySQL Cluster 中使用 Query Cache 。从 MySQL Cluster 的原理来分析,也觉得应该可以使用,毕竟 SQL 节点和数据节点比较独立,各司其职,只是 Cache 的失效机制会要稍微复杂一点。

Page 71: Mysql 培训-优化篇

MySQL 中优化 sql 语句查询常用的 30 种方法

• 1. 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。 2. 应尽量避免在 where 子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描。 3. 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num is null 可以在 num 上设置默认值 0 ,确保表中 num 列没有 null 值,然后这样查询: select id from t where num=0 4. 应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,如: select id from t where num=10 or num=20 可以这样查询: select id from t where num=10 union all select id from t where num=20

Page 72: Mysql 培训-优化篇

MySQL 中优化 sql 语句查询常用的 30 种方法

• 5. 下面的查询也将导致全表扫描: select id from t where name like '%abc%' 若要提高效率,可以考虑全文检索。 6.in 和 not in 也要慎用,否则会导致全表扫描,如: select id from t where num in(1,2,3) 对于连续的数值,能用 between 就不要用 in 了: select id from t where num between 1 and 3 7. 如果在 where 子句中使用参数,也会导致全表扫描。因为 SQL 只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描: select id from t where num=@num 可以改为强制查询使用索引: select id from t with(index( 索引名 )) where num=@num 8. 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如: select id from t where num/2=100 应改为 : select id from t where num=100*2

Page 73: Mysql 培训-优化篇

MySQL 中优化 sql 语句查询常用的 30 种方法• 9. 应尽量避免在 where 子句中对字段进行函数操作,这将导致引擎放弃使用索

引而进行全表扫描。如: select id from t where substring(name,1,3)='abc'--name 以 abc 开头的 id select id from t where datediff(day,createdate,'2005-11-30')=0--'2005-11-30' 生成的 id 应改为 : select id from t where name like 'abc%' select id from t where createdate>='2005-11-30' and createdate<'2005-12-1' 10. 不要在 where 子句中的“ =” 左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。 11. 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。 12. 不要写一些没有意义的查询,如需要生成一个空表结构: select col1,col2 into #t from t where 1=0 这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样: create table #t(...) 13. 很多时候用 exists 代替 in 是一个好的选择: select num from a where num in(select num from b) 用下面的语句替换: select num from a where exists(select 1 from b where num=a.num)

Page 74: Mysql 培训-优化篇

MySQL 中优化 sql 语句查询常用的 30 种方法

• 14. 并不是所有索引对查询都有效, SQL 是根据表中数据来进行查询优化的,当索引列有大量数据重复时, SQL 查询可能不会去利用索引,如一表中有字段 sex , male 、 female 几乎各一半,那么即使在 sex 上建了索引也对查询效率起不了作用。 15. 索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过 6 个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。 16. 应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。 17. 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。 18. 尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

Page 75: Mysql 培训-优化篇

MySQL 中优化 sql 语句查询常用的 30 种方法

• 19. 任何地方都不要使用 select * from t ,用具体的字段列表代替“ *” ,不要返回用不到的任何字段。 20. 尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。 21. 避免频繁创建和删除临时表,以减少系统表资源的消耗。 22.临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。 23. 在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table ,避免造成大量 log ,以提高速度;如果数据量不大,为了缓和系统表的资源,应先 create table ,然后 insert 。 24. 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。 25. 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过 1万行,那么就应该考虑改写。 26./* 使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。 */

Page 76: Mysql 培训-优化篇

MySQL 中优化 sql 语句查询常用的 30 种方法

• 27. 与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。 28. 在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。 29. 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。 30. 尽量避免大事务操作,提高系统并发能力。

Page 77: Mysql 培训-优化篇

Query 优化基本原则

• 永远用小结果集驱动大的结果集; • 尽可能在索引中完成排序; • 只取出自己需要的 Columns ; • 仅仅使用最有效的过滤条件; • 尽可能避免复杂的 Join 和子查询;能不

使用子查询就不使用子查询 • 多使用 profile

Page 78: Mysql 培训-优化篇

Mysql存储引擎

Page 79: Mysql 培训-优化篇

MYSQL存储引擎

Page 80: Mysql 培训-优化篇

5. 常用存储引擎优化

• 1.MYISAM存储引擎优化• 2.INNODB存储引擎优化

Page 81: Mysql 培训-优化篇

5.1 MYISAM存储引擎优化

• 1. 索引缓存优化• 2. 多 KEY CACHE 的使用• 3.KEY CACHE 的互斥( MUTEX)问题• 4.KEY CACHE 预加载• 5.NULL 值对统计信息的影响• 6. 表读取缓存优化• 7. 并发优化• 8. 其他可以优化的地方

Page 82: Mysql 培训-优化篇

补充: Mysql 数据库命名规范

• 主键、外键、索引、视图、函数、过程、触发器等的命名规范• 此要求必须严格执行

• 主键:使用 pk_前缀 ,前缀名称一般不超过 5 字• 外键:使用 fk_前缀 ,前缀名称一般不超过 5 字• 索引:使用 idx_前缀 ,前缀名称一般不超过 5 字• 视图:使用 v_前缀 ,前缀名称一般不超过 5 字• 函数:使用 f_前缀 ,前缀名称一般不超过 5 字• 过程:使用 p_前缀 ,前缀名称一般不超过 5 字• 触发器:使用 t_前缀 ,前缀名称一般不超过 5 字

Page 83: Mysql 培训-优化篇

两天性能测试中发现的问题

• 1. 在数据库中进行大排序• 2. 系统中索引的使用率较低• 3. 多表连接性能低• 4. 数据库对象命名不规范• 5. 数据库设计不遵循 3NF

• 6. 产品中存在锁争用的问题• 7. 产品中存在循环次数不正确的地方

Page 84: Mysql 培训-优化篇

MYSQL 中的大排序• 在公告功能测试中,后台出现了锁等待 ,造成的原因是对 40万的数据进行

了排序• Select * from onlinemessage order by sendDate;

Page 85: Mysql 培训-优化篇

系统中索引的使用率较低

索引使用情况:下图说明我们的 SQL 中走全表扫描的百分比,大于 20%警告,大于 40% 比较严重,而我们达到 66.5%

Page 86: Mysql 培训-优化篇

没有使用索引的例子

• select id,businesstype,docid,state,userid,createtime,typeflag from fulltextsearch_info where ?=? and businesstype = ? and docid = ?;

Page 87: Mysql 培训-优化篇

多表连接

Page 88: Mysql 培训-优化篇

多表连接• select document.ID, document.SEC_ID, document.IMP_ID, document.PER_ID,

document.title, document.CREATEDATE, document.STATE, document.TIMENESS, document.SORT, document.word_no, document.sendunit, document.urgentlevel, document.word_in_no, document.f?,document.f?, document.sum_i?, document.sum_i?,document.sum_i?, document.sum_i?, document.templetid, flownode.ID, flownode.DISCRIPTION, flownode.URGENT,flownode_parent.WORKDATE, flownode_member.ID, flownode_member.ENTITY, flownode_member.WORKFLAG, flownode_member.DELFLAG, flownode_member.TRACKFLAG, flownode_member.URGENT, assess_member.ID,assess_member.prestartdate,assess_member.preenddate,assess_member.relstartdate, assess_member.relenddate, assess_date_type.typename from flownode_member left join flownode on (flownode.id=flownode_member.flo_id) left join document on (document.id=flownode_member.doc_id) left join flownode flownode_parent on (flownode_parent.id=flownode.parent_ID ) left join assess_node on (assess_node.flownode_id=flownode.ID) left join assess_date_type on (assess_date_type.id=assess_node.datatype_id) left join assess_member on (assess_member.flownodemember_id=flownode_member.id ) where flownode_member.ENTITY in(?,-?) and flownode_member.delflag=? and flownode_member.workFlag in (?,?,?) and (flownode_member.orgcode=? or flownode_member.orgcode=? or flownode_member.orgcode=?) and ( (flownode_parent.workFlag=? or flownode.parent_ID=? or (flownode_parent.workFlag=? and (flownode.isreturn=? or flownode_parent.isreturn=?) ) ) or (flownode.workFlag=? and flownode_member.workFlag=?) ) and document.SORT=? and (document.state in (?,?) and sum_i? =? ) and flownode.chooseflag<>? and flownode_member.isdelperson = ? order by flownode_parent.WORKDATE desc LIMIT ?,?;

Page 89: Mysql 培训-优化篇

多表连接

Page 90: Mysql 培训-优化篇

多表连接• assess_date_type.typename• FROM•

flownode_member --- 第一个表• LEFT JOIN flownode ON( -- 第二个表•

flownode.id = flownode_member.flo_id

• )• LEFT JOIN document ON( -- 第三个表•

document.id = flownode_member.doc_id

• )• LEFT JOIN flownode flownode_parent ON( -- 第四个表

•flownode_parent.id = flownode.parent_ID

• )• LEFT JOIN assess_node ON(------ 第五个表•

assess_node.flownode_id = flownode.ID

• )• LEFT JOIN assess_date_type ON((------ 第六个表•

assess_date_type.id = assess_node.datatype_id

• )• LEFT JOIN assess_member ON((------ 第七个表•

assess_member.flownodemember_id = flownode_member.id

• )• WHERE

Page 91: Mysql 培训-优化篇

美 成都丽

感 的 注谢您 关haoxp20110515