美文网首页
每天一点MYSQL之事物处理(发散)

每天一点MYSQL之事物处理(发散)

作者: Insecurity | 来源:发表于2020-12-20 21:54 被阅读0次

    本文章主要是以线性的方式引入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知识,串联起来,加深理解(巩固),喜欢的朋友可以点个赞,若是有不错的文章也可以推荐一下。谢谢大家的支持。(知识积累的差不多了,后续会继续 细化这些原理,配上测试案例。达到小白入门即学会。)

    相关文章

      网友评论

          本文标题:每天一点MYSQL之事物处理(发散)

          本文链接:https://www.haomeiwen.com/subject/qlicnktx.html