MySQL 数据库进阶 0611 完成 0620
串起来的内容简述。
MySQL 事务中 redo log、undo log 理解,undo log 实现 AC,同时 MVCC(RC、RR)下基于版本链创建规则实现 I,redo log 实现 D;索引对组合索引最左前缀研究,基本掌握索引优化思路;引擎 InnoDB buff pool 中脏页、空闲页、LRU 链表与 checkpoint 联系;隔离级别 RR 下 X 锁 SQL 分析,并与索引类型、是否命中三者组合;调优方案中慢查询、explain 不同列、profile 性能实操;分库分表、集群等具有实战意义的概念。
没有想象中简单,甚至一时不知从何开始笔记,难熬的几天。参考书籍,从 MySQL 必知必会 -> 高性能 MySQL,即基础篇 -> 进阶篇。加锁问题多 session 事务跑 SQL,其设计逻辑清晰后可解决(博主水平有限,若有纰漏,欢迎探讨);再者包括 SQL 语句性能调优,从慢查询日志、explain 到 profile;以及对集群主从复制,中间件实现分库分表、读写分离的理解(由于没在虚拟机,必要时候再学,以补充 MyCAT 集群实战,包括一主一从、高可用双主集群),内容如下:
前情提要——MySQL 数据库基础
MySQL 数据库基础 0606 完成 0610
出发点是先有体系,再理解底层。0606 搭建环境,熟悉 MySQL 语法及规范,本地连接云服务器,解决 DataGrip 关键字默认 upper、console 无法中文输入等问题。紧接着 down 例子,练手过基础内容,最终同步的整理笔记完成。
除数据库以外,知识体系完整,较夯实。包括接触数据库锁概念,发现其等同进程间通信 IPC cond 与 mutex 对数据加锁,故死锁问题考虑,初步判断,底层实现逻辑相同(理解相同点、不同点)。进度较快,但计划时间有限,提醒自己务必注重学习效率。
计划——下一阶段 MySQL 进阶(包括 MySQL 架构、引擎&事务&索引、锁、锁&分库分表、集群、高可用),深入探索底层原理,以及之后 Redis、MongoDB 等择其一 Redis,理解非关系型数据库,与 MySQL 关系型数据库对比。数据库整体目标,首先具备实战能力,其次重点理解、掌握知识点:
MySQL 物理文件
数据文件
日志文件
MySQL 软件架构
MySQL 执行流程
MySQL 存储引擎
InnoDB 存储引擎
事务概念
隔离级别
持久性是如何实现的?
一致性和原子性是如何实现的?
MVCC 机制如何实现?(讨论的 MVCC 是在 RC、RR 隔离级别下,执行 select 操作后访问记录版本链过程)
undo log 版本链
read view(活跃事务 ID,按照顺序存放,最低事务 ID(up),最高事务 ID(low))
MVCC 下的读操作
索引分类
索引使用
索引的数据结构
B+ 树的特点
InnoDB 的索引结构(B+ 树)
InnoDB 每张表必须有一个主键索引
覆盖索引
组合索引
索引下推 ICP(index condition pushdown)
大家有没有一个疑问,MySQL 在使用组合索引在检索数据时是使用最左前缀原则来定位记录,那不满足最左前缀的索引列,MySQL 会怎么处理?
使用 explain 工具,查看执行计划,extra 列中的“Using index condition”执行器表示使用了索引条件下推 ICP,若只有“Using where”执行器表示没有使用索引条件下推 ICP。具体实现,见下一篇章——InnoDB 内存篇
LRU(least recently used)
索引优化建议
buffer pool
page(页):数据页、索引页、undo 页
buffer pool:内存初始化的时候,pool 初始化大小:8192 个页(空闲页)
total pages:总页数(默认 8192 页)
空闲页链表(free page):未被使用的页
LRU 链表(data page):已使用的页(从磁盘中加载出来的数据,都会占用空闲页)
脏页链表(dirty page):已使用的页里面,有些页面数据已经被修改,所以产生的脏页也会单独存储
fuzzy checkpoint:
LRU 链表:young(八分之五)、old(八分之三),为了解决劣币驱逐良币的现象
where 条件(结合组合索引的最左前缀匹配原则来理解)
全局锁
表级锁
读锁(共享锁):S
写锁(排他锁):X
S + S 不堵塞,其它都堵塞
是由 MySQL server 层实现的,不是存储引擎层实现的
MDL:元数据锁,MySQL 自动加的,我们人为不能加 MDL
行级锁
死锁
死锁场景 1:单主键索引。会话 1 锁定记录,会话 2 锁定另一个记录。之后会话 1 操作会话 2 锁定的记录,会话 2 操作会话 1 锁定的记录,造成互相等待。
死锁场景 2:主键索引 + 两个辅助索引。辅助索引树 1 本应锁定两条,辅助索引树 2 本应锁定两条。对应主键索引为相同记录,锁定记录的先后顺序情况,辅助索引树 1 和辅助索引树 2 的第二条锁定互相等待。(RC 替代 RR 来简化场景,帮助理解)
如何避免死锁?
性能优化的思路
慢查询日志(重要)
慢查询日志介绍
开启慢查询功能
查询是否开启慢查询功能
SHOW VARIABLES LIKE '%slow_query%'; # 查看慢查询是否开启,以及文件所在位置
SHOW VARIABLES LIKE '%long_query_time%'; # 查看慢查询当前时间阈值
临时开启慢查询功能
在 MySQL 执行 SQL 语句设置,但是如果重启 MySQL 的话将失效
SET GLOBAL SLOW_QUERY_LOG = on; # 当前采用的开启方式
SET SESSION LONG_QUERY_TIME = 1;
永久开启慢查询
这里的配置重点是我找了好久,在 /etc/ 目录下并没有找到 my.cnf 文件(其他在 /etc/my.cnf 的操作,与该步骤同),也没找到替代文件 my-default.cnf 文件。由于下载的 5.7 最新版本(5.7.34),就在想是不是版本变动,后来才发现,从 5.7.18 开始官方不再二进制包中提供 my-default.cnf 文件。具体参考:https://dev.mysql.com/doc/refman/5.7/en/binary-installation.html,总结如下,以前的需要配置 /etc/my.cnf 的操作,配置 /etc/mysql/mysql.conf.d 即解决
所以修改 /etc/mysql/mysql.conf.d/mysqld.cnf 配置文件,重启 MySQL,这种永久生效
# 可见文件注释语句,取消注释并按自己的需求配置
# Here you can see queries with especially long duration
# slow_query_log = 1
# slow_query_log_file = /var/log/mysql/mysql-slow.log
# long_query_time = 2
# log-queries-not-using-indexes
# 重启 MySQL 数据库
sudo service mysql restart
慢查询日志格式
SELECT sleep(3); # 创建大于阈值 1s 的查询语句
# Time: 2021-06-17T05:54:05.120219Z
# User@Host: root[root] @ [115.236.71.98] Id: 450
# Query_time: 3.000247 Lock_time: 0.000000 Rows_sent: 1 Rows_examined: 0
use information_schema;
SET timestamp=1623909245;
/* ApplicationName=DataGrip 2021.1.3 */ SELECT SLEEP(3);
格式说明
分析慢查询日志的工具
使用 mysqldumpslow 工具,mysqldumpslow 是 MySQL 自带的慢查询日志工具
# -s:表示按照何种方式排序
# -t:表示 top n,即返回前面多少条的数据
# -g:后面可以写一个正则匹配模式,大小写不敏感
mysqldumpslow -s t -t 10 -g 'SLEEP' /var/lib/mysql/aliyun-slow.log # 终端执行
# mysqldumpslow 工具查询结果
Reading mysql slow query log from /var/lib/mysql/aliyun-slow.log
Count: 1 Time=3.00s (3s) Lock=0.00s (0s) Rows=1.0 (1), root[root]@[115.236.71.98]
/* ApplicationName=DataGrip N.N.N */ SELECT SLEEP(N)
Died at /usr/bin/mysqldumpslow line 167, <> chunk 1.
EXPLAIN 查看执行计划(重要)
EXPLAIN 命令介绍
EXPLAIN 命令使用
EXPLAIN 各列的含义
例子脚本
CREATE TABLE tuser (
id int PRIMARY KEY AUTO_INCREMENT,
name varchar(100),
age int,
sex char(1),
address varchar(100)
);
ALTER TABLE tuser ADD INDEX idx_name_age(name(100), age);
ALTER TABLE tuser ADD INDEX idx_sex(sex(1));
INSERT INTO tuser(id, name, age, sex, address) VALUES (1, 'laohu', 20, '1', '北京');
INSERT INTO tuser(id, name, age, sex, address) VALUES (2, 'xuance', 16, '1', '上海');
INSERT INTO tuser(id, name, age, sex, address) VALUES (3, 'hanxin', 34, '1', '杭州');
INSERT INTO tuser(id, name, age, sex, address) VALUES (4, 'yuji', 26, '2', '广州');
INSERT INTO tuser(id, name, age, sex, address) VALUES (5, 'daji', 18, '2', '上海');
# 例如对上表跑以下 SQL,查看结果
EXPLAIN SELECT * FROM tuser WHERE id = 1;
# SELECT * FROM tuser a;
# SELECT * FROM tuser b WHERE b.id = a.id;
EXPLAIN SELECT * FROM tuser a LEFT JOIN tuser b ON a.id = b.id; # 如上
EXPLAIN SELECT * FROM tuser WHERE name = 'laohu' AND age = 20;
EXPLAIN SELECT * FROM tuser WHERE sex = '1';
EXPLAIN SELECT * FROM tuser;
EXPLAIN SELECT id, name, age FROM tuser;
EXPLAIN SELECT * FROM tuser WHERE name = 'laohu' ORDER BY sex; # Using filesort 需要优化
SQL 语句优化(重要)
索引优化
LIMIT 优化(MySQL 特有的关键字)
如果预计 select 语句的查询结果是一条,最好使用 LIMIT 1,可以停止全表扫描
SELECT * FROM tuser WHERE name = 'laohu'; # username 没有建立唯一索引
SELECT * FROM tuser WHERE name = 'laohu' LIMIT 1;
处理分页会使用到 LIMIT,当翻页到非常靠后页面的时候,偏移量会非常大,这时 LIMIT 的效率会非常差
SELECT * FROM (SELECT * FROM tuser WHERE id > 1000000 AND id <1000500 ORDER BY id) t LIMIT 0, 20
其他查询优化
profile 分析语句
介绍
开启 profile 功能
profile 功能由 MySQL 会话变量:profiling 控制,默认是 OFF 关闭状态
查看是否开启了 profile 功能
SELECT @@profiling;
# 或者
SHOW VARIABLES like '%profil%';
开启 profile 功能
SET profiling = 1; # 1 是开启, 0 是关闭
profile 使用
SHOW PROFILE;
SHOW PROFILES;
SHOW PROFILE CPU, BLOCK IO;
SHOW PROFILE ALL;
performance_schema 性能分析(当前发展,进阶选择)
服务器层面优化
缓冲区优化(较重要)
将数据保存在内存中,保证从内存读取数据
设置足够大的 innodb_buffer_pool_size,将数据读取到内存。建议设置为总内存的 3/4 或者 4/5
怎么确定 Innodb_buffer_pool_size 足够大。数据是从内存读取而不是硬盘?
SHOW GLOBAL STATUS LIKE 'innodb_buffer_pool_pages_%';
# 查询结果
# Innodb_buffer_pool_pages_data, 594
# Innodb_buffer_pool_pages_dirty, 0
# Innodb_buffer_pool_pages_flushed, 5326
# Innodb_buffer_pool_pages_free, 7597 # 为 0 则表示 buffer pool 已用完
# Innodb_buffer_pool_pages_misc, 1
# Innodb_buffer_pool_pages_total, 8192
降低磁盘写入次数(较重要)
MySQL 数据库配置优化(较重要)
操作系统优化
服务器硬件优化
提升硬件设备,例如选择尽量高频率的内存(频率不能高于主板的支持)、提升网络带宽、使用 SSD 告诉磁盘、提升 CPU 性能等
例如 CPU 的选择
为什么要分库分表
数据切分(sharding)方案
数据的切分(sharding)根据其切分规则的类型,可以分为两种切分模式
如何分库分表
切分规则
切分原则
分库分表需要解决的问题
分库分表实现技术
分布式 ID 都有那些生成方式?
主从复制作用
实现原理
bin log 模式
三种存储格式
statement 模式
row 模式(默认模式)
mixed 模式
bin log 开启与使用
查看 bin log 开启状态
SHOW VARIABLES LIKE 'log_bin';
开启 bin log
这里引用慢查询开启一样的经历,麻了,文件系统 yyds
这里的配置重点是我找了好久,在 /etc/ 目录下并没有找到 my.cnf 文件(其他在 /etc/my.cnf 的操作,与该步骤同),也没找到替代文件 my-default.cnf 文件。由于下载的 5.7 最新版本(5.7.34),就在想是不是版本变动,后来才发现,从 5.7.18 开始官方不再二进制包中提供 my-default.cnf 文件。具体参考:https://dev.mysql.com/doc/refman/5.7/en/binary-installation.html,总结如下,以前的需要配置 /etc/my.cnf 的操作,配置 /etc/mysql/mysql.conf.d 即解决
server-id = 1 # 0620 取消注释
log_bin = /var/log/mysql/mysql-bin.log # 0620 取消注释
expire_logs_days = 10
max_binlog_size = 100M
# binlog_do_db = include_database_name
# binlog_ignore_db = include_database_name
# 重启 MySQL 数据库
sudo service mysql restart
# 查看当前模式
SHOW VARIABLES LIKE 'binlog_format';
# 修改当前模式
SET binlog_format = row; # row 模式
SET binlog_format = statement; # statement 模式
bin log 文件
查看 bin log 内容
mysqlbinlog 工具查看
在 MySQL 命令中使用命令查看
# 查看 bin log 文件列表
SHOW MASTER LOGS;
# 查看 bin log 文件内容
SHOW BINLOG EVENTS IN 'mysql-bin.000002';
基于 bin log 日志点的主从复制
同步数据
配置主服务器
已开启 bin log
# 清空主服务器的 bin log
reset master;
# 查看日志状态
show master status;
创建一个主从复制的用户,授权
# 创建用户,授权
mysql> create user 'slave'@'%' identified by 'Abc_123456';
mysql> grant replication slave on *.* to 'slave'@'%';
配置从服务器
配置文件中增加一个 server-id 即可,不需要开 log_bin
使用 change master to 命令配置主从
# 清除 relay log
reset slave;
# 停止从服务
stop slave;
# change master to 连接
change master to
master_host='192.168.68.132',
master_port=3306,
master_user='slave',
master_password='Abc_123456',
master_log_file='mysql-bin.000003',
master_log_pos=1682,
MASTER_AUTO_POSITION=0;
# MASTER_AUTO_POSITION:不使用GTID
开启主从复制
start slave;
查看主从状态
show slave status \G # \G 只能在命令行,不能在可视化使用
基于 GTID 的主从复制(5.6 之后的版本才有,更方便)
GTID
步骤
数据同步
在主、从服务器配置文件中增加如下配置
#开启 GTID 模式(必选)
gtid_mode=ON
#强制 gtid 一致性(必选)
enforce-gtid-consistency=true
清空主服务器的 bin log 和从服务器的 relay log
# 清除 bin log
reset master;
# 清除 relay log
reset slave;
创建一个主从复制的用户,并授权
# 若已存在,则不需要重复创建
mysql> create user 'slave'@'%' identified by 'Abc_123456';
mysql> grant replication slave on *.* to 'slave'@'%';
从服务器上重新执行 change master to 命令
stop slave;
change master to
master_host='192.168.68.132',
master_port=3306,
master_user='slave',
master_password='Abc_123456',
MASTER_AUTO_POSITION=1;
主从同步延迟原因及优化方案
读写分离
方案一:在 APP 中创建两个数据源(可扩展性差,改源代码)
方案二:使用中间件实现读写分离
Atlas 测试读写分离
# 如何使用,查看官方仓库
mysql -uroot -proot -P1234 --protocol=tcp -e"select @@server_id"
MyCAT 读写分离
基于主从复制的高可用方案
学习笔记,定期回顾,有问题留言。