(万字超详细总结纯手打)MYSQL深度学习分析
Posted 未来窥视者
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了(万字超详细总结纯手打)MYSQL深度学习分析相关的知识,希望对你有一定的参考价值。
mysql
架构演变:
- 单机单库
- 主从架构
- 分库分表
- 云数据库
一、架构原理
1.1 Mysql体系架构
分为四层:网络连接层、服务层、存储引擎层和系统文件层
查询优化器:选取-投影-联接 策略
日志文件
- 错误日志
- 通用查询日志
- 二进制日志(binlog)——记录对数据的修改操作
- 慢查询日志——记录所有超时的执行sql,主要是select,也会有insert等,默认是10S
数据文件
- db.opt
- frm
- myd(myisam)
- myi(myisam)
- ibd and iddata(innodb)独享表空间、共享表空间
- ibdata1(innodb)
- pid
- socket
1.2 Mysql运行机制
描述
- 客户端发起sql
- 查询缓存,有则返回,无则向下
- 解析器解析成解析树
- 预处理器解析为新解析树
- 查询优化器对新解析树分析生成执行计划
- 查询执行引擎按照执行计划通过API接口查询(Myisam或Innodb)
- 查询数据,返回给查询引擎
- 查询执行引擎更新缓存并且返回结果给客户端,结束。
show processlist;--查看连接数
show engines;--5.5开始默认采用Innodb
1.3 存储引擎
1.3.1 Innodb和Myisam对比
- 事务和外键
- 锁机制
- 索引结构
- 并发处理能力
- 存储文件
- 适用场景
总结:事务、并发修改,使用Innodb
快速查询、修改少,使用Myisam
综合来说,大多数情况,使用Innodb
1.3.2 存储结构
内存、磁盘两大块
内存:
buffer pool简称BP
采用链表的结构,页为单位,16k
page
- free page 空闲页
- clean page 无修改页
- dirty page 修改页
LRU算法管理BP
changed buffer简称CB
占用BP空间
adaptive hash Index
log buffer
redo、undo日志
磁盘:
Tablespace、Innodb Data Dictionary(.frm有重合,新版本移除),DoubleWrite buffer、Redo log and Undo log
Redo 记录了所有的BP的修改
Undo 记录了修改前的记录
1.3.3 线程模型
-
IO Thread
磁盘和缓存间的读写
-
Purge Thread
回收事务提交后的undo页
-
Page cleaner Thread
脏页刷盘,调用write线程,覆盖redo页
-
Master Thread
主线程,负责调度其它线程,优先级最高。包含上述的操作,完成数据的刷盘,保持磁盘和内存的数据一致性
1.3.4 数据文件
Ibd文件-Segment段-Extent区-Page页-Row行
表空间有多个ibd文件
segment有索引段,数据段,回滚段,前两者是必须的。
区,分配资源的最小单位。
page,数据页,undo页,系统页,事务数据页,Blob对象页
1.3.5 undo log
在事务提交完成后,undo log会被放入删除列表,通过后台线程purge Thread回收。
其属于逻辑日志,如果事务为删除,则记录相反的插入。保证恢复。
存储:采用回滚段管理,一个回滚段包含1024个segment
作用:实现事务原子性;实现mvcc,提供快照读。
A事务提交更新操作,会生成一条记录在undo log,供此时其它查询操作快照读。
1.3.6 redo log和binlog
redo log保存了事务提交时的操作,当事务提交操作中断,达到重做的目的。
生命周期:事务提交时生成,事务执行完成,数据写到磁盘,redolog就可以覆盖了。
写入机制:顺序循环写入,checkpoint 、write pos
binlog:记录所有的数据库表结构更改、数据修改操作
场景:主从复制
数据恢复
文件记录模式:
statement:解析成语句,ID值无法一致,now函数等
row:alter操作会产生大量数据,每一行都能记录,完整恢复
mixed:
二、索引原理
2.1 索引类型
普通索引:基于普通字段建立的索引。
唯一索引:创建索引的字段的值必须唯一,允许空值。unique Index,索引前使用唯一关键字
主键索引:primary key,特殊的唯一索引
复合索引:对多列创建索引。超过2列称为宽索引。
全文索引:fulltext index,使用——match(colname) against (‘aaa’)
2.2 索引原理
索引在ibd(Innodb)中,利用page存储;
利用二分查找,hash,B+树。
hash主要在自适应hash中,Innodb对热点数据自动创建的。
2.2.1 B+树
B树:数据按照B树的形式存放,每个节点包含多个索引及数据,按照树的节点查找数据,实现了二分。
B+树:非叶子节点不存储数据,只存放索引值,可以存放更多索引值。
叶子节点包含了所有数据,有索引+数据。叶子节点用指针连接,当锁定到子节点范围时,只需要叶子节点利用指针遍历,不需要跨回去上一层。
2.2.2 聚簇索引、辅助索引
Innodb的表要求必须有且只有一个聚簇索引——里面存放了索引值及所有的数据
- 主键索引
- 第一个非空unique作为索引
- 隐藏rowid作为聚簇索引
辅助索引,也是非聚簇索引,同样是B+,不同的是其索引值对应的数据是主键。
叫二级索引,需要回表查询,先通过索引找到主键值,然后在聚簇索引中找到数据值。
2.3 索引分析、优化
2.3.1 explain命令
- select_type 查询方式simple,union等
- type存储引擎查询数据方式,all,index,range等
- possible key可能使用的索引
- key使用的索引
- rows扫描的行数
- extra操作相关的提示信息。包括索引执行情况,查询结果等
2.3.2 回表查询
即前面的辅助索引,需要通过索引查找主键值,再到聚簇索引查找记录。
2.3.3 覆盖索引
一个SQL查询所需要的列都在一个索引树上,无需回表。select中、where条件中用到的列。建立组合索引。
2.3.4 最左前缀——最左原则
ABC索引,生效情况:A,AB,ABC,最左侧的列必须使用。
2.3.5 like查询
在’5%'可以生效,其它情况不生效。
2.3.6 null值查询
索引生效。
2.3.7 索引排序
filesort(双路排序)、Index(单路排序)
where+order加起来,匹配上最左也会覆盖索引。
同时asc,desc,多列里,索引排序失效,走filesort
2.4 查询优化
慢查询定位slow_query_log;long_query_time
查看日志:打开slow.log即可
慢查询跟是否索引命中没有必然联系
慢查询是指查询超过了设定的查询时间阈值。命中索引的查询如果过滤性不强,也会很耗时。
思考:
select * from temp where id = 3;--id是主键,需要回表吗?temp字段有多个
分页优化:
覆盖索引;子查询
三、Mysql事务和锁
3.1 ACID
A原子性:底层利用redo,undo保证
D持久性:数据刷盘。WAL技术,write ahead log,写前记录日志。
I隔离性:mvcc,隔离级别
C一致性:数据完整性限制。
3.2 事务控制的演进
- 更新丢失
- 脏读
- 不可重复读——针对同一行记录
- 幻读——针对总记录数
3.2.1、排队
序列化
3.2.2、排它锁
不区分读写,都锁住
3.2.3、读写锁
细化了锁的粒度,读读之间可以共享锁,读锁。
3.2.4、MVCC
多版本并发控制multi version concurrent control;多了一个读写并行、写读并行
利用redo,undo,binlog,锁进行。
mvcc首先是为了解决读跟写引发的并发环境下的问题;读、写这两个的并发情况可以解决,但是写跟写还是无法解决的;
然后他的主要思想是copy on write,就是每次的写,会先copy一份,记录下来,放到undo里面,undo里面就有当前的记录的数据信息,以及事务id,rowid,回滚指针;回滚指针意思是指向前一个状态的事务id,比如写之前的,这样发生回滚,就能拿到上一个undo的日志,取出里面存放的数据,然后进行rollback,事务执行失败,需要回滚,就要拿到上一个状态的事务;同时,在完成事务前,其实还得记录redo日志,就是当前的事务可能会因宕机丢失,这样拿到redo日志,重新执行,可以保证事务不丢失。
这样他解决的就是写和读的并发问题,在写的时候,可以提供快照度、当前读,写和写则是无法解决的。
mvcc中,对某条记录加了写锁,会在undo中提供一个快照,也就是事务提交前的数据状态,供其他事务读,写是不能的。当事务提交后,其他事务就可以读到新的记录。例如:
begin;
update user set age = 20 where id = 1;--A tc,| 1 | a1 | 12 | update before,not commit
select * from user where id =1;--B tc select result age=12;
commit;--A tc
select * from user where id =1;--B tc query result age=20; after update commit;
这是不是不可重复读?
select * from user where id =1 lock in share mode;
如果说需要手动用读锁,确实是锁住了的。
3.3 事务隔离级别
为什么是默认可重复读呢:
那就拿读已提交来对比咯,串行化跟读未提交就不用对比了,很显然,串行化并发度太小,虽然基本不会产生并发问题;
而读未提交,则明显并发问题最多,两个事务之间,对记录的修改,基本是没有任何加锁操作,那么就剩下两个之间了,而这也是主流数据库会选择的。Mybatis基于我们实际工作来讲,他更多应用于互联网企业,更多的并发场景,那么显然,他肯定对并发问题出现的要求更高,读已提交,可重复读,就是保证的行记录的问题了,两次读之间,不允许别的记录修改。也就是行锁,读已提交,则是只要事务提交了,就允许读,没有加读锁,Mybatis默认可重复读,这个特性对于高并发场景恰恰就很重要啊。这里又可以引申出来,读锁、写锁,加了读锁,那么就只允许所有事务进行读,不允许其他事务对该记录进行写操作。Mybatis应用于电商,就很常见了,假如两次读出来数据不一样,那么肯定生产事故了。
3.3.1 Mysql数据库隔离级别
通过修改默认隔离级别,set tx_isolation来修改。
然后开启两个事务,分别进行dml操作。观察隔离级别的影响。
-
读未提交:未提交的事务操作会被查出来
-
读已提交RC read committed
-
可重复读(RR repeatable read):
幻读可能产生。
当a事务在两次select中间,B事务对该select内容进行了insert,并提交。这种情况不发生幻读。
而A事务如果在第二次select之前,B事务Commit之后,对B新增的记录进行了Update,那么会产生幻读,也即是A的第二次select会把B提交的那条记录,A修改的内容给显示出来。
-
串行化:serial
两事务如果是读,那么互不影响。
如果是一方是写,那么另一方需要等待前者的事务commit。
例如:
A进行了update,未提交,此时B进行select,该过程会阻塞,待A commit后,B取消阻塞,显示出select结果。
3.3.2 隔离级别和锁
- 事务隔离级别是事务并发控制的整体解决方案。本质是封装锁和mvcc,隐藏底层实现的细节。
- 事务隔离就是根据锁来实现的,根据不同类型、强度的锁,来对数据库的读写操作进行限制。
- 对于用户使用,先选择隔离级别,当一些复杂场景无法实现,再手动设置锁。
3.4 锁机制和实战
3.4.1 锁分类
表锁MyIsam、页锁BDB、行锁InnoDb
三者的并发度依次增大。
页锁是对表中某行相邻的一组数据加锁。
行锁:
-
读锁S(共享锁):多个读操作可以共同进行。
-
写锁X(排它锁):写操作完成前,阻塞其它的读和写。
IS,IX:意向读锁、意向写锁。针对S、X锁(行级锁),追加的表级锁。利于判断该表中是否包含针对某行的锁,如果有这个锁,说明这个表里包含有某行记录的行锁,这样就不需要进入表中对每行数据进行是否有加行锁判断。类似生活中:酒店房门前悬挂免打扰牌子。
S锁,允许当前加锁的事务进行读,不允许写操作。同时允许其他事务获取该记录的S锁,并且不允许进行写操作。必须等当前事务的读操作完成,该记录的所有S锁释放后,才可进行写操作。
X锁,A事务对记录加了X锁,那么可以获得读写权限,其他事务不能对该记录进行读写。
从性质来讲,以上都是悲观锁。乐观锁一般由开发者自己实现。
3.4.2 行锁原理
Innodb行锁是通过对索引数据页上的记录加锁实现的。分为三种:Record lock,Gap lock和Next-key lock。
- Record lock:锁定单个记录(RC,RR)
- gaplock:锁定索引间隙,保持间隙不变(范围锁,RR)
- next-key lock:记录锁+间隙锁,锁住记录及间隙(RR)
在RR隔离级别,Innodb对行加锁先采用next-key lock,当SQL操作含有唯一索引时,会优化加锁操作,降级为Record lock。
普通的select语句,基于mvcc的读非阻塞,不加锁。
select from in share mode共享读锁。
测试数据:
--建表
create table t1(
id int,
name varchar(20))engine=innodb charset=utf8;
create index id on t1(id);
--插入测试数据
insert into t1 values (1,'a');--1,3,5,7
begin;--开启事务
select * from t1 where id = 5 for update;--使用next-key锁,3,7也锁住,无唯一索引
--在另一端口开启事务,模仿多事务
use lagou
insert into values(2,'e');--插入成功,1,3的间隙不会被锁住
insert into values(4,'3');--阻塞,3,5间隙锁
--若没有索引,那么锁表,因为Innodb是基于索引加锁的
drop index id on t1;
show index from t1;
insert into values (199,'we');--不成功,阻塞
表级锁
lock table t1 read;--表级读锁其它事务可以读,不可写
select * from t1;--正常
update t1 set name = 'b' where id=1;--当前事务,其它事务都失败
show open tables;--查看表上加过的锁
unlock tables;--删除锁
lock table t1 write;--写锁,其它事务所有操作都阻塞,读也不行,当前事务读写都可以
行级锁
select * from t1 lock in share mode;--只能读取,不能修改
--无索引,锁全表
select * from t1 where id =1 for update;--加上行锁,写锁
--另一个事务,如果是如上获取写锁,是不行的
select * from t1 where id = 1;--普通查询则没有问题,不涉及任何锁
--update也是不行的
乐观锁
方式:version;时间戳,对比是否为最新时间
create table products(
id int primary key,
quantity int,
version int)engine=innodb charset=utf8;
insert into products values (1,100,1);
update products set quantity=quantity-1,version=version-1 where id=1 and version = 1;
--另一个事务,0match
hibernate封装了version的乐观锁,Mybatis使用OptimisticLocker插件。
3.4.3 死锁
-
表级
解决:一般是程序设计bug,保证锁的顺序,避免互相争夺锁,不要同时锁住两个资源
-
行级
扫描无索引的查询,导致全表锁定。表锁。
解决:不要使用太复杂的查询,或者进行explain优化,简历索引。
两条不同的行,需要互相争夺
-
共享锁升级排它锁
一个读锁未释放,另一事务加排它锁,本事务也需要加,导致争夺。
解决:前端控制按钮,不可重复点击,避免重复提交产生的情况;
乐观锁实现。来自外部的系统不受控制,导致脏数据。
死锁排查:
show engine innodb status
show status like 'innodb_row_lock%'
四、Mysql集群架构
4.1 架构设计
可用性->冗余节点->数据一致性->数据同步
主从模式、双主模式,双主双写,双主单写(推荐)
扩展性:加从库,但是会增加主库的性能损耗。分库分表:水平、垂直拆分
一致性:不使用从库;访问路由层控制,获得主从同步的最大时间t,数据发生修改时,在t时间内只访问主库。
4.2 主从模式
默认采用异步的形式复制,可以通过参数,设定复制特定数据库、特定表。
主从部署条件:
- 主库开启binlog
- 主从可以互通
- 主从serverid不同
4.2.2 实现原理
描述:**主库部分:**master发生修改时,记录变更操作到binlog日志中,且BinlogDump Thread把日志推送到从库的IO Thread,
从库部分:从库的IO将读取到的信息写入从库relay log中,SQL Thread检测到relay log的变更,解析内容进行更新。
存在问题:1、宕机后,可能发生数据丢失(binlog推送是异步导致)
2、从库只一个sql Thread,写压力大,复制延迟。
解决:半同步复制、并行复制。
半同步复制
跟全同步区别就是,不等待从库的commit relay内容,而是在从库的binlog接收后就进行主库的commit了。
5.5的半同步与5.7的增强半同步区别
after-commit:主库提交后才接收从库的ack
after-sync:主库推送binlog时接收ack后才commit
并行复制
MTS(multi Thread slave)针对从库
IO Thread、Sql Thread都是单线程的,有性能问题。
演进
5.6 针对数据库的多线程,一个库一个线程,多个库,多线程。一个库的多线程未实现,并行事务的处理顺序也不行。
5.7 组提交,对事务分组,一组事务提交。找出无冲突的事务
Innodb 两阶段提交事务
prepare时会把事务写入undo,redo中,此时事务是否有冲突就可以检测出来了,commit
实现原理:
基于gtid,对应从库也有一个id
记录last_commited,sequence_number,last相同的,可以放一起并行。
8.0 基于write-set,维护一个集合变量来存储需要修改的记录信息(主键哈希值),如果有冲突,说明修改的是同一主键的数据,即同一行数据。没有冲突即可并行。此时的并行就到了row的级别。
并行复制监控
show slave status;
4.2.3 实战
mysql 5.7.28
rpm -qa|grep mariadb#检测默认的
#移除
rpm -e mariadb.. --nodeps
#安装RPM
rpm -ivh ..
#顺序
common->community->compat->client->server->devel
mysqld --initialize --user=mysql#初始化
#初始生成密码
cat /var/log/mysqld.log#最后可以查看
mysql -uroot -p#登录账户
#修改密码
set password=password('root');
systemctl start mysqld.service#开机服务启动
systemctl status mysqld.service#查看状态
关闭防火墙:
systemctl stop iptables
systemctl stop firewalld
systemctl disable firewalld.service #停用开机启动
主从设置
#my.cnf
log_bin=mysql-bin
server-id=1
sync-binlog=1#启用binlog同步
binlog-ignore=xx#不同步的对象
#重启
systemctl restart mysqld
#主库授权,进入数据库
grant replication slave on *.* to 'root'@'%' identified by 'root';
grant all privileges on *.* to 'root'@'%' identified by 'root';
flush privileges;
show master status;#查看主库信息
#从库配置 vi /etc/my.cnf
server-id=2
read_only=1
relay_log=mysql-relay
#重启
show slave status;
change master to master_host='',master_port=3306,master_user='root',master_password='root',master_log_file=''
,master_log_pos=;
start slave status;
--关闭
stop slave status;
运行一段时间后,增加一个从库,如何操作?
如果按照第一个从库的做法,那么需要同步的数据是很大的,会增加主库的负担。
解决:找一个最新的从库,利用mysqldump工具导出数据到新的库,然后建立主从。
mysqldump --all-databases > mysql_backup_all.sql -uroot -p
半同步复制
主库、从库
select @@have_dynamic_loading;--动态插件
show plugins;
install plugin rpl_semi_sync_master soname 'semisync_master.so';--从库为slave,只需要启用即可
--开启功能
show variables like '%semi%';
--set或者修改配置文件,set 值=1
--开启,修改timeout
--重启从库 stop slave;start slave;
查看日志,验证是否成功启动
cd /var/log
cat mysqld.log
semi出现。
并行复制
修改参数即可
show variables like '%binlog_group%';--设置主库的延迟,事务数,事务组
--从库
show variables like '%slave%';--type,worker4-8,
--开启recovery,Repository=table
修改配置文件需要重启服务,命令不需要,有些只读的只能改配置文件,或者有些会被恢复掉。
4.2.4 读写分离
主从模式的基础上,实现主负责写,从负责读。
减少了读写锁的冲突,提升了读写性能。主要是读。
索引的影响也减少了,只在从库加,主库不加,提升写效率。
问题:主从同步延迟,读写分配机制。
解决:
写后立刻读;读主库。
二次查询;先查从库,再查主库。有可能增加主库压力,遭到恶意攻击,封装api操作。
根据业务特殊处理。实时性高、重要程度高,就放在主库。
方案落地
关键就是读写路由分配机制。
编程实现
代理实现:mycat,shardingjdbc
实战
解压,修改配置文件my-proxy.cnf
#数据库信息
user=root
admin-username=root
admin-password=root
proxy-address=xxxx:4040#当前代理主机地址,默认4040端口
proxy-backend-addresses=x.x.x.x:3306#主库的IP端口信息,多个用,隔开
proxy-read-only-addresses=x.x.x.x:3306#从库,只读
proxy-lua-script=/root/xx/xx/x.lua #绝对路径,使用自带的lua脚本,用于实现分发读写到主从库的控制逻辑。
log-file=/var/log/mysql-proxy.log#日志路径
log-level=debug
daemon=true#指定守护进程的形式运行
keepalive=true#故障重试,尝试重启
#然后修改权限
chmod 660 /etc/mysql-proxy.cnf#可读写
#修改lua脚本配置项,最小读写数设为1
#为什么用./mysql-proxy?因为默认是在usr/bin目录下查找,./指定当前bin目录
./mysql-proxy --defaults-file=/etc/mysql-proxy.cnf#指定启动加载的配置文件
测试使用,新建连接,对proxy连接,类似数据库连接。
利用写是对主库操作,读对从库操作,那么当从库关闭stop slave;的时候,数据不同步了,主库正常插入,说明是起作用,写分离。
数据状态最新的没有同步过来,proxy查询的还是从库的旧数据,说明读分离。
命令行登录远程数据库
mysql -h192.168.95.134 -P4040 -uroot -p;
4.3 双主模式
-
双主双写
ID冲突,写入时,未同步过去,两边都插入了。步长解决,135,248,但仍是扩展性不好,运维不易
更新丢失,更新时,被覆盖
-
双主单写
VIP:虚拟IP,virtual
高可用组件
作用:主库故障自动切换。
keepalived
MMM(3M)架构:Master-Master Replication for Mysql,双主复制。用agent监听各实例
MHA架构:Master High Avaliability。主从模式。一主多从,主挂了,在从库选一个做主。manager不要放在主库节点。利用节点,植入每个库中。监听库的状态。一个manager可以监听多个主从集群,上百台机器。
双主双写实战
加一个master。
在原有master的配置基础下,增加备份机制,互为主备的条件。
relay_log=mysql-relay-bin
log_slave_updates=1
auto_increment_offset=1#指定递增开始
auto_increment_increment=2#指定递增值,用于步长实现,1,3,5
#重启
主备切换策略
可靠性
可用性
4.4 分库分表
垂直拆分:单库表多分库存,单表字段多,分表。(切蛋糕,竖着)无法解决表的数据量问题,要搭配水平拆分。
问题:主键冗余,每个拆分出去的表都需要
水平拆分:单表记录过多,按数据特征分表(固定记录条数、时间、字段属性),分表过多后,就需要搭配垂直拆分
(切蛋糕,横着)
一般开发专注水平拆分。
主键策略
应对分库分表的环境主键增长。
- UUID
- COMB——解决UUID的无序问题
- snowflake
- 主键生成表
分片策略
分片就是确定数据在多台存储设备中的分布。实现分库分表目的的依据。
- 范围分片
- 哈希取模
- 一致性哈希
扩容方案
-
停机扩容
-
平滑扩容
成倍扩容,双主双写,同步过去。采用新的哈希进行分片,把冗余数据删除。
ShardingSphere
- Sharding Jdbc:应用层编码+配置实现读写分离
- Sharding Proxy:服务端的代理,相当于一台数据库,由它路由转发读写到实际的库。
以上是关于(万字超详细总结纯手打)MYSQL深度学习分析的主要内容,如果未能解决你的问题,请参考以下文章
YOLO系列算法精讲:从yolov1至yolov4的进阶之路(2万字超全整理,建议收藏!)