本文章主要是以线性的方式引入MYSQL的原理,从梳理到快速记忆,做到过目不忘的效果。
MYISAM和INNODB的区别
MYISAM | INNODB | |
---|---|---|
事物支持 | 不支持 | 支持 |
数据行锁 | 无 | 有 |
外键约束 | 不支持 | 支持 |
全文索引 | 支持 | 不支持 |
表空间 | 小 | 大(两倍) |
区别速记
MYISAM 是轻量级、效率高,支持全文索引,但不安全
INNODB 安全,支持事物
MYISAM的索引和数据是分开存储的,索引文件只存放了行的主键(这种方式叫做聚簇索引)
物理空间的位置
所有的数据库文件都存放在data目录下,本质还是文件的存储
-INNODB 在数据库表中只有一个.frm文件,以及上级目录下的ibdata1文件
-MYISAM 对应的文件,.frm ,.MYD ,.MYI
聚簇索引和非聚簇索引的区别(KEY:是否能直接找到行数据)
1.聚簇索引和非聚簇索引的区别,其实就是MYISAM和INNODB对索引实现的方式的区别
2.从空间上看:聚簇索引将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据,
非聚簇索引将数据与索引分开存储,索引结构的叶子节点指向了数据对应的位置
3.在innodb中,在聚簇索引之上创建的索引称之为辅助索引,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引。辅助索引叶子节点存储的不再是行的物理位置,而是主键值,辅助索引访问数据总是需要二次查找。
引出 MYISAM 和INNODB的索引实现方式,索引的类型,索引的数据结构演变
MYISAM 和INNODB的索引实现方式
MYISAM 索引文件和数据文件 是分离的,通过索引查询只能查询到 数据文件的地址
INNODB 索引文件和数据文件是聚集的,能直接通过主键索引查询到行数据。
引出:辅助索引 -> innodb 的非主键索引,是先查找到主键索引,再回表搜索数据行的。
索引的类型 (HASH&B+)
哈希索引通过哈希的一致性算法,计算主键的哈希值 。缺陷:hash碰撞,不支持范围搜索
B+索引:以B+树的形式存储数据结构、支持范围搜搜。
引出B+索引的发展史
索引的数据结构演变
二叉树 :缺陷 ,顺序插入时,退化为链表。查询效率不稳定
红黑树 :通过自选在一定程度上解决了平衡问题,但依旧存在右倾,高度是递增的
AVL树::绝对平衡树 ,解决了高度问题。缺陷:一次只能读取一个数据。
B树:每个节点多存放几个数据,一次性取出多个数据。缺陷:数据一起存放在子节点中,有点。
B+树:只有叶子结点才会存放data数据,并且将最终子节点,连接上了
Mysql 事物的基本要素(ACID,原一隔持)
原子性(Atomic)一个事物,要么所有执行成功,要么全失败,当做成一个不可分割的概念
一致性(Consistent)数据处于一种有意义的状态。是语义上的,也是最终目的。(转账, ++,--必须同步)
隔离性(Isolation) 同一时间只允许一个请求,不同的事物不能有干扰
持久性(Durability)事物完成后,事物对数据库的所有更新都保存到数据中,不能回滚
Mysql 事物并发问题
1.脏读,事物A读取到了B事物中的过程数据(非最终结果)
2.重复读,事物A读取到了B事物中的过程数据(B操作了多次,导致每次结果不一样,A全部读取到了)(注重点在修改)
3.幻读,事物A没有接收到事物B的新增/删除的操作导致数据,多/少(注重点在 删除和插入)
Mysql支持的事物隔离级别(可以解决哪些并发问题)(速记:对角线分割)
事物隔离级别 | 脏读 | 重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
读已提交(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
如何记忆?(按时间顺序记忆)
读取B线程未提交的数据会导致 脏读(读已提交能解决脏读)
读取B线程已经提交的数据会导致 重复读(可重复读,能解决重复读)
无法读取到B线程新插入/删除的记过 幻读 (串行化能解决 所有)
Mysql实现可重复读,读已提交(MVCC,Multi-Version Concurrency Control ( PS: 不同于MVC和前端的MVVM))
MVCC 只在 读已提交、可重复读两种隔离级别工作
如何实现的呢? 增加版本号
insert 保存当前事物的系统版本号
delete 存放当前系统版本号,作为删除标记
update 原数据:插入一行新数据版本号为系统版本号。旧数据 存放当前系统版本号,作为删除标记
select
1.仅仅查找版本号小于等于当前事物版本号的数据
2.行的删除版本要么未定义,要么大于当前事物版本号。
PS:MVCC在一定程度上解决了幻读,(但,如果 先 commit create ,当A线程执行更新对应的语句后,再次select 依旧会出现幻读)
image.png
快照读和当前读
select 快照读
当select执行以前的,任何操作,都可以监听,一旦select执行,就无法监听 之后的 事物提交了
比如说:(第一次select 会存储快照 也就是 select * from id >10)如果此时插入 id=11 是无法获取的
当前读(update,insert(无所谓),delete),在update user set age =18 where name ="zs"
中,一定获取的是最新版本的记录。(并更新为当前事物版本,因此,更新后,再次select就能查询到数据了,因为 update的新版本已经更新为当前版本, 删除版本号为null)
引发的问题:如果 修改数据都是获取到最新版本的操作。会引发冲突 => X锁,或者是next-key锁解决(上锁,避免同时修改)
PS :
情况一:
假设目前有2个会话
会话A:开启事物 (update t set name = 'ls' where id = 1)
会话B:开启事物(update t set name = 'zs' where id = 1)
此时会产生排它锁 => 阻塞,必须等待其中一个会话释放才能进行。
情况二:
假设目前有2个会话
会话A:开启事物 select * from t; update t set c = c+1; select * from t;
会话B:不开启事物(update t set c = c+1;)
结果 会话A结果 取到了会话B的值 因此 c=3;
结论:update、delete 都是当前读、select 可以添加 select xx for update 或则 select XX for share mode
MYSQL查询过程
select
1.客户端发起查询请求(select) -> 2.查询缓存(命中则直接返回) -> 3.语法解析器和预处理器(分析语法、词法,是否合理)->4.查询优化器(索引覆盖,中断查询。)返回查询执行计划(数据结构,指令树)->5.查询执行引擎(INNODB 、MYISAM) ->6.调用api接口并并返回数据
2.快速记忆,头尾缓存不用记(请求必须要有发起和结束,查询缓存很正常):只需要记住三步:语法解析和预处理,内部优化,查询执行引擎
update(举例说明redo log和binlog在update T set c=c+1 where ID=2;时是怎么工作的)
1.查询缓存
2.更新,写内存
3.写redo.log
4.写binlog
5.提交事物
回顾SELECT执行顺序: [from、join]、[on,where],[group by ,having ] ,select、distinct、order by、limit
小记:
select (先查询)、distinct(去重)、order by(排序)
on(先关联表)、where(过滤数据)、group by(分组) ,having(分组数据过滤)
引出 redo log(重做日志)和 binlog(归档日志)
image.png
image.png
binlog(归档日志)和 redo log(重做日志)
1.binlog 记录mysql的写入性操作(PS:与引擎无关,任何殷勤都有,是在服务层进行写入的)
引入:逻辑日志(sql操作)&物理日志(具体的数据行) 对比redis(因为是二进制,因此叫做bin log)
作用:数据恢复、主从复制
刷盘时机:sync_binlog 控制,(0,1[默认],N,N代表事物个数)写入磁盘
binlog的日志格式: statement(sql,批量更新时,只会记录单条),row(无上下文的sql,批量更新,会新增N条记录)、 mixed(两者混合)
2.redo log
引入:MYSQL如何保证持久性 -> 每次提交事物时,就将数据写入盘中 => 结果,INNODB是按页提交,修改的仅仅是一部分数据浪,费资源,并且一个事物是多个数据页,需要进行随机IO,性能差 => redo log 优化,解决资源和性能的问题。
作用:实现mysql的原子性,并提供性能,降低资源消耗(引入关键字:write ahead log(写前日志))
刷盘时机(参数配置):先写内存 批量提交都磁盘(先写入OS buffer,在写入到redo file),
innodb_flush_log_at_trx_commit (0,1,2)
理解方式: (0,commit,后续都是按秒写入)写入内存(2,commit,写入内存既可提交事物)-- 写入磁盘 (1,commit,都写完才提交事物)
3.undo log
引入:delete时,如何回滚? redo log 仅仅记录了 delete操作 => undo log ,反向操作,以便回滚。(更新操作是如何回滚的?)
作用:回滚,多个行版本控制MVCC
flush阶段、sync阶段、commit阶段。
image.png
image.png
Mysql 锁
分类 --行锁(INNODB)&表锁(MYISAM为主)
1.无锁
索引不存在
select * from user where id = -1 for update;
2.行锁
INNODB实现的行锁,是基于索引的,也就是当查询条件包含索引时才会添加行锁,否则添加的就是表锁
索引明确
select * from user where id = 1 for update;
select * from user where id = 1 and name = 'kk' for update;
3.表锁
索引不明确
select * from user where name = "zs" for update;
select * from user where id !=3 for update;
锁算法(锁机制:粒度-实现)
1.行锁算法
record lock 普通行锁
·键值在条件范围内
·记录存在
gap lock 间隙锁
·对于键值不存在条件范围内,叫做间隙,引擎会对 这个间隙加锁
next-key lock (行&间隙锁,可能会降级为行锁。)
·键值在条件范围内,同时又不在条件范围内。
·select * from user id >49 for update ; //id只有50
2.表锁算法
意向锁(升级机制)
1.当一个事物带着表锁去范恩一个被加了行锁的资源,行锁会升级为表锁。(升级)
2.举例如下:
select * from user where id = 10 for update ; //事物A(行锁,此时升级为意向锁,将表锁住)
select * from user where name like 'zs%' for update ; //事物B (表锁)
自增锁
1.事物插入增增类型的列时,会获取自增锁
2.如果一个表正在插入自增记录,其他事物必须等待
Mysql锁的实现
共享锁&排它锁
行锁和表锁是粒度的概念,共享锁和排它锁是具体的实现
共享锁(S Share):(不仅仅是读锁)
允许一个事物去读一行,组织其他事物去获取改行的排它锁
排它锁(X Exclusive ):写锁
允许持有排它锁的事物读取写数据,阻止其他事物获取该资源的共享锁和排它锁
不能获取任何锁,但不代表不能读
注意点
某个事物获取数据的排它锁,其他事物不能获取该数据的任何锁,并不代表其他事物不能无锁读取该数据
无锁 select ... from ..
共享锁 select ... lock in share mode => 增设了 for share skip locked跳过锁 ,可以高效的实现等待队列(Mysql 8.0)
排它锁 update、delete、insert、select
乐观锁&悲观锁
乐观锁
一般通过版本号进行更新行
update set xx ='x' where id =1 and version = 1;
悲观锁
如果一个事务执行的操作读某行数据应用了锁,那只有当这个事务把锁释放,其他事务才能够执行与该锁冲突的操作(比如说库存的超卖) select * from xx for update;
Mycat(ShareDB)
在集成mycat之前,可以先考虑分区
RXC方案与Replication方案
RXC 必须所有节点都写入,才提交,写入慢,但更可靠
Replication 其他节点延时提交。
主键生成算法:
1.雪花算法,生成 有序整数(推荐)
2.UUUID ,无序字符串(不利于索引书创建)
3.取余算法(无法动态扩容)
后续可以整合 keepalived +HAProxy 做到高可用
总结
希望通过自己的文章,能将大家的MYSQL知识,串联起来,加深理解(巩固),喜欢的朋友可以点个赞,若是有不错的文章也可以推荐一下。谢谢大家的支持。(知识积累的差不多了,后续会继续 细化这些原理,配上测试案例。达到小白入门即学会。)
网友评论