(万字超详细总结纯手打)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万字超全整理,建议收藏!)

上岸了,私藏总结的 20 万字超全硬核面试题送给学弟妹们,奥利给!

Linux新手快速入门(万字超详细)

MySQL系列:纯手打“RocketMQ笔记”

万字总结Keras深度学习中文文本分类