SlideShare una empresa de Scribd logo
1 de 91
MYSQL 培训 - 优化篇
HXP_20110515
化篇优
化篇优
1
2
3
4
MYSQL 架构组成
MYISAM 表锁优化建
议
MYSQL 数据库 QUERY 的优
化
QUERY CACHE 优化
6
常用存储引擎优化
MYSQL 可扩展设计的基本原
则
5
1. MYSQL 架构组成
• 1.MYSQL 物理文件组成
• 1.> 日志文件
• 2.> 数据文件
• 3.>REPLICATION 相关文件
• 4.> 其他文件
• 2.MYSQL SERVER 系统架构
• 1.> 逻辑模块组成
• 2.> 各模块工作配合
日志文件
• 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
数据文件
• 1.FRM 文件
• 与表相关的元数据信息都存放在 .FRM 文件中,包括表结构信息。不论是什么的存储引擎都
会有一个表名 .FRM 的文件
• 2.MYD 文件
• 是 MYISAM 存储引擎专用的,存放 MYISM 表的数据。
• 3.MYI 文件
• 也是 MYISAM 存储引擎专用的,主要存放 MYISAM 表的索引相关信息。对于 MYISAM 存储来
说,可以被 CACHE 的内容主要是来源于 .MYI 的文件中。
• 4.IBD 文件和 IBDATA 文件
• 这两种都是存放 INNODB 数据的文件(包括索引),因为 INNODB 的数据存储方式能够通过
配置来决定是使用共享表空间存放存储数据,还是用独享表空间来存放数据。
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 线程及某些管理操作随时能
够获得当前复制的相关信息。
其他文件
• 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
MYSQL SERVER 系统架构
•1. 逻辑模块组成
•2. 各模块工作配合
逻辑模块组成
• 在 Mysql 中,我们看作两层架构,即
SQL Layer ( SQL 处理层)和 Storage
Engine Layer (存储引擎层)。在
MySQL 处理底层数据之前,所有的
操作都是在 SQL Layer 层完成的,如
:权限判断、 SQL 解析、查询优化、
cache 处理等。经过这一层,再交由
Storage Engine Layer 层处理。所以我
们可以将 MySQL 看作是右图的结构。
• 但是, 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 。
•
逻辑模块组成
• 5. 用户模块
• 此模块主要实现权限控制和授权管理。如判断某个用户能不能进入某个库内进
行相关操作。
• 6. 访问控制模块
• 此模块与用户模块主要的区别是它限定了用户的访问控制权限,如
select 、 update 等。这两个模块共同组成了整个系统的权限安全管理功能。
• 7. 连接管理、连接线程和线程管
理
• 连接管理模块主要负责监听 MySQL Server 的各种请求,接收和转发连接请求
到线程管理模块。每一个连接上 MySQL 的客户端请求都会被分配或创建一个独
享的线程为其服务。而连接线程的主要工作就是负责 MySQL Server 和客户端的
连接通信,接受客户端请求、传递 Server 端结果信息等。线程管理模块则负责管
理和维护这些线程,包括线程创建、线程的 cache 等。
• 8. Query 解析和转发模块
• 此模块主要负责将接收到的 SQL 进行语义和语法的分析,然后按照分类有针对
逻辑模块组成
• 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. 表维护模块
• 此模块主要负责表的状态检查、错误修复以及优化和分析。
逻辑模块组成
• 13. 表管理器
• 此模块与表维护模块的功能完全不同。上次我们提到的 .frm 文件就是由这个模块进行
管理和维护的。它还负责 table 级别的锁管理。
• 14. 系统状态管理模块
• 此模块主要负责当客户端请求系统状态时,将各种状态信息返回给客户。如 show
status 、 show variables 等。
• 15. 日志记录模块
• 此模块主要负责整个系统的逻辑层的日志的记录,包括 Error Log 、 Binary Log 、
Slow Query Log 等。
逻辑模块组成
• 16. 复制模块
• 此模块主要负责主从配置中的数据同步,故又可分为 Master 模块和 Slave 模块。
• Master 模块主要负责在 Slave 端读取 Binary Log 以及与 Master 端的 I/O 线程交互等工
作。
• Slave 模块主要负责从 Master 端请求和接受 Binary Log ,并写入本地的 Relay Log 中
的 I/O 线程,然后将其转化为 SQL 应用于 Slave 端。
• 17. 存储引擎接口模块
• 目前所有的数据库产品中,基本上只有 MySQL 实现了底层数据存储引擎的插件式
管理。这个模块实际上是一个抽象类,将各种数据处理高度抽象化。
各
模
块
之
间
的
相
互
配
合
MySQL 数据库锁定机制
• MySQL 使用了三种锁定机制:行级锁定、页级锁定和表级锁定
• 一、行级锁定
• 特点:锁定对象的颗粒度很小,也是目前各大数据库管理软件中所实现的锁定颗粒最小的
。发生锁定资源的概率最小,能够给予尽可能大的并发处理能力,提高整体性能。
• 弊端:每次获取锁和释放锁需要做的事情较多,带来的消耗更大,容易发生死锁。
• 用途: MyISAM 、 Memorey 、 CSV 等一些非事务性存储引擎。
• 二、表级锁定
• 特点:表级锁定是 MySQL 各大存储引擎中颗粒度最大的锁定机制,实现逻辑非常简单,
带来的系统负面影响最小。获取和释放锁的速度较快,避免死锁问题。
• 弊端:出现锁定资源争用的概率最高,致使并发度大打折扣。
• 用途: InnoDB 和 NDB Cluster 存储引擎。
• 三、页级锁定
• 特点:锁定颗粒度介于行级锁定与表级锁定之间,并发处理能力也介于二者之间,同
样会发生死锁问题。
• 用途: BerkeleyDB 存储引擎
• 在数据库实现锁定资源的过程中,随着锁定颗粒度越来越小,锁定相同数据量的数据所消耗
的内存数量是越来越多的,实现算法也越来越复杂。不过随着颗粒度的减小,应用程序访问
请求遇到锁等待的可能性也会随之降低,系统整体并发度也会随之提升。
各种锁定机制分析
• 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. 行级锁定
表锁定
• 虽然对于使用者来说表现为两种锁定,但在 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 而产生
表锁定
• 读锁定:
• 一个新的客户端请求在申请获取读锁定资源的时候,需要满足两个条件:
• 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
行级锁定
• 行级锁定不是 MySQL 自己实现的锁定机制,而是由 Innodb 和 NDB Cluster 存储引擎
实现的。而由于这个锁定机制是由各个存储引擎自行实现,所以具体实现算法也有
差别。
• Innodb 的行锁定分为两种:共享锁和排它锁。而为了让行
锁定和表锁定机制共存, Innodb 也使用了意向锁(表级锁定)的概念,于是有了意
向共享锁和意向排它锁。
• 意向锁的意思就是我这个线程在遇到需要资源被锁定的情况下,再附加一个我想要
的锁的意思,来等待资源的释放,再自行添加自己需要的锁
• 注意:当一个请求需要锁定资源的时候
,如果资源已被共享锁锁定,只能添加
共享锁;而如果资源已被排它锁锁定,
则只能等待锁定释放,再添加自己使用
的锁。意向共享锁可以并存,意向排它
锁只能存在一个。
行级锁定
合理利用锁机制优化 MYSQL
• 1.MYISAM 表锁优化建议 _ 重点
• 2.INNODB 行锁优化建议
• 3. 系统锁定争用情况查询 _ 重点
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 。
MYISAM 表锁优化建议 _ 重点
• 3 、合理利用读写优先级
• MYSQL 的表级锁定在默认情况下是写优先级大于读
。所以,如果系统是一个以读为主,而且要优先保证
查询性能的话,可以通过设置系统参数选项
low_priority_updates=1 ,将写的优先级设置为比读
低,即告诉 MYSQL 尽量先处理读请求;如果系统须
要有限保证数据写入的性能的话,则不用设置
low_priority_updates 参数了。并发优化的另外一个
方面还可以通过开启 Key Cache ,用来缓存索引来提
高读取速度,如果这样还不觉得快的话,还可以通过
Query Cache 功能来直接缓存 Query 的结果集。当然
,还可以合理利用第三方案 Cache 软件,如
Memcached ,来缓存数据,提升系统性能。
Innodb 行锁优化
• 缩小锁定范围
• 尽量让所有检索都通过索引来完成
• 合理设计索引,让 Innodb 在索引键上面加锁
时尽可能准确,缩小锁定范围。
• 查询时尽可能减少基于范围的检索过滤,避免
锁定不必要记录
• 尽量控制事务大小,减小锁定资源量与锁定时
间
• 尽量使用较低级别的事物隔离,以减少 mysql
实现事务隔离带来的成本
系统锁定争用情况查询
Table_locks_immediate
:产生表级锁定的次数
Table_locks_waited :
出现表级锁定争用而发生
等待的次数
系统锁定争用情况查询
MYSQL 数据库 QUERY 的优化
• 1. 理解 MYSQL 的 QUERY OPTIMIZE
• 2.QUERY 语句优化基本思路和原则
• 3. 充分利用 EXPLAIN 和 PROFILING
• 4. 合理设计并利用索引
• 5.JOIN 的实现原理与优化思路
• 6.ORDER BY 、 GROUP BY 和 DISTINCT
优化
• 7. 其他常用优化
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 信息还不足以完全确定执行计划,则
QUERY 语句优化基本思路和原
则• (1 )优化更需要优化的 Query ;
• ( 2 )定位优化对象的性能瓶颈;
• ( 3 )明确优化目标;
• ( 4 )从 Explain 入手;
• ( 5 )多使用 Profile ;
• ( 6 )永远用小结果集驱动大的结果集;
• ( 7 )尽可能在索引中完成排序;
• ( 8 )只取自己需要的 Columns ;
• ( 9 )仅仅使用最有效的过滤条件; _ 举例
• ( 10 )尽可能避免复杂的 Join 和子查询。
仅仅使用最有效的过滤条件
• 方案一:将用户 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) 两个索引
仅仅使用最有效的过滤条件
• 方案二:仅仅将用户 ID 作为过滤条件放在 WHERE 子句中来查询,
Query 的执行计划如示例代码 8-2 所示:代码 8-2
• sky@localhost : example 11:30:45> EXPLAIN SELECT * FROM gr
oup_message
• -> WHERE user_id = 1G
• *************************** 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)
仅仅使用最有效的过滤条件
• 方案三:仅将用户 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)
仅仅使用最有效的过滤条件
• 初略一看三个执行计划好像都挺好啊,每一个 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 一个过滤条件
的方案优。
JOIN 的原理
• 在 MySQL 中,只有一种 Join 算法,就是
大名鼎鼎的 Nested Loop Join ,它没有很多
其他数据库所提供的 Hash Join ,也没有
Sort Merge Join 。顾名思义, Nested Loop
Join 实际上就是通过驱动表的结果集作为循
环基础数据,然后将该结果集中的数据作为过
滤条件一条条地到下一个表中查询数据,最后
合并结果。如果还有第三个表参与 Join ,则
把前两个表的 Join 结果集作为循环基础数据
,再一次通过循环查询条件到第三个表中查询
数据,如此往复。
• 驱动表的定义:首先被访问的表又称外表
尽可能避免复杂的 Join 和子
查询
• MySQL 在并发这一块并不是太好,当并发量太高的时候,系统
整体性能可能会急剧下降,尤其是遇到一些较为复杂的 Query 的
时候。这主要与 MySQL 内部资源的争用锁定控制有关,如读写
相斥等。 InnoDB 存储引擎由于实现了行级锁定可能还要稍微好一些
,如果使用的是 MyISAM 存储引擎,并发一旦较高,性能下降非
常明显。所以, Query 语句所涉及的表越 多
,须要锁定的资源就越多。也就是说,
越复杂的 Join 语句,锁定的资源也就
越多,所阻塞的其他线程也就越多。相反,
如果将比较复杂的 Query 语句分拆成多个较为简单的 Query 语句
分步执行,每次锁定的资源也就会少很多,所阻塞的其他线程也要少
一些。
尽可能避免复杂的 Join 和子
查询
• 对于子查询,可能很多人都明白为什么会不被推荐使用。
在 MySQL 中,子查询的实现目前还比较差
,很难得到一个很好的执行计划,很多时候明
明有索引可以利用,可 Query Optimizer 就
是不用。 MySQL 官方给出的信息说,这一问题将在
MySQL 6.0 中得到较好的解决,将会引入 SemiJoin 的
执行计划,可 MySQL 6.0 离我们投入生产环境使用恐怕
还有很遥远的一段时间。所以,在 Query 优化的过程中
,能不用子查询就尽量不要用。
3. 充分利用 EXPLAIN 和
PROFILING
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: 输出引用的表。
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 包含这些
索引的最长的关键元素。
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 ,使得行能从早先的表中基于常数值或列值被检索出来。
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 认为它执行查询时必须检查的行数
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 ,这就会发生,或者是查询
有问题。
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 的具体用法。
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)
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 |
• +----------+------------+--------------------------------------------------------+
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 |
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 的效率。
MYSQL 的索引
• Hash 索引
• Hash 索引在 MySQL 中使用的并不是很多,目前主要是
Memory 和 NDB Cluster 存储引擎使用。
所谓 Hash 索引,实际上就是通过一定的 Hash 算法,将须要索引的键
值进行 Hash 运算,然后将得到的 Hash 值存入一个 Hash 表中。每
次须要检索的时候,都会将检索条件进行相同算法的 Hash 运算,再和
Hash 表中的 Hash 值进行比较,并得出相应的信息。
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 索引
中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结
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 的索引
• 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 索引就无
能为力了
•
如何判定是否须要创建索引
• 1. 较频繁的作为查询条件的字段应该创
建索引
• 2. 唯一性太差的字段不适合单独创建索
引,即使频繁作为查询条件
• 3. 更新非常频繁的字段不适合创建索引
• 4. 不会出现在 WHERE 子句中的字段
不该创建索引
单列索引还是复合索引• 对于这种问题,很难有一个绝对的定论,须要从多方面来分析考虑,平衡两种
方案各自的优劣,然后选择一种最佳的方案。而组合索引中因为有多个字段存在
,理论上被更新的可能性肯定比单键索引要大很多,这样带来的附加成本也 就比单键
索引要高。但是,当 WHERE 子句中的查询条件含有多个字段时,通过这多个字段共
同组成的组合索引的查询效率肯定比只用过滤条件中的某一个字段创建的索引要高。
因为通过单键索引过滤的数据并不完整,和组合索引相比,存储引擎须要访问更多的
记录数,自然就会访问更多的数据量,也就是说需要更高的 IO 成本。
• 可能有朋友会说,那可以创建多个单键索引啊。确实可以将 WHERE 子句中的每一
个字段都创建一个单键索引。但是这样真的有效吗?在这样的情况下, MySQL Query
Optimizer 大多数时候都只会选择其中的一个索引,然后放弃其他的索引。即使他选
择了同时利用两个或更多的索引通过 INDEX_MERGE 来优化查询,所收到的效果可
能并不会比选择其中某一个单键索引更高效。因为如果选择通过 INDEX_MERGE 来
优化查询,就须要访问多个索引,同时还要将几个索引进行 merge 操作,这带来的
成本可能反而会比选择其中一个最有效的索引更高。
• 在一般的应用场景中,只要不是其中某个过滤字段在大多数场景
下能过滤 90% 以上的数据,而其他的过滤字段会频繁的更
新,一般更倾向于创建组合索引, 尤其是在并发量较高的场景下。因为当并
发量较高的时候,即使只为每个 Query 节省了很少的 IO 消耗,但因为执行量
非常大,所节省的资源总量仍然是非常可观的。
• 当然,创建组合索引并不是说就须要将查询条件中的所有字段都放在一个索引
中,还应该尽量让一个索引被多个 Query 语句利用,尽量减少同一个表上的索
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 索引
6.ORDER BY 、 GROUP BY 和
DISTINCT 优化
• 1.Order by 的实现与优化:
• 2.Group by 的实现与优化
• 3.Distinct 的实现与优化
Order by 的实现与优化:
•
在 MySQL 中, Order by 的实现有两种,一是通过有序的索引直接取得有序的数据
直接返回客户端;二是通过 MySQL 的排序算法进行排序后在将排序后的数据返回到
客户端。
•       经实践证明利用索引实现数据排序的方法是 MySQL 中实现结果集排序的最佳
做法,可以完全避免因为排序计算所带来的资源消耗。所以,在我们优化 Query 语
句中的 ORDER BY 的时候,尽可能利用已有的索引来避免实际的排序计算,可以
很大幅度的提升 ORDER BY 操作的性能 。
•     对于采取排序算法 ,MySQL 有两种方式能够实现
• 1. 取出满足过滤条件的用于排序条件的字段以及可以直接定位到行数据的行
指针信息,在 SortBuffer 中进行实际的排序操作,然后利用排好序之后的数
据根据行指针信息返回表中取得客户端请求的其他字段的数据,再返回给客
户端;
2. 根据过滤条件一次取出排序字段以及客户端请求的所有其他字段的数据,
并将不需要排序的字段存放在一块内存区域中,然后在 Sort Buffer 中将排序
字段和行指针信息进行排序,最后再利用排序后的行指针与存放在内存区域
中和其他字段一起的行指针信息进行匹配合并结果集,再按照顺序返回给客
户端。
•        这两种方式略有不同,能够看出来第二种方式优于第一种方式,第二种方式是
典型的以内存为代价换取效率的提升。
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
Distinct 的实现与优化
•
Distinct 实际上和 Group by 的操作非常相似,只不过是在 Group by 之后
的每组中只取出一条记录而已。所以, Distinct 的实现和 Group by 的实现也
基本差不多,没有太大的区别。同样可以通过松散索引扫描或者是紧凑索引
扫描来实现,当然,在无法仅仅使用索引即能完成
Distinct 的时候, MySQL 只能通过临时表来完成。但
是,和 Group by 有一点差别的是, Distinct 并不需
要进行排序。也就是说,在仅仅只是 Distinct 操作的
Query 如果无法仅仅利用索引完成操作的时
候, MySQL 会利用临时表来做一次数据的“缓”,但
是不会对临时表中的数据进行 filesort 操作。当然,
如果我们在进行 Distinct 的时候还使用了 Group by
并进行了分组,并使用了类似于 max 之类的聚合函数
操作,就无法避免 filesort 了
MYSQL 的其他常用优化
• 1. 网络连接与连接线程
• 2.TABLE CACHE 的相关优化
• 3.SORT BUFFER 和 JOIN BUFFER
1. 网络连接与连接线程
• 查看连接线程相关的系统变量的设置值
• MYSQL>show variables like ‘thread%’
• 系统被连接的次数及当前系统中连接线
程的状态值
• Mysql>show status like ‘connections’
• mysql>show status like ‘%thread’
2.TABLE CACHE 的相关优化
• 查看表 CACHE 的设置和当前系统中的
使用状况
• Mysql>show variables like ‘table_cache’
• Mysql>show status like ‘open_tables’
3.SORT BUFFER 和 JOIN BUFFER
• Mysql>show variables like ‘%buffer%’
3.SORT BUFFER 和 JOIN BUFFER
• 用 set SESSION 命令设置会话级变量的新值
• mysql> set SESSION sort_buffer_size=7000000;
Query OK, 0 rows affected (0.00 sec)
• -- 修改会话级变量对当前会话来说立刻生效
QUERY CACHE 优化
• 定义
• 顾名思义, MySQL Query Cache 就是用
来缓存和 Query 相关的数据的。具体来
说, Query Cache 缓存了我们客户端提交
给 MySQL 的 SELECT 语句以及该语句
的结果集。大概来讲,就是将 SELECT
语句和语句的结果做了一个 HASH 映
射关系然后保存在一定的内存区域中。
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 优化
• 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 模块进行执
行计划的分析优化,更不许要发生任何存储引擎的交互,减少
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 不仅不能提高效
率,反而可能造成负面影响。
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 ,这也是业界比较常用的做法。
QUERY CACHE 优化
• MySQL Cluster 是否可以使用 Query Cache ?可以
使用
其实在我们的生产环境中也没有使用 MySQL Cluster ,
所以我也没有在 MySQL Cluster 环境中使用 Query
Cache 的实际经验,只是 MySQL 文档中说明确实可以
在 MySQL Cluster 中使用 Query Cache 。从 MySQL
Cluster 的原理来分析,也觉得应该可以使用,毕竟
SQL 节点和数据节点比较独立,各司其职,只是 Cache
的失效机制会要稍微复杂一点。
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
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
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)
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 ,因为首先变长字段
存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字
段内搜索效率显然要高些。
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./* 使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来
解决问题,基于集的方法通常更有效。 */
MySQL 中优化 sql 语句查询
常用的 30 种方法
• 27. 与临时表一样,游标并不是不可使用。对小型数据集使用
FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是
在必须引用几个表才能获得所需的数据时。在结果集中包括“合
计”的例程通常要比使用游标执行的速度快。如果开发时间允许
,基于游标的方法和基于集的方法都可以尝试一下,看哪一种
方法的效果更好。
28. 在所有的存储过程和触发器的开始处设置 SET NOCOUNT
ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过
程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息
。
29. 尽量避免向客户端返回大数据量,若数据量过大,应该考虑
相应需求是否合理。
30. 尽量避免大事务操作,提高系统并发能力。
Query 优化基本原则
• 永远用小结果集驱动大的结果集;
• 尽可能在索引中完成排序;
• 只取出自己需要的 Columns ;
• 仅仅使用最有效的过滤条件;
• 尽可能避免复杂的 Join 和子查询;能不
使用子查询就不使用子查询
• 多使用 profile
Mysql 存储引擎
MYSQL 存储引擎
5. 常用存储引擎优化
• 1.MYISAM 存储引擎优化
• 2.INNODB 存储引擎优化
5.1 MYISAM 存储引擎优化
• 1. 索引缓存优化
• 2. 多 KEY CACHE 的使用
• 3.KEY CACHE 的互斥( MUTEX )问题
• 4.KEY CACHE 预加载
• 5.NULL 值对统计信息的影响
• 6. 表读取缓存优化
• 7. 并发优化
• 8. 其他可以优化的地方
补充: Mysql 数据库命名规
范
• 主键、外键、索引、视图、函数、过程、触发器等的命名规范
• 此要求必须严格执行
• 主键:使用 pk_ 前缀 , 前缀名称一般不超过 5 字
• 外键:使用 fk_ 前缀 , 前缀名称一般不超过 5 字
• 索引:使用 idx_ 前缀 , 前缀名称一般不超过 5 字
• 视图:使用 v_ 前缀 , 前缀名称一般不超过 5 字
• 函数:使用 f_ 前缀 , 前缀名称一般不超过 5 字
• 过程:使用 p_ 前缀 , 前缀名称一般不超过 5 字
• 触发器:使用 t_ 前缀 , 前缀名称一般不超过 5 字
两天性能测试中发现的问题
• 1. 在数据库中进行大排序
• 2. 系统中索引的使用率较低
• 3. 多表连接性能低
• 4. 数据库对象命名不规范
• 5. 数据库设计不遵循 3NF
• 6. 产品中存在锁争用的问题
• 7. 产品中存在循环次数不正确的地方
MYSQL 中的大排序
• 在公告功能测试中,后台出现了锁等待 , 造成的原因是对 40 万的数据进行
了排序
• Select * from onlinemessage order by sendDate;
系统中索引的使用率较低
索引使用情况:下图说明我们的 SQL 中走全表扫
描的百分比,大于 20% 警告,大于 40% 比较严重
,
而我们达到 66.5%
没有使用索引的例子
• select id,businesstype,docid,state,userid,createtime,typeflag from
fulltextsearch_info where ?=? and businesstype = ? and docid = ?;
多表连接
多表连接
• 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_memb
er.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
多表连接
多表连接• 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_membe
• )
• WHERE
美 成都丽
感 的 注谢您 关
haoxp20110515

Más contenido relacionado

La actualidad más candente

【Ask maclean技术分享】oracle dba技能列表 z
【Ask maclean技术分享】oracle dba技能列表 z【Ask maclean技术分享】oracle dba技能列表 z
【Ask maclean技术分享】oracle dba技能列表 zmaclean liu
 
内部MySQL培训.3.基本原理
内部MySQL培训.3.基本原理内部MySQL培训.3.基本原理
内部MySQL培训.3.基本原理Lixun Peng
 
Oracle使用者安全設定
Oracle使用者安全設定Oracle使用者安全設定
Oracle使用者安全設定Chien Chung Shen
 
MySQL新技术探索与实践
MySQL新技术探索与实践MySQL新技术探索与实践
MySQL新技术探索与实践Lixun Peng
 
Ch04 會話管理
Ch04 會話管理Ch04 會話管理
Ch04 會話管理Justin Lin
 
Oracle管理藝術第1章 在Linux作業體統安裝Oracle 11g
Oracle管理藝術第1章 在Linux作業體統安裝Oracle 11gOracle管理藝術第1章 在Linux作業體統安裝Oracle 11g
Oracle管理藝術第1章 在Linux作業體統安裝Oracle 11gChien Chung Shen
 
第一讲 My sql初步
第一讲 My sql初步第一讲 My sql初步
第一讲 My sql初步hjl888666
 
五款常用mysql slow log分析工具的比较
五款常用mysql slow log分析工具的比较 五款常用mysql slow log分析工具的比较
五款常用mysql slow log分析工具的比较 colderboy17
 
MySQL源码分析.02.Handler API
MySQL源码分析.02.Handler APIMySQL源码分析.02.Handler API
MySQL源码分析.02.Handler APILixun Peng
 
深入了解Oracle自动内存管理asmm
深入了解Oracle自动内存管理asmm深入了解Oracle自动内存管理asmm
深入了解Oracle自动内存管理asmmmaclean liu
 
Oracle数据库高级安全选件ASO介绍
Oracle数据库高级安全选件ASO介绍Oracle数据库高级安全选件ASO介绍
Oracle数据库高级安全选件ASO介绍jenkin
 
Introduction to MySQL and phpMyAdmin
Introduction to MySQL and phpMyAdminIntroduction to MySQL and phpMyAdmin
Introduction to MySQL and phpMyAdminDrake Huang
 
CH10:輸入輸出
CH10:輸入輸出CH10:輸入輸出
CH10:輸入輸出Justin Lin
 
Ch03 請求與回應
Ch03 請求與回應Ch03 請求與回應
Ch03 請求與回應Justin Lin
 
MySQL 網路參考架構
MySQL 網路參考架構MySQL 網路參考架構
MySQL 網路參考架構郁萍 王
 
MySQL5.6新功能
MySQL5.6新功能MySQL5.6新功能
MySQL5.6新功能郁萍 王
 
Mysql展示功能与源码对应
Mysql展示功能与源码对应Mysql展示功能与源码对应
Mysql展示功能与源码对应zhaolinjnu
 

La actualidad más candente (20)

【Ask maclean技术分享】oracle dba技能列表 z
【Ask maclean技术分享】oracle dba技能列表 z【Ask maclean技术分享】oracle dba技能列表 z
【Ask maclean技术分享】oracle dba技能列表 z
 
Oracle Instance 介紹
Oracle Instance 介紹Oracle Instance 介紹
Oracle Instance 介紹
 
内部MySQL培训.3.基本原理
内部MySQL培训.3.基本原理内部MySQL培训.3.基本原理
内部MySQL培训.3.基本原理
 
Oracle使用者安全設定
Oracle使用者安全設定Oracle使用者安全設定
Oracle使用者安全設定
 
MySQL新技术探索与实践
MySQL新技术探索与实践MySQL新技术探索与实践
MySQL新技术探索与实践
 
Ch04 會話管理
Ch04 會話管理Ch04 會話管理
Ch04 會話管理
 
Oracle管理藝術第1章 在Linux作業體統安裝Oracle 11g
Oracle管理藝術第1章 在Linux作業體統安裝Oracle 11gOracle管理藝術第1章 在Linux作業體統安裝Oracle 11g
Oracle管理藝術第1章 在Linux作業體統安裝Oracle 11g
 
第一讲 My sql初步
第一讲 My sql初步第一讲 My sql初步
第一讲 My sql初步
 
五款常用mysql slow log分析工具的比较
五款常用mysql slow log分析工具的比较 五款常用mysql slow log分析工具的比较
五款常用mysql slow log分析工具的比较
 
Oracle Tablespace介紹
Oracle Tablespace介紹Oracle Tablespace介紹
Oracle Tablespace介紹
 
MySQL源码分析.02.Handler API
MySQL源码分析.02.Handler APIMySQL源码分析.02.Handler API
MySQL源码分析.02.Handler API
 
深入了解Oracle自动内存管理asmm
深入了解Oracle自动内存管理asmm深入了解Oracle自动内存管理asmm
深入了解Oracle自动内存管理asmm
 
Oracle 資料庫建立
Oracle 資料庫建立Oracle 資料庫建立
Oracle 資料庫建立
 
Oracle数据库高级安全选件ASO介绍
Oracle数据库高级安全选件ASO介绍Oracle数据库高级安全选件ASO介绍
Oracle数据库高级安全选件ASO介绍
 
Introduction to MySQL and phpMyAdmin
Introduction to MySQL and phpMyAdminIntroduction to MySQL and phpMyAdmin
Introduction to MySQL and phpMyAdmin
 
CH10:輸入輸出
CH10:輸入輸出CH10:輸入輸出
CH10:輸入輸出
 
Ch03 請求與回應
Ch03 請求與回應Ch03 請求與回應
Ch03 請求與回應
 
MySQL 網路參考架構
MySQL 網路參考架構MySQL 網路參考架構
MySQL 網路參考架構
 
MySQL5.6新功能
MySQL5.6新功能MySQL5.6新功能
MySQL5.6新功能
 
Mysql展示功能与源码对应
Mysql展示功能与源码对应Mysql展示功能与源码对应
Mysql展示功能与源码对应
 

Similar a Mysql 培训-优化篇

My sql explain & select
My sql explain & selectMy sql explain & select
My sql explain & selectMing-Ying Wu
 
浅谈 My sql 性能调优
浅谈 My sql 性能调优浅谈 My sql 性能调优
浅谈 My sql 性能调优thinkinlamp
 
MySQL应用优化实践
MySQL应用优化实践MySQL应用优化实践
MySQL应用优化实践mysqlops
 
MySQL5.6&5.7 Cluster 7.3 Review
MySQL5.6&5.7 Cluster 7.3 ReviewMySQL5.6&5.7 Cluster 7.3 Review
MySQL5.6&5.7 Cluster 7.3 Review郁萍 王
 
4 葉金榮-my sql優化 - 20151219
4 葉金榮-my sql優化 - 201512194 葉金榮-my sql優化 - 20151219
4 葉金榮-my sql優化 - 20151219Ivan Tu
 
MySQL数据库设计、优化
MySQL数据库设计、优化MySQL数据库设计、优化
MySQL数据库设计、优化Jinrong Ye
 
百度数据库中间层
百度数据库中间层百度数据库中间层
百度数据库中间层yp_fangdong
 
Notes of jcip
Notes of jcipNotes of jcip
Notes of jcipDai Jun
 
Mysql调优
Mysql调优Mysql调优
Mysql调优ken shin
 
1, OCP - architecture intro
1, OCP - architecture intro1, OCP - architecture intro
1, OCP - architecture introted-xu
 
基于MySQL开放复制协议的同步扩展
基于MySQL开放复制协议的同步扩展基于MySQL开放复制协议的同步扩展
基于MySQL开放复制协议的同步扩展Sky Jian
 
深入解析MySQL之锁机制应用
深入解析MySQL之锁机制应用深入解析MySQL之锁机制应用
深入解析MySQL之锁机制应用banping
 
MySQL基础技能与原理——高级应用
MySQL基础技能与原理——高级应用MySQL基础技能与原理——高级应用
MySQL基础技能与原理——高级应用Michael Zhang
 
新浪微博Feed服务架构
新浪微博Feed服务架构新浪微博Feed服务架构
新浪微博Feed服务架构XiaoJun Hong
 
Oracle北大青鸟完全教程
Oracle北大青鸟完全教程Oracle北大青鸟完全教程
Oracle北大青鸟完全教程yiditushe
 
111030 gztechparty-小路-云时代的mysql
111030 gztechparty-小路-云时代的mysql111030 gztechparty-小路-云时代的mysql
111030 gztechparty-小路-云时代的mysqlZoom Quiet
 
Mysql proxy cluster
Mysql proxy clusterMysql proxy cluster
Mysql proxy clusterYiwei Ma
 

Similar a Mysql 培训-优化篇 (20)

My sql explain & select
My sql explain & selectMy sql explain & select
My sql explain & select
 
浅谈 My sql 性能调优
浅谈 My sql 性能调优浅谈 My sql 性能调优
浅谈 My sql 性能调优
 
MySQL应用优化实践
MySQL应用优化实践MySQL应用优化实践
MySQL应用优化实践
 
MySQL5.6&5.7 Cluster 7.3 Review
MySQL5.6&5.7 Cluster 7.3 ReviewMySQL5.6&5.7 Cluster 7.3 Review
MySQL5.6&5.7 Cluster 7.3 Review
 
4 葉金榮-my sql優化 - 20151219
4 葉金榮-my sql優化 - 201512194 葉金榮-my sql優化 - 20151219
4 葉金榮-my sql優化 - 20151219
 
MySQL数据库设计、优化
MySQL数据库设计、优化MySQL数据库设计、优化
MySQL数据库设计、优化
 
百度数据库中间层
百度数据库中间层百度数据库中间层
百度数据库中间层
 
Notes of jcip
Notes of jcipNotes of jcip
Notes of jcip
 
Mysql调优
Mysql调优Mysql调优
Mysql调优
 
1, OCP - architecture intro
1, OCP - architecture intro1, OCP - architecture intro
1, OCP - architecture intro
 
基于MySQL开放复制协议的同步扩展
基于MySQL开放复制协议的同步扩展基于MySQL开放复制协议的同步扩展
基于MySQL开放复制协议的同步扩展
 
深入解析MySQL之锁机制应用
深入解析MySQL之锁机制应用深入解析MySQL之锁机制应用
深入解析MySQL之锁机制应用
 
MySQL基础技能与原理——高级应用
MySQL基础技能与原理——高级应用MySQL基础技能与原理——高级应用
MySQL基础技能与原理——高级应用
 
新浪微博Feed服务架构
新浪微博Feed服务架构新浪微博Feed服务架构
新浪微博Feed服务架构
 
Oracle北大青鸟完全教程
Oracle北大青鸟完全教程Oracle北大青鸟完全教程
Oracle北大青鸟完全教程
 
Mysql Replication
Mysql ReplicationMysql Replication
Mysql Replication
 
111030 gztechparty-小路-云时代的mysql
111030 gztechparty-小路-云时代的mysql111030 gztechparty-小路-云时代的mysql
111030 gztechparty-小路-云时代的mysql
 
MySQL入門介紹
MySQL入門介紹MySQL入門介紹
MySQL入門介紹
 
内存数据库[1]
内存数据库[1]内存数据库[1]
内存数据库[1]
 
Mysql proxy cluster
Mysql proxy clusterMysql proxy cluster
Mysql proxy cluster
 

Mysql 培训-优化篇

  • 1. MYSQL 培训 - 优化篇 HXP_20110515 化篇优
  • 2. 化篇优 1 2 3 4 MYSQL 架构组成 MYISAM 表锁优化建 议 MYSQL 数据库 QUERY 的优 化 QUERY CACHE 优化 6 常用存储引擎优化 MYSQL 可扩展设计的基本原 则 5
  • 3. 1. MYSQL 架构组成 • 1.MYSQL 物理文件组成 • 1.> 日志文件 • 2.> 数据文件 • 3.>REPLICATION 相关文件 • 4.> 其他文件 • 2.MYSQL SERVER 系统架构 • 1.> 逻辑模块组成 • 2.> 各模块工作配合
  • 4. 日志文件 • 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
  • 5. 数据文件 • 1.FRM 文件 • 与表相关的元数据信息都存放在 .FRM 文件中,包括表结构信息。不论是什么的存储引擎都 会有一个表名 .FRM 的文件 • 2.MYD 文件 • 是 MYISAM 存储引擎专用的,存放 MYISM 表的数据。 • 3.MYI 文件 • 也是 MYISAM 存储引擎专用的,主要存放 MYISAM 表的索引相关信息。对于 MYISAM 存储来 说,可以被 CACHE 的内容主要是来源于 .MYI 的文件中。 • 4.IBD 文件和 IBDATA 文件 • 这两种都是存放 INNODB 数据的文件(包括索引),因为 INNODB 的数据存储方式能够通过 配置来决定是使用共享表空间存放存储数据,还是用独享表空间来存放数据。
  • 6. 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 线程及某些管理操作随时能 够获得当前复制的相关信息。
  • 7. 其他文件 • 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
  • 8. MYSQL SERVER 系统架构 •1. 逻辑模块组成 •2. 各模块工作配合
  • 9. 逻辑模块组成 • 在 Mysql 中,我们看作两层架构,即 SQL Layer ( SQL 处理层)和 Storage Engine Layer (存储引擎层)。在 MySQL 处理底层数据之前,所有的 操作都是在 SQL Layer 层完成的,如 :权限判断、 SQL 解析、查询优化、 cache 处理等。经过这一层,再交由 Storage Engine Layer 层处理。所以我 们可以将 MySQL 看作是右图的结构。 • 但是, MySQL 的每一层也包含许多小 模块,下面我们做一简单介绍
  • 10. 逻辑模块组成 • 1. 初始化模块 • 此模块是在 MySQL Server 启动的时候对整个系统进行初始化操作,包括 buffer 、 cache 结构的初始化和内存空间的申请、各种系统变量和存储引擎的初始化工作等。 • 2. 核心 API • 此模块主要是为了提供一些非常高效的底层操作功能的优化实现,包括各种底层数据结构的实 现、特殊算法的实现、字符串与数字处理以及最重要的内存管理工作等。核心 API 的所有 源代码都集中在 mysys 和 string 文件夹下面。 • 3. 网络交互模块 • 底层网格交互模块抽象出底层网络交互所使用的 api ,实现底层网络数据的接收与发送,以方 便其他模块调用和对这一部分的维护。源代码在 vio 文件夹下面。 • 4. Client & Server 交互协议模块 • 任何 C/S 结构的软件系统,都有自己独有的信息交互协议, MySQL 也是如此。这些协议都是 建立在 OS 七层模型之上的,如 TCP/IP 和 Unix Socket 。 •
  • 11. 逻辑模块组成 • 5. 用户模块 • 此模块主要实现权限控制和授权管理。如判断某个用户能不能进入某个库内进 行相关操作。 • 6. 访问控制模块 • 此模块与用户模块主要的区别是它限定了用户的访问控制权限,如 select 、 update 等。这两个模块共同组成了整个系统的权限安全管理功能。 • 7. 连接管理、连接线程和线程管 理 • 连接管理模块主要负责监听 MySQL Server 的各种请求,接收和转发连接请求 到线程管理模块。每一个连接上 MySQL 的客户端请求都会被分配或创建一个独 享的线程为其服务。而连接线程的主要工作就是负责 MySQL Server 和客户端的 连接通信,接受客户端请求、传递 Server 端结果信息等。线程管理模块则负责管 理和维护这些线程,包括线程创建、线程的 cache 等。 • 8. Query 解析和转发模块 • 此模块主要负责将接收到的 SQL 进行语义和语法的分析,然后按照分类有针对
  • 12. 逻辑模块组成 • 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. 表维护模块 • 此模块主要负责表的状态检查、错误修复以及优化和分析。
  • 13. 逻辑模块组成 • 13. 表管理器 • 此模块与表维护模块的功能完全不同。上次我们提到的 .frm 文件就是由这个模块进行 管理和维护的。它还负责 table 级别的锁管理。 • 14. 系统状态管理模块 • 此模块主要负责当客户端请求系统状态时,将各种状态信息返回给客户。如 show status 、 show variables 等。 • 15. 日志记录模块 • 此模块主要负责整个系统的逻辑层的日志的记录,包括 Error Log 、 Binary Log 、 Slow Query Log 等。
  • 14. 逻辑模块组成 • 16. 复制模块 • 此模块主要负责主从配置中的数据同步,故又可分为 Master 模块和 Slave 模块。 • Master 模块主要负责在 Slave 端读取 Binary Log 以及与 Master 端的 I/O 线程交互等工 作。 • Slave 模块主要负责从 Master 端请求和接受 Binary Log ,并写入本地的 Relay Log 中 的 I/O 线程,然后将其转化为 SQL 应用于 Slave 端。 • 17. 存储引擎接口模块 • 目前所有的数据库产品中,基本上只有 MySQL 实现了底层数据存储引擎的插件式 管理。这个模块实际上是一个抽象类,将各种数据处理高度抽象化。
  • 16. MySQL 数据库锁定机制 • MySQL 使用了三种锁定机制:行级锁定、页级锁定和表级锁定 • 一、行级锁定 • 特点:锁定对象的颗粒度很小,也是目前各大数据库管理软件中所实现的锁定颗粒最小的 。发生锁定资源的概率最小,能够给予尽可能大的并发处理能力,提高整体性能。 • 弊端:每次获取锁和释放锁需要做的事情较多,带来的消耗更大,容易发生死锁。 • 用途: MyISAM 、 Memorey 、 CSV 等一些非事务性存储引擎。 • 二、表级锁定 • 特点:表级锁定是 MySQL 各大存储引擎中颗粒度最大的锁定机制,实现逻辑非常简单, 带来的系统负面影响最小。获取和释放锁的速度较快,避免死锁问题。 • 弊端:出现锁定资源争用的概率最高,致使并发度大打折扣。 • 用途: InnoDB 和 NDB Cluster 存储引擎。 • 三、页级锁定 • 特点:锁定颗粒度介于行级锁定与表级锁定之间,并发处理能力也介于二者之间,同 样会发生死锁问题。 • 用途: BerkeleyDB 存储引擎 • 在数据库实现锁定资源的过程中,随着锁定颗粒度越来越小,锁定相同数据量的数据所消耗 的内存数量是越来越多的,实现算法也越来越复杂。不过随着颗粒度的减小,应用程序访问 请求遇到锁等待的可能性也会随之降低,系统整体并发度也会随之提升。
  • 17. 各种锁定机制分析 • 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. 行级锁定
  • 18. 表锁定 • 虽然对于使用者来说表现为两种锁定,但在 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 而产生
  • 19. 表锁定 • 读锁定: • 一个新的客户端请求在申请获取读锁定资源的时候,需要满足两个条件: • 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
  • 20. 行级锁定 • 行级锁定不是 MySQL 自己实现的锁定机制,而是由 Innodb 和 NDB Cluster 存储引擎 实现的。而由于这个锁定机制是由各个存储引擎自行实现,所以具体实现算法也有 差别。 • Innodb 的行锁定分为两种:共享锁和排它锁。而为了让行 锁定和表锁定机制共存, Innodb 也使用了意向锁(表级锁定)的概念,于是有了意 向共享锁和意向排它锁。 • 意向锁的意思就是我这个线程在遇到需要资源被锁定的情况下,再附加一个我想要 的锁的意思,来等待资源的释放,再自行添加自己需要的锁 • 注意:当一个请求需要锁定资源的时候 ,如果资源已被共享锁锁定,只能添加 共享锁;而如果资源已被排它锁锁定, 则只能等待锁定释放,再添加自己使用 的锁。意向共享锁可以并存,意向排它 锁只能存在一个。
  • 22. 合理利用锁机制优化 MYSQL • 1.MYISAM 表锁优化建议 _ 重点 • 2.INNODB 行锁优化建议 • 3. 系统锁定争用情况查询 _ 重点
  • 23. 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 。
  • 24. MYISAM 表锁优化建议 _ 重点 • 3 、合理利用读写优先级 • MYSQL 的表级锁定在默认情况下是写优先级大于读 。所以,如果系统是一个以读为主,而且要优先保证 查询性能的话,可以通过设置系统参数选项 low_priority_updates=1 ,将写的优先级设置为比读 低,即告诉 MYSQL 尽量先处理读请求;如果系统须 要有限保证数据写入的性能的话,则不用设置 low_priority_updates 参数了。并发优化的另外一个 方面还可以通过开启 Key Cache ,用来缓存索引来提 高读取速度,如果这样还不觉得快的话,还可以通过 Query Cache 功能来直接缓存 Query 的结果集。当然 ,还可以合理利用第三方案 Cache 软件,如 Memcached ,来缓存数据,提升系统性能。
  • 25. Innodb 行锁优化 • 缩小锁定范围 • 尽量让所有检索都通过索引来完成 • 合理设计索引,让 Innodb 在索引键上面加锁 时尽可能准确,缩小锁定范围。 • 查询时尽可能减少基于范围的检索过滤,避免 锁定不必要记录 • 尽量控制事务大小,减小锁定资源量与锁定时 间 • 尽量使用较低级别的事物隔离,以减少 mysql 实现事务隔离带来的成本
  • 28. MYSQL 数据库 QUERY 的优化 • 1. 理解 MYSQL 的 QUERY OPTIMIZE • 2.QUERY 语句优化基本思路和原则 • 3. 充分利用 EXPLAIN 和 PROFILING • 4. 合理设计并利用索引 • 5.JOIN 的实现原理与优化思路 • 6.ORDER BY 、 GROUP BY 和 DISTINCT 优化 • 7. 其他常用优化
  • 29. 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 信息还不足以完全确定执行计划,则
  • 30. QUERY 语句优化基本思路和原 则• (1 )优化更需要优化的 Query ; • ( 2 )定位优化对象的性能瓶颈; • ( 3 )明确优化目标; • ( 4 )从 Explain 入手; • ( 5 )多使用 Profile ; • ( 6 )永远用小结果集驱动大的结果集; • ( 7 )尽可能在索引中完成排序; • ( 8 )只取自己需要的 Columns ; • ( 9 )仅仅使用最有效的过滤条件; _ 举例 • ( 10 )尽可能避免复杂的 Join 和子查询。
  • 31. 仅仅使用最有效的过滤条件 • 方案一:将用户 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) 两个索引
  • 32. 仅仅使用最有效的过滤条件 • 方案二:仅仅将用户 ID 作为过滤条件放在 WHERE 子句中来查询, Query 的执行计划如示例代码 8-2 所示:代码 8-2 • sky@localhost : example 11:30:45> EXPLAIN SELECT * FROM gr oup_message • -> WHERE user_id = 1G • *************************** 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)
  • 33. 仅仅使用最有效的过滤条件 • 方案三:仅将用户 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)
  • 34. 仅仅使用最有效的过滤条件 • 初略一看三个执行计划好像都挺好啊,每一个 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 一个过滤条件 的方案优。
  • 35. JOIN 的原理 • 在 MySQL 中,只有一种 Join 算法,就是 大名鼎鼎的 Nested Loop Join ,它没有很多 其他数据库所提供的 Hash Join ,也没有 Sort Merge Join 。顾名思义, Nested Loop Join 实际上就是通过驱动表的结果集作为循 环基础数据,然后将该结果集中的数据作为过 滤条件一条条地到下一个表中查询数据,最后 合并结果。如果还有第三个表参与 Join ,则 把前两个表的 Join 结果集作为循环基础数据 ,再一次通过循环查询条件到第三个表中查询 数据,如此往复。 • 驱动表的定义:首先被访问的表又称外表
  • 36. 尽可能避免复杂的 Join 和子 查询 • MySQL 在并发这一块并不是太好,当并发量太高的时候,系统 整体性能可能会急剧下降,尤其是遇到一些较为复杂的 Query 的 时候。这主要与 MySQL 内部资源的争用锁定控制有关,如读写 相斥等。 InnoDB 存储引擎由于实现了行级锁定可能还要稍微好一些 ,如果使用的是 MyISAM 存储引擎,并发一旦较高,性能下降非 常明显。所以, Query 语句所涉及的表越 多 ,须要锁定的资源就越多。也就是说, 越复杂的 Join 语句,锁定的资源也就 越多,所阻塞的其他线程也就越多。相反, 如果将比较复杂的 Query 语句分拆成多个较为简单的 Query 语句 分步执行,每次锁定的资源也就会少很多,所阻塞的其他线程也要少 一些。
  • 37. 尽可能避免复杂的 Join 和子 查询 • 对于子查询,可能很多人都明白为什么会不被推荐使用。 在 MySQL 中,子查询的实现目前还比较差 ,很难得到一个很好的执行计划,很多时候明 明有索引可以利用,可 Query Optimizer 就 是不用。 MySQL 官方给出的信息说,这一问题将在 MySQL 6.0 中得到较好的解决,将会引入 SemiJoin 的 执行计划,可 MySQL 6.0 离我们投入生产环境使用恐怕 还有很遥远的一段时间。所以,在 Query 优化的过程中 ,能不用子查询就尽量不要用。
  • 38. 3. 充分利用 EXPLAIN 和 PROFILING
  • 39. 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: 输出引用的表。
  • 40. 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 包含这些 索引的最长的关键元素。
  • 41. 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 ,使得行能从早先的表中基于常数值或列值被检索出来。
  • 42. 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 认为它执行查询时必须检查的行数
  • 43. 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 ,这就会发生,或者是查询 有问题。
  • 44. 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 的具体用法。
  • 45. 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)
  • 46. 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 | • +----------+------------+--------------------------------------------------------+
  • 47. 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 |
  • 48. 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 的效率。
  • 49. MYSQL 的索引 • Hash 索引 • Hash 索引在 MySQL 中使用的并不是很多,目前主要是 Memory 和 NDB Cluster 存储引擎使用。 所谓 Hash 索引,实际上就是通过一定的 Hash 算法,将须要索引的键 值进行 Hash 运算,然后将得到的 Hash 值存入一个 Hash 表中。每 次须要检索的时候,都会将检索条件进行相同算法的 Hash 运算,再和 Hash 表中的 Hash 值进行比较,并得出相应的信息。
  • 50. 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 索引 中直接完成查询,还是要通过访问表中的实际数据进行相应的比较,并得到相应的结
  • 51. 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 通过一 些特定的语法信息,针对自然语言做了各种相应规则的匹配,最后给出了非负的
  • 52. 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 索引就无 能为力了 •
  • 53. 如何判定是否须要创建索引 • 1. 较频繁的作为查询条件的字段应该创 建索引 • 2. 唯一性太差的字段不适合单独创建索 引,即使频繁作为查询条件 • 3. 更新非常频繁的字段不适合创建索引 • 4. 不会出现在 WHERE 子句中的字段 不该创建索引
  • 54. 单列索引还是复合索引• 对于这种问题,很难有一个绝对的定论,须要从多方面来分析考虑,平衡两种 方案各自的优劣,然后选择一种最佳的方案。而组合索引中因为有多个字段存在 ,理论上被更新的可能性肯定比单键索引要大很多,这样带来的附加成本也 就比单键 索引要高。但是,当 WHERE 子句中的查询条件含有多个字段时,通过这多个字段共 同组成的组合索引的查询效率肯定比只用过滤条件中的某一个字段创建的索引要高。 因为通过单键索引过滤的数据并不完整,和组合索引相比,存储引擎须要访问更多的 记录数,自然就会访问更多的数据量,也就是说需要更高的 IO 成本。 • 可能有朋友会说,那可以创建多个单键索引啊。确实可以将 WHERE 子句中的每一 个字段都创建一个单键索引。但是这样真的有效吗?在这样的情况下, MySQL Query Optimizer 大多数时候都只会选择其中的一个索引,然后放弃其他的索引。即使他选 择了同时利用两个或更多的索引通过 INDEX_MERGE 来优化查询,所收到的效果可 能并不会比选择其中某一个单键索引更高效。因为如果选择通过 INDEX_MERGE 来 优化查询,就须要访问多个索引,同时还要将几个索引进行 merge 操作,这带来的 成本可能反而会比选择其中一个最有效的索引更高。 • 在一般的应用场景中,只要不是其中某个过滤字段在大多数场景 下能过滤 90% 以上的数据,而其他的过滤字段会频繁的更 新,一般更倾向于创建组合索引, 尤其是在并发量较高的场景下。因为当并 发量较高的时候,即使只为每个 Query 节省了很少的 IO 消耗,但因为执行量 非常大,所节省的资源总量仍然是非常可观的。 • 当然,创建组合索引并不是说就须要将查询条件中的所有字段都放在一个索引 中,还应该尽量让一个索引被多个 Query 语句利用,尽量减少同一个表上的索
  • 55. 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 索引
  • 56. 6.ORDER BY 、 GROUP BY 和 DISTINCT 优化 • 1.Order by 的实现与优化: • 2.Group by 的实现与优化 • 3.Distinct 的实现与优化
  • 57. Order by 的实现与优化: • 在 MySQL 中, Order by 的实现有两种,一是通过有序的索引直接取得有序的数据 直接返回客户端;二是通过 MySQL 的排序算法进行排序后在将排序后的数据返回到 客户端。 •       经实践证明利用索引实现数据排序的方法是 MySQL 中实现结果集排序的最佳 做法,可以完全避免因为排序计算所带来的资源消耗。所以,在我们优化 Query 语 句中的 ORDER BY 的时候,尽可能利用已有的索引来避免实际的排序计算,可以 很大幅度的提升 ORDER BY 操作的性能 。 •     对于采取排序算法 ,MySQL 有两种方式能够实现 • 1. 取出满足过滤条件的用于排序条件的字段以及可以直接定位到行数据的行 指针信息,在 SortBuffer 中进行实际的排序操作,然后利用排好序之后的数 据根据行指针信息返回表中取得客户端请求的其他字段的数据,再返回给客 户端; 2. 根据过滤条件一次取出排序字段以及客户端请求的所有其他字段的数据, 并将不需要排序的字段存放在一块内存区域中,然后在 Sort Buffer 中将排序 字段和行指针信息进行排序,最后再利用排序后的行指针与存放在内存区域 中和其他字段一起的行指针信息进行匹配合并结果集,再按照顺序返回给客 户端。 •        这两种方式略有不同,能够看出来第二种方式优于第一种方式,第二种方式是 典型的以内存为代价换取效率的提升。
  • 58. 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
  • 59. Distinct 的实现与优化 • Distinct 实际上和 Group by 的操作非常相似,只不过是在 Group by 之后 的每组中只取出一条记录而已。所以, Distinct 的实现和 Group by 的实现也 基本差不多,没有太大的区别。同样可以通过松散索引扫描或者是紧凑索引 扫描来实现,当然,在无法仅仅使用索引即能完成 Distinct 的时候, MySQL 只能通过临时表来完成。但 是,和 Group by 有一点差别的是, Distinct 并不需 要进行排序。也就是说,在仅仅只是 Distinct 操作的 Query 如果无法仅仅利用索引完成操作的时 候, MySQL 会利用临时表来做一次数据的“缓”,但 是不会对临时表中的数据进行 filesort 操作。当然, 如果我们在进行 Distinct 的时候还使用了 Group by 并进行了分组,并使用了类似于 max 之类的聚合函数 操作,就无法避免 filesort 了
  • 60. MYSQL 的其他常用优化 • 1. 网络连接与连接线程 • 2.TABLE CACHE 的相关优化 • 3.SORT BUFFER 和 JOIN BUFFER
  • 61. 1. 网络连接与连接线程 • 查看连接线程相关的系统变量的设置值 • MYSQL>show variables like ‘thread%’ • 系统被连接的次数及当前系统中连接线 程的状态值 • Mysql>show status like ‘connections’ • mysql>show status like ‘%thread’
  • 62. 2.TABLE CACHE 的相关优化 • 查看表 CACHE 的设置和当前系统中的 使用状况 • Mysql>show variables like ‘table_cache’ • Mysql>show status like ‘open_tables’
  • 63. 3.SORT BUFFER 和 JOIN BUFFER • Mysql>show variables like ‘%buffer%’
  • 64. 3.SORT BUFFER 和 JOIN BUFFER • 用 set SESSION 命令设置会话级变量的新值 • mysql> set SESSION sort_buffer_size=7000000; Query OK, 0 rows affected (0.00 sec) • -- 修改会话级变量对当前会话来说立刻生效
  • 65. QUERY CACHE 优化 • 定义 • 顾名思义, MySQL Query Cache 就是用 来缓存和 Query 相关的数据的。具体来 说, Query Cache 缓存了我们客户端提交 给 MySQL 的 SELECT 语句以及该语句 的结果集。大概来讲,就是将 SELECT 语句和语句的结果做了一个 HASH 映 射关系然后保存在一定的内存区域中。
  • 66. 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) 则在锁定时刻仍然允许读取该表
  • 67. 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 模块进行执 行计划的分析优化,更不许要发生任何存储引擎的交互,减少
  • 68. 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 不仅不能提高效 率,反而可能造成负面影响。
  • 69. 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 ,这也是业界比较常用的做法。
  • 70. QUERY CACHE 优化 • MySQL Cluster 是否可以使用 Query Cache ?可以 使用 其实在我们的生产环境中也没有使用 MySQL Cluster , 所以我也没有在 MySQL Cluster 环境中使用 Query Cache 的实际经验,只是 MySQL 文档中说明确实可以 在 MySQL Cluster 中使用 Query Cache 。从 MySQL Cluster 的原理来分析,也觉得应该可以使用,毕竟 SQL 节点和数据节点比较独立,各司其职,只是 Cache 的失效机制会要稍微复杂一点。
  • 71. 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
  • 72. 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
  • 73. 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)
  • 74. 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 ,因为首先变长字段 存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字 段内搜索效率显然要高些。
  • 75. 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./* 使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来 解决问题,基于集的方法通常更有效。 */
  • 76. MySQL 中优化 sql 语句查询 常用的 30 种方法 • 27. 与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是 在必须引用几个表才能获得所需的数据时。在结果集中包括“合 计”的例程通常要比使用游标执行的速度快。如果开发时间允许 ,基于游标的方法和基于集的方法都可以尝试一下,看哪一种 方法的效果更好。 28. 在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过 程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息 。 29. 尽量避免向客户端返回大数据量,若数据量过大,应该考虑 相应需求是否合理。 30. 尽量避免大事务操作,提高系统并发能力。
  • 77. Query 优化基本原则 • 永远用小结果集驱动大的结果集; • 尽可能在索引中完成排序; • 只取出自己需要的 Columns ; • 仅仅使用最有效的过滤条件; • 尽可能避免复杂的 Join 和子查询;能不 使用子查询就不使用子查询 • 多使用 profile
  • 80. 5. 常用存储引擎优化 • 1.MYISAM 存储引擎优化 • 2.INNODB 存储引擎优化
  • 81. 5.1 MYISAM 存储引擎优化 • 1. 索引缓存优化 • 2. 多 KEY CACHE 的使用 • 3.KEY CACHE 的互斥( MUTEX )问题 • 4.KEY CACHE 预加载 • 5.NULL 值对统计信息的影响 • 6. 表读取缓存优化 • 7. 并发优化 • 8. 其他可以优化的地方
  • 82. 补充: Mysql 数据库命名规 范 • 主键、外键、索引、视图、函数、过程、触发器等的命名规范 • 此要求必须严格执行 • 主键:使用 pk_ 前缀 , 前缀名称一般不超过 5 字 • 外键:使用 fk_ 前缀 , 前缀名称一般不超过 5 字 • 索引:使用 idx_ 前缀 , 前缀名称一般不超过 5 字 • 视图:使用 v_ 前缀 , 前缀名称一般不超过 5 字 • 函数:使用 f_ 前缀 , 前缀名称一般不超过 5 字 • 过程:使用 p_ 前缀 , 前缀名称一般不超过 5 字 • 触发器:使用 t_ 前缀 , 前缀名称一般不超过 5 字
  • 83. 两天性能测试中发现的问题 • 1. 在数据库中进行大排序 • 2. 系统中索引的使用率较低 • 3. 多表连接性能低 • 4. 数据库对象命名不规范 • 5. 数据库设计不遵循 3NF • 6. 产品中存在锁争用的问题 • 7. 产品中存在循环次数不正确的地方
  • 84. MYSQL 中的大排序 • 在公告功能测试中,后台出现了锁等待 , 造成的原因是对 40 万的数据进行 了排序 • Select * from onlinemessage order by sendDate;
  • 86. 没有使用索引的例子 • select id,businesstype,docid,state,userid,createtime,typeflag from fulltextsearch_info where ?=? and businesstype = ? and docid = ?;
  • 88. 多表连接 • 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_memb er.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
  • 90. 多表连接• 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_membe • ) • WHERE
  • 91. 美 成都丽 感 的 注谢您 关 haoxp20110515