zoukankan      html  css  js  c++  java
  • Innodb 实现高并发、redo/undo MVCC原理

    一、并发控制
      因为并发情况下有可能出现不同线程对同一资源进行变动,所以必须要对并发进行控制以保证数据的同一与安全。   可以参考CPython解释器中的GIL全局解释器锁,所以说python中没有真正的多线程,多线程任意时刻只有一个程序能申请到GIL操作CPU。
    1.2 Innodb中的并发控制
     

    锁(lock)
    数据多版本(multi versioning)

    1.2.1 锁
      - 使用普通锁来保证数据的一致性:   操作数据前实行互斥,即当前程序曹锁数据时,不允许其他并发任务操作该数据,进入排队等待 操作完成后释放锁  
    • 普通锁问题:
      锁定粒度大,容易出现死锁
      简单粗暴,没有并行“读任务”,本质上是串行执行任务。

    • 正对上述问题,有了共享锁及排他锁

    1. 共享锁(share locks 称为S锁)读取数据时加S锁
      共享锁之间不互斥,简记为:读读可以并行

    2. 排他锁(exclusive locks 称为X锁)修改数据时加X锁
      排他锁与任何锁互斥,简记为:写读,写写不可以并行

    上述中可以了解到,只要修改数据时加了排他锁, 其它操作同一数据的并行线程都进入了排队等待释放锁。且写的数据没有完成提交的话,其它任务是没法去读取数据的,也阻塞了

    1.2.2 数据多版本(某种意义的读写分离)
      - 作为一种提高并发的方式:原理
    1. 写任务发生时,将数据克隆一份,以版本号区分;

    2. 写任务操作新克隆的数据,直至提交;

    3. 并发读任务可以继续读取旧版本的数据,不至于阻塞;

    • 如上图:
    1. 最开始数据的版本是V0;

    2. T1时刻发起了一个写任务,这是把数据clone了一份,进行修改,版本变为V1,但任务还未完成;

    3. T2时刻并发了一个读任务,依然可以读V0版本的数据;

    4. T3时刻又并发了一个读任务,依然不会阻塞;

    可以看到,数据多版本,通过“读取旧版本数据”能够极大提高任务的并发度。

    总结:

    • 普通锁,本质是串行执行

    • 读写锁,可以实现读读并发

    • 数据多版本,可以实现读写并发

    二、Innodb redo/undo 日志、回滚段 (rollback segment) 操作,实现上述思路
     
    • ACID:
      原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)
    2.1 redo日志
     

    数据库事务提交后,必须将更新后的数据刷到磁盘上,以保证ACID特性。磁盘随机写性能较低,如果每次都刷盘,会极大影响数据库的吞吐量。

    优化方式为:将修改行为先写到redo日志里(此时变成了顺序写),再定期将数据刷到磁盘上,这样能极大提高性能。

    假如某一时刻,数据库崩溃,还没来得及刷盘的数据,在数据库重启后,会重做redo日志里的内容,以保证已提交事务对数据产生的影响都刷到磁盘上。

    一句话,redo日志用于保障,已提交事务的ACID特性。

    2.2 undo日志
     

    数据库事务未提交时,会将事务修改数据的镜像(即修改前的旧版本)存放到undo日志里,当事务回滚时,或者数据库奔溃时,可以利用undo日志,即旧版本数据,撤销未提交事务对数据库产生的影响。

    对于insert操作,undo日志记录新数据的PK(ROW_ID),回滚时直接删除;

    对于delete/update操作,undo日志记录旧数据row,回滚时直接恢复;

    他们分别存放在不同的buffer里。

    一句话,undo日志用于保障,未提交事务不会对数据库的ACID特性产生影响。

    2.3 回滚段
     

    存储undo日志的地方,是回滚段。

    undo日志和回滚段和InnoDB的MVCC密切相关

    建议看完MVCC原理再看下面:MVCC原理
    直接看也行

    举例:
    create table tb(
    id int auto_increment primary key,
    name cahr(32));

    插入三条数据
    1, shenjian

    2, zhangsan

    3, lisi

    此时没有事务未提交,故回滚段是空的。

    接着启动了一个事务:

    start trx;

    delete (1, shenjian);

    update set(3, lisi) to (3, xxx);

    insert (4, wangwu);

    并且事务处于未提交的状态。

    可以看到:

    (1)被删除前的(1, shenjian)作为旧版本数据,进入了回滚段;

    (2)被修改前的(3, lisi)作为旧版本数据,进入了回滚段;

    (3)被插入的数据,PK(4)进入了回滚段;

    接下来,假如事务rollback,此时可以通过回滚段里的undo日志回滚。

    假设事务提交,回滚段里的undo日志可以删除。

    可以看到:

    (1)被删除的旧数据恢复了;

    (2)被修改的旧数据也恢复了;

    (3)被插入的数据,删除了;

    事务回滚成功,一切如故。

    三、innodb 是基于MVCC(多版本并发控制)的存储引擎
      InnoDB是高并发互联网场景最为推荐的存储引擎,根本原因,就是其多版本并发控制(Multi Version Concurrency Control, MVCC)。行锁,并发,事务回滚等多种特性都和MVCC相关。

    MVCC就是通过“读取旧版本数据”来降低并发事务的锁冲突,提高任务的并发度。

    旧数据存储的地方:
    (1)旧版本数据存储在回滚段里;

    (2)对MySQL和InnoDB原有架构体系冲击不大;

    InnoDB的内核,会对所有row数据增加三个内部属性:

    (1)DB_TRX_ID,6字节,记录每一行最近一次修改它的事务ID;

    (2)DB_ROLL_PTR,7字节,记录指向回滚段undo日志的指针;

    (3)DB_ROW_ID,6字节,单调递增的行ID;

    回滚段里的数据,其实是历史数据的快照(snapshot),这些数据是不会被修改,select可以肆无忌惮的并发读取他们。

    快照读(Snapshot Read),这种一致性不加锁的读(Consistent Nonlocking Read),就是InnoDB并发如此之高的核心原因之一。

    这里的一致性是指,事务读取到的数据,要么是事务开始前就已经存在的数据(当然,是其他已提交事务产生的),要么是事务自身插入或者修改的数据。

    什么样的select是快照读?

    除非显示加锁,普通的select语句都是快照读,例如:

    select * from t where id>2;

    这里的显示加锁,非快照读是指:

    select * from t where id>2 lock in share mode;

    select * from t where id>2 for update;

    总结

    (1)常见并发控制保证数据一致性的方法有锁,数据多版本;

    (2)普通锁串行,读写锁读读并行,数据多版本读写并行;

    (3)redo日志保证已提交事务的ACID特性,设计思路是,通过顺序写替代随机写,提高并发;

    (4)undo日志用来回滚未提交的事务,它存储在回滚段里;

    (5)InnoDB是基于MVCC的存储引擎,它利用了存储在回滚段里的undo日志,即数据的旧版本,提高并发;

    (6)InnoDB之所以并发高,快照读不加锁;

    (7)InnoDB所有普通select都是快照读;

  • 相关阅读:
    Spring实现AOP
    js Form表单转json格式,及后台接收(多种方法)
    Java 网络编程
    分布式系统学习
    java消息中间件
    oracle Clob类型转换成String类型
    Oracle的CLOB大数据字段类型
    oracle wm_concat函数 列转行 分组函数
    Oracle trunc函数使用
    ajax异步提交文件
  • 原文地址:https://www.cnblogs.com/shiqi17/p/9787940.html
Copyright © 2011-2022 走看看