Mysql 高级
1 MySQL 的架构介绍
1.1 sql_mode
sql_mode 是一个容易忽视的变量,默认情况下为空,可以忍耐一些非法操作,在生产环境中,必须将其设置为严格模式,在开发测试环境中配该变量也是很有必要的,因为这样可以在生产之前发现问题。
sql_mode 常用值如下:
- ONLY_FULL_GROUP_BY:对于 GROUP BY 聚合操作,如果在 SELECT 中的列没有在 GROUP BY 中出现,那么这个 sql 是不合法的
- NO_AUTO_VALUE_ON_ZERO:该值影响自增列的插入,默认情况下,插入 0 或 NULL 代表生成下一个增长值,如果用户希望插入的主键 ID 为0,就可以配置这个值
- STRICT_TRANS_TABLES:如果一个值不能插入到一个事务表中,则中断当前的操作,非事务表不限制
- NO_ZERO_IN_DATE:在严格模式下,不允许日期和月份为零
- NO_ZERO_DATE:不允许插入零日期,否则抛出异常
- ERROR_FOR_DIVISION_BY_ZERO:在插入或更新过程中,如果数据被零除,则产生错误,如果未配置该参数,那么数据被零除时返回 NULL
- NO_AUTO_CREATE_USER:禁止创建密码为空的用户
- NO_ENGINE_SUBSTITUTION:如果需要的存储引擎未编译或被禁用,则抛出错误
- PIPES_AS_CONCAT:将 || 视为字符串连接操作符
- ANSI_QUOTES:配置该参数后,不能用 "" 引用字符串
1.2 MySQL 逻辑架构
和其它数据库相比,MySQL 有点与众不同,它的架构可以在多种不同场景中应用并发挥良好作用,主要体现在存储引擎的架构上,插件式的存储引擎架构将查询处理和其它的系统任务以及数据的存储提取相分离,这种架构可以根据业务的需求和实际需要选择合适的存储引擎。
连接层
最上层是客户端和连接服务,包含本地 socket 通信和 tcp/ip 通信,主要完成连接处理、授权认证及相关的安全方案,该层引入了线程池,为授权用户提供线程,还实现了 ssl 安全链接。
服务层
- Management Serveices & Utilities:系统管理和控制工具
- SQL Interface:SQL 接口,接收 SQL 命令,并返回查询结果
- Parser:解析器,对 SQL 命令进行验证和解析
- Optimizer:查询优化器,在查询之前对语句进行优化
- Cache 和 Buffer:查询缓存,如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据,这个缓存机制是由一系列小缓存组成的,比如表缓存,记录缓存,key缓存,权限缓存等
引擎层
存储引擎层,负责了数据的存储和提取,服务器通过 API 与存储引擎进行通信。
存储层
数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
1.2.1 SQL 的执行周期
开启诊断分析工具
set profiling=1;
显示最近的几条查询
show profiles;
查看 SQL 的执行步骤
show profile cpu,block io for query 1;
1.2.2 查询流程
- 客户端和 MySQL 建立连接,发送查询语句,先查询缓存,如果缓存有命中,直接返回结果,否则进行语句解析
- 也就是说,解析之前先访问缓存,解析器将对语句进行语法规则校验和解析查询,然后会生成一颗解析树
- 之后由优化器将解析器转换成执行计划,最后执行计划,返回结果
1.2.3 SQL 执行顺序
FROM ON JOIN WHERE GROUP BYHAVING SELECTDISTINCT ORDER BY LIMIT
1.3 MySQL 存储引擎
查看支持的存储引擎
show engines;
查看当前默认的存储引擎
show variables like '%storage_engine%';
1.3.1 各个引擎简介
InnoDB
InnoDB 是 MySQL 默认的事务型引擎,用来处理大量的短期事务,除非有特别的原因需要用到其他存储引擎,否则优先考虑 InnoDB。
MyISAM
MyISAM 提供了大量的特性,包括全文检索、压缩、空间函数等,但 MyISAM 不支持事务和行级锁,缺点是崩溃后无法安全恢复。
Archive
Archive 档案存储引擎只支持 INSERT 和 SELECT 操作,在 MySQL5.1 之前不支持索引;
Archive 表适合日志和数据采集类应用;
根据英文的测试结论来看,Archive 表比 MyISAM 表要小大约 75%,比支持事务处理的 InnoDB 表小大约 83%。
Blackhole
Blackhole 引擎没有实现任何存储机制,它会丢弃所有插入的数据,不做任何保存。但服务器会记录Blackhole表的日志,所以可以用于复制数据到备库,或者简单地记录到日志。但这种应用方式会碰到很多问题,因此并不推荐。
CSV
CSV 引擎可以将普通的 CSV 文件作为 MySQL 表来处理,但不支持索引, CSV 可以作为一种数据交换的机制,CSV 引擎存储的数据可以被文本编辑器、execl 读取。
Memory
如果需要快速地访问数据,并且这些数据不会被修改,重启以后丢失也没有关系,那么使用Memory表是非常有用,Memory 表至少比 MyISAM 表要快一个数量级。
Federated
Federated 引擎是访问其他 MySQL 服务器的一个代理,尽管该引擎看起来提供了一种很好的跨服务器的灵活性,但也经常带来问题,因此默认是禁用的。
1.3.2 InnoDB 和 MyISAM
对比项 | InnoDB | MyISAM |
---|---|---|
外键 | 支持 | 不支持 |
事务 | 支持 | 不支持 |
行表锁 | 行锁,操作时只锁定操作的那一行,不会对其他行产生影响,适合于高并发 | 表锁,即使只操作一行也会锁定整个表,不适合高并发 |
缓存 | 不仅缓存索引还要缓存真实数据,对内存要求较高,而且内存大小对性能有决定性的影响 | 只缓存索引,不缓存真实数据 |
关注点 | 并发写、事务、更大资源 | 节省资源、消耗少、简单业务 |
默认安装 | Y | Y |
默认使用 | Y | N |
自带系统表使用 | N | Y |
2 索引优化分析
2.1 优化步骤
分库分表
SQL 优化
建立索引
调整 my.cnf 优化服务器及配置参数
2.2 索引简介
2.2.1 什么是索引?
数据本身之外,数据库还维护着一个满足特定查找算法的数据结构,这些数据结构以某种方式指向数据,这样就可以在这些数据结构的基础上实现高级查找算法,这种数据结构就是索引;
一般来说索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上;
虽然索引提高了查询的效率,但是也降低了更新的效率,因为更新表时,不仅要插入数据,同时还要保存一下索引文件每次更新添加了的索引列的字段,都会调整因为更新所带来的键值变化后的索引信息;
实际上索引也是一张表,该表保存了主键与索引字段,并指向实体表的记录,所以索引列也是要占用空间的。
2.2.2 MySQL 索引结构
2.2.2.1 BTree 索引
如图所示,磁盘块 1 包含数据项 17 和 35,包含指针 P1、P2、P3
P1 表示小于 17 的磁盘块,P2 表示介于 17 和 35 之间的磁盘块,35 表示大于 35 的磁盘块
查找过程
如果要查找数据项 29,首先将磁盘块 1 加载到内存,此时发生一次 IO,利用二分查找确定 29 在 17 和 35 之间,锁定磁盘块 1 的 P2 指针,通过磁盘块 1 的 P2 指针的磁盘地址把磁盘块 3 加载到内存,此时发生一次 IO,利用二分查找确定 29 在26 和 30 之间,锁定磁盘块 3 的 P2 指针,通过磁盘块 3 的 P2 指针的磁盘地址把磁盘块 8 加载到内存,此时发生一次 IO,同时利用二分查找到 29,查询结束。
2.2.2.2 B+Tree 索引
B+ 树的非叶子节点只是存储 key,占用空间非常小,因此每一层的节点能索引到的数据范围更加的广,换句话说,每次 IO 操作可以观看更多的数据;
叶子节点两两相连,符合磁盘的预读特性。如图存储 5、8 、9 的叶子节点,它有个指针指向了 10、15、18 这个叶子节点,那么当我们从磁盘读取5、8、9 对应的数据的时候,由于磁盘的预读特性,会顺便把 10、15、18 对应的数据读取出来,这个时候属于顺序读取,而不是磁盘寻道了,加快了速度;
支持范围查询,而且部分范围查询非常高效,原因是数据都是存储在叶子节点这一层,并且有指针指向其他叶子节点,这样范围查询只需要遍历叶子节点这一层,无需整棵树遍历。
2.2.2.3 聚簇索引与非聚簇索引
聚簇索引并不是一种单独的索引类型,而是一种数据存储方式,聚簇表示数据行和相邻的键值聚簇的存储在一起;
按照聚簇索引排列顺序,查询显示一定范围数据的时候,由于数据都是紧密相连,数据库不不用从多个数据块中提取数据,所以节省了大量的 IO 操作;
对于 MySQL 数据库目前只有 InnoDB 数据引擎支持聚簇索引,而 MyISAM 并不支持聚簇索引;
由于数据物理存储排序方式只能有一种,所以每个 MySQL 的表只能有一个聚簇索引,一般情况下就是该表的主键;
为了充分利用聚簇索引的聚簇的特性,所以 InnoDB 表的主键列尽量选用有序的顺序 ID,而不建议用无序的 ID,比如 UUID这种。
2.2.3 MySQL 索引分类
2.2.2.3.1 单值索引
即一个索引只包含单个列,一个表可以有多个单列索引
随表一起建索引: CREATE TABLE customer ( id INT (10) UNSIGNED AUTO_INCREMENT, customer_no VARCHAR (200), customer_name VARCHAR (200), PRIMARY KEY (id), KEY (customer_name));单独建单值索引: CREATE INDEX idx_customer_name ON customer (customer_name);删除索引: DROP INDEX idx_customer_name ON customer;
2.2.3.2 唯一索引
索引列的值必须唯一,但可以为空
随表一起建索引: CREATE TABLE customer ( id INT (10) UNSIGNED AUTO_INCREMENT, customer_no VARCHAR (200), customer_name VARCHAR (200), PRIMARY KEY (id), KEY (customer_name), UNIQUE (customer_no));单独建唯一索引: CREATE UNIQUE INDEX idx_customer_no ON customer (customer_no);删除索引: DROP INDEX idx_customer_no ON customer;
2.2.3.3 主键索引
设为主键后自动创建主键索引
随表一起建索引: CREATE TABLE customer ( id INT (10) UNSIGNED AUTO_INCREMENT, customer_no VARCHAR (200), customer_name VARCHAR (200), PRIMARY KEY (id));单独建主键索引: ALTER TABLE customer ADD PRIMARY KEY customer (customer_no);删除建主键索引: ALTER TABLE customer DROP PRIMARY KEY;修改建主键索引: 必须先删除掉 (DROP) 原索引,再新建 (ADD) 索引
2.2.3.4 复合索引
一个索引包含单个列
随表一起建索引: CREATE TABLE customer ( id INT (10) UNSIGNED AUTO_INCREMENT, customer_no VARCHAR (200), customer_name VARCHAR (200), PRIMARY KEY (id), KEY (customer_name), UNIQUE (customer_name), KEY (customer_no, customer_name));单独建索引: CREATE INDEX idx_no_name ON customer (customer_no, customer_name);删除索引: DROP INDEX idx_no_name ON customer;
2.2.4 创建索引的时机
哪些情况需要创建索引?
- 主键自动创建唯一索引
- 频繁作为查询条件的字段应该创建索引
- 查询中与其他表关联的字段,外键关系建立索引
- 组合索引比单值索引性价比更高
- 查询排序的字段,排序字段若通过索引去访问将大大提高排序速度
- 查询中统计或分组字段
哪些情况不需要创建索引?
- 表记录太少
- 经常更新的表
- where 条件里用不到的字段不需要创建索引
- 过滤性不好的字段不需要创建索引
2.3 性能分析
2.3.1 EXPLAN
使用 EXPLAIN 关键字可以模拟优化器执行 SQL 查询语句,从而知道 MySQL 是如何处理 SQL 语句的,分析查询语句或是表结构的性能瓶颈。
EXPLAN 的作用:
查看表的读取顺序
查看哪些索引可以被使用
数据读取操作的操作类型
哪些索引被实际使用
表之间的引用
使用方式:
Explain + SQL
Explain SELECT * FROM t_emp A LEFT JOIN t_dept B ON A.deptId = B.id WHERE B.`id` IS NULL -> UNION -> SELECT * FROM t_emp A RIGHT JOIN t_dept B ON A.deptId = B.id WHERE A.`deptId` IS NULL;
2.3.2 各字段解释
id:
SELECT 查询的序列号,包含一组数字,表示查询中执行 SELECT 子句或操作表的顺序
id 相同:执行顺序由上至下
id 不同:如果是子查询,id 的序号会递增,id 值越大优先级越高,越先被执行
- id 相同不同,同时存在:id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行
每个 id 表示一趟独立的查询,一个 SQL 的查询趟数越少越好
select_type
查询的类型,主要是用于区别普通查询、联合查询、子查询等的复杂查询
SIMPLE:最简单的查询,不包含 UNION 和子查询
PRIMARY:查询中若包含复杂的子部分,最外层查询被标记为 PRIMARY
DERIVED:在 FROM 列表中包含的子查询被标记为 DERIVED,MySQL 会递归执行这些子查询, 把结果放在临时表里
SUBQUERY:在 SELECT 或 WHERE 列表中包含子查询
DEPENDENT SUB:在 SELECT 或 WHERE 列表中包含子查询,子查询基于外层
UNCACHEABLE SUBQUREY:结果集不能被缓存的子查询,必须重新为外层查询的每一行进行评估
UNION:若第二个 SELECT 出现在 UNION 之后,则被标记为 UNION
- UNION RESULT:从 UNION 表获取结果的 SELECT
table
显示这一行的数据是关于哪张表的
type
显示连接使用的类型,按最优到最差的类型排序
system:表只有一行记录
const:表示通过索引一次就找到了,const 用于比较 primary key 或者 unique 索引,因为只匹配一行数据,所以很快
如将主键置于 where 列表中,MySQL 就能将该查询转换为一个常量eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配,常见于主键或唯一索引扫描
ref:非唯一性索引扫描,返回匹配某个单独值的所有行,本质上也是一种索引访问,它返回所有匹配某个单独值的行,然而,它可能会找到多个符合条件的行,所以它应该属于查找和扫描的混合体
range:只检索给定范围的行,使用一个索引来选择行,key 列显示使用了哪个索引,一般就是在 where 语句中出现了 between、<、>、in 等的查询,这种范围扫描索引扫描比全表扫描要好,因为它只需要开始于索引的某一点,而结束语另一点,不用扫描全部索引
index:出现 index 是 SQL 使用了索引但是没用通过索引进行过滤,一般是使用了覆盖索引或者是利用索引进行了排序分组
all:Full Table Scan,将遍历全表以找到匹配的行
index_merge:在查询过程中需要多个索引组合使用,通常出现在有 or 的关键字的 SQL 中
ref_or_null:对于某个字段既需要关联条件,也需要 null 值的情况下,查询优化器会选择用 ref_or_null 连接查询
index_subquery:利用索引来关联子查询,不再全表扫描
- unique_subquery:该联接类型类似于 index_subquery,子查询中的唯一索引
一般来说,得保证查询至少达到 range 级别,最好能达到 ref
possible_keys
显示可能应用在这张表中的索引,一个或多个,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询实际使用
key
实际使用的索引,如果为NULL,则没有使用索引,查询中若使用了覆盖索引,则该索引和查询的 select 字段重叠
key_len
表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度, key_len 字段能够检查是否充分的利用上了索引
ref
显示索引的哪一列被使用了,如果可能的话,是一个常数,哪些列或常量被用于查找索引列上的值
rows
rows 列显示 MySQL 认为它执行查询时必须检查的行数
Extra
包含不适合在其他列中显示但十分重要的额外信息
- Using filesort:说明 mysql 会对数据使用一个外部的索引排序,而不是按照表内的索引顺序进行读取,MySQL 中无法利用索引完成的排序操作称为文件排序
- Using temporary:使了用临时表保存中间结果,MySQL 在对查询结果排序时使用临时表,常见于排序 order by 和分组查询 group by
- USING index:表示相应的 select 操作中使用了覆盖索引(Covering Index),避免访问了表的数据行,效率不错,如果同时出现 using where,表明索引被用来执行索引键值的查找,如果没有同时出现 using where,表明索引只是用来读取数据而非利用索引执行查找
- Using where:表明使用了 where 过滤
- using join buffer:使用了连接缓存:
- impossible where:where 子句的值总是 false,不能用来获取任何元组
- select tables optimized away:在没有 GROUP BY 子句的情况下,基于索引优化 MIN/MAX 操作或者对于MyISAM 存储引擎优化 COUNT(*) 操作,不必等到执行阶段再进行计算,查询执行计划生成的阶段即完成优化。
2.4 查询优化
2.4.1 单表使用索引失效问题
index(a,b,c)
Where语句 | 索引是否被使用 |
---|---|
WHERE a = 3 | y,使用到 a |
WHERE a = 3 AND b = 5 | y,使用到 a、b |
WHERE a = 3 AND b = 5 AND c = 4 | y,使用到 a、b、c |
WHERE b = 3、WHERE b = 3 AND c = 4、WHERE c = 4 | n |
WHERE a = 3 AND c = 5 | y,使用到 a,b中断了 |
WHERE a = 3 AND b > 4 AND c = 5 | y,使用到 a,b 中断了 |
WHERE a IS NULL AND b IS NOT NULL | is null 支持索引 但是is not null 不支持,所以 a 可以使用索引,b 不可以使用索引 |
WHERE a <> 3 | <> 不能使用索引 |
WHERE abs(a) = 3 | abs 不能使用索引 |
WHERE a = 3 AND b LIKE 'kk%' AND c = 4 | y,使用到 a、b、c |
WHERE a = 3 AND b LIKE '%kk' AND c = 4 | y,使用到 a |
WHERE a = 3 AND b LIKE '%kk%' AND c = 4 | y,使用到 a |
WHERE a = 3 AND b LIKE 'k%kk%' AND c = 4 | y,使用到 a、b、c |
创建索引的建议:
对于单值索引,尽量选择针对当前查询过滤性更高的字段
选择组合索引,当前查询过滤性最高的字段在索引的位置越靠前越好
选择组合索引,尽量选择可以能够包含当前查询中的 where 字句中更多字段的索引
在选择组合索引的时候,如果某个字段可能出现范围查询时,尽量把这个字段放在索引次序的最后面
2.4.2 关联查询优化
保证被驱动表的 join 字段已经被索引
left join 时,选择小表作为驱动表,大表作为被驱动表
inner join 时,MySQL 会自己把小结果集的表选为驱动表
子查询尽量不要放在被驱动表,有可能使用不到索引
能够直接多表关联的尽量直接关联,不用子查询
2.4.3 子查询优化
尽量不要使用not in 或者 not exists,用 left join on xxx is null 替代
2.4.4 排序分组优化
ORDER BY子句,尽量使用Index方式排序,避免使用FileSort方式排序
如果不在索引列上,filesort 有两种算法:
双路排序
- MySQL 4.1 之前是使用双路排序,字面意思就是两次扫描磁盘,最终得到数据,读取行指针和 order by 列,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出
- 从磁盘取排序字段,在 buffer 进行排序,再从磁盘取其他字段
- 取一批数据,要对磁盘进行了两次扫描,众所周知,IO 是很耗时的,所以在 MySQL 4.1 之后,出现了第二种改进的算法,就是单路排序
单路排序:
- 从磁盘读取查询需要的所有列,按照 order by 列在 buffer 对它们进行排序,然后扫描排序后的列表进行输出,它的效率更快一些,避免了第二次读取数据,并且把随机 IO 变成了顺序 IO,但是它会使用更多的空间,因为它把每一行都保存在内存中了
- 单路排序是把所有的字段都取出,所以有可能取出的数据的总大小超过了 sort_buffer 的容量,导致每次只能取 sort_buffer 容量大小的数据进行排序,排序完再取一部分,反而增大了 IO
- 优化策略
- 增大sort_buffer_size参数的设置
- 增大max_length_for_sort_data参数的设置
- 减少 select 后面的查询的字段
- 禁止使用 select *
group by 使用索引的原则几乎跟 order by 一致 ,唯一区别是 group by 即使没有过滤条件用到索引,也可以直接使用索引
3 查询截取分析
什么是慢查询日志?
慢查询日志是 MySQL 提供的一种日志记录,它用来记录在 MySQL 中响应时间超过阈值的语句,具体指运行时间超过long_query_time 值的 SQL,则会被记录到慢查询日志中;
long_query_time 的默认值为10,意思是运行10秒以上的语句。
默认慢查询日志是关闭的,需要手动开启
查看慢查询日志是否开启SHOW VARIABLES LIKE '%slow_query_log%';开启慢查询日志set global slow_query_log=1;
查看并配置 long_query_time
查看long_query_timeSHOW VARIABLES LIKE 'long_query_time%';set long_query_time=1
日志分析工具 mysqldumpslow
常用参考:
hadoop100得到返回记录集最多的10个SQLmysqldumpslow -s r -t 10 /var/lib/mysql/hadoop100-slow.log得到访问次数最多的10个SQLmysqldumpslow -s c -t 10 /var/lib/mysql/hadoop100-slow.log得到按照时间排序的前10条里面含有左连接的查询语句mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/hadoop100-slow.log另外建议在使用这些命令时结合 | 和more 使用 ,否则有可能出现爆屏情况mysqldumpslow -s r -t 10 /var/lib/mysql/hadoop100-slow.log | more
4 主从复制
复制的基本原理
master 将改变记录到二进制日志(binary log),这些记录过程叫做二进制日志事件,binary log events;
slave 将 master 的 binary log events 拷贝到它的中继日志(relay log);
slave 重做中继日志中的事件,将改变应用到自己的数据库中,MySQL 复制是异步的且串行化的。
复制的基本原则
每个 slave 只有一个 master
每个 slave 只能有一个唯一的服务器 ID
每个 master 可以有多个salve
4.1 配置主从复制
1、配置主数据库
vim /etc/my.cnf
server-id=1log-bin=mysql-binbinlog_format=mixed
为从服务分配账号
查看主服务器 BIN 日志的信息
show master status;
重启主数据库
systemctl restart mariadb
2、配置从数据库
连接主数据库
CHANGE MASTER TO -> MASTER_HOST="192.168.10.100", -> MASTER_USER="slave", -> MASTER_PASSWORD="123456", -> MASTER_LOG_FILE="mysql-bin.000001", -> MASTER_LOG_POS=388;
启动从数据库
start slave;