zoukankan      html  css  js  c++  java
  • java架构之路-(mysql底层原理)Mysql事务隔离与MVCC

      上几篇博客我们大致讲了一下mysql的底层结构,什么B+tree,什么Hash需要回行啊,再就是讲了mysql优化的explain,这次我们来说说mysql的锁。

    mysql锁

      锁从性能上分为乐观锁(用版本对比来实现)和悲观锁,乐观锁的性能要比悲观锁高。

      从对数据库操作的类型分,分为读锁和写锁(都属于悲观锁)

      读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响。除锁以外的线程只可读,不可以写入。

      写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。除锁以外的线程不可以做任何操作。

      从对数据操作的粒度分,分为表锁和行锁,再就是不常提到的间隙锁。

    我们主要来说表锁和行锁,还有我们的间隙锁。

    注意:有锁等待的几乎都为悲观锁

    表锁 

      顾名思义,加了表锁,会将整张表锁住。开销很小,加锁很快,不会出现死锁;锁定粒度大,发生锁冲 突的概率最高,并发度最低;

    我们来看几条命令。以student表为例

    加表锁:lock table 表名称 read(write),表名称2 read(write);

    lock table student write;

    查看表状态(是否被加锁):

    show open tables;

    内有有一个列为In_use为1的即为已有锁存在。

    解锁表:unlock tables;

    unlock tables;

    MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁
    在执行增删改操作前,会自动给涉及的表加写锁。
    1、对MyISAM表的读操作(加读锁) ,不会阻寒其他进程对同一表的读请求,但会阻塞对同一表的写请求。只有当读锁释放后,才会执行其它进程的写操作。
    2、对MylSAM表的写操作(加写锁) ,会阻塞其他进程对同一表的读和写操作,只有 当写锁释放后,才会执行其它进程的读写操作
    3、MylSAM表不支持行锁,也不支持事务。

    行锁

      每次操作锁住一行数据。开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度最高。

    说到这要提到我们的ACID了,我们来复习一下。

    A(atomicity)原子性:

    即事务要么全部做完,要么全部不做,不会出现只做一部分的情形,如A给B转帐,不会出现A的钱少了,B的钱却没有增加的情况,要么全部成功,要么全部失败(回滚)。这一系列的动作可以视为一个原子。

    C(consistency)一致性:

    指的是事务从一个状态到另一个状态是一致的,如A减少了100,B不可能只增加30。

    I(isolation)隔离性:

    即一个事务在没有完成数据的提交修改时,对其它事务是不可见的。当然这里有个隔离级别的概念,在不同隔离级别下,这里会有不同的表现形式。

    D(durability)持久性:

    一旦事务提交,则所做修改就会被永久保存到数据库中。

    然后就是我们的并发事务处理带来的问题,先过一遍这些都会造成什么后果。

      更新丢失(Lost Update)

    当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每 个事务都不知道其他事务的存在,就会发生丢失更新问题–最后的更新覆盖了由其 他事务所做的更新。

    举例:比如我们同时开启两个线程去售票,卖一张少一张,我们线程A开启事务,同时我们开启线程B,同时查询到余票为10张,卖一张吧。A卖了一张,10-1,剩余9张,我们B线程也卖了一张也是10-1,也剩余9张,提交A,提交B,我们明明卖了两张票,可是数据库得到的确实9,只卖了一张票。

      脏读(Dirty Reads)

    一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数 据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控 制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提 交的数据依赖关系。这种现象被形象的叫做“脏读”。 一句话:事务A读取到了事务B已经修改但尚未提交的数据,还在这个数据基 础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。

      不可重读(Non-Repeatable Reads)

    一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现 其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不 可重复读”。 一句话:事务A读取到了事务B已经提交的修改数据,不符合隔离性。

      幻读(Phantom Reads)

    一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插入了满足其查询条件的新数据,这种现象就称为“幻读”。 一句话:事务A读取到了事务B提交的新增数据,不符合隔离性

    后面的两个说完MVCC机制也就知道是怎么回事了,暂时放在这里。

    这些问题我们再回到我们的数据库吧。

    事务的隔离级别

      

    一般都设置为可重复读的。数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。 同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用

     对“不可重复读"和“幻读”并不敏感,可能更关心数据并发访问的能力。 常看当前数据库的事务隔离级别: show variables like 'tx_isolation'; 设置事务隔离级别:set tx_isolation='REPEATABLE-READ';

     其余的可以自己去尝试一下,读未提交READ-UNCOMMITTED,读已提交READ-COMMITTED,可串行化SERIALIZABLE;

    MVCC:

      这个超级重要,懂了这个上面的几乎都懂了~!

    英文全称为Multi-Version Concurrency Control,翻译为中文即 多版本并发控制。这个概念很抽象,我们并不知道他控制的是什么。

    举一个栗子来说一下,假设我们的MySQL表里有两个虚拟的字段,一个叫开启事务ID,一个叫删除事务ID,都为自增的。再开启事务时不会给予任何数值,在执行第一条SQL时,给予开启事务ID一个数字,我们假设为0,但是不给与提交事务ID(还是为空)。以我们给出的学生表为例上图说话。

     简单说一下图的意思,我们每次在运行sql的时候,都会以时间戳生成一个快照版本号,如果是查询SQL,会把这个版本号更新到我们的createID字段,增删改操作会把我们的版本号更新到的deleteID字段,每个线程事务之间版本号是独立的,对于我们的下一次查询来说,我们会查询数据中createID大于等于我们的快照版本号,且deleteID小于我们的当前的快照版本号ID的数据。MVCC一般在可重复读的隔离级别,但同时在读已提交也是试用的。MVCC缺点是会保存多个快照版本,造成了空间的冗余,但是保证了每个线程的独立操作。

    间隙锁

    简单说一下间隙锁,如果我们的表ID是自增的,我们写一个开启事务,我们写一条修改SQL

     update student set name = '1111'  where id>8 and id<22;

    也就是说,不管你有没有id为8~22的数据,这时都对小于8的最大ID到大于22的最小ID这个范围加了锁,这断范围是禁止你新增和修改的,其余位置是可以的。看你的表结构

    比如你的表是

     sql为 update student set name = '1111'  where id>8 and id<22; 其实我们加锁的范围是(6~22)的范围开区间都是不可以操作的 。

     锁升级:

       我们内部的InnoDB的锁是加在索引上的,也就是说,我们update或者delete时后面的where条件尽力要跟索引字段。

    锁的分析: 

      我们可以通过show status like'innodb_row_lock%';命令来查看我们行锁的争夺情况。

    分别表示为

    Innodb_row_lock_current_waits: 当前正在等待锁定的数量

    Innodb_row_lock_time: 从系统启动到现在锁定总时间长度(等待总时长)

    Innodb_row_lock_time_avg: 每次等待所花平均时间(等待平均时长)

    Innodb_row_lock_time_max:从系统启动到现在等待最长的一次所花时间

    Innodb_row_lock_waits:系统启动后到现在总共等待的次数(等待总次数) 

    死锁

    也就是相互的锁等待造成死锁。

    查看近期死锁日志信息:show engine innodb statusG; 大多数情况mysql可以自动检测死锁并回滚产生死锁的那个事务,但是有些情况mysql没法自动检测死锁

    总结

    尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁,合理设计索引。

    尽量缩小锁的范围,尽可能减少检索条件范围,避免间隙锁。

    尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql

    尽量放在事务最后执行

    尽可能低级别事务隔离

      

    最进弄了一个公众号,小菜技术,欢迎大家的加入

  • 相关阅读:
    PHP保留小数的相关方法
    ASP.NET Core MVC 之过滤器(Filter)
    ASP.NET Core MVC 之控制器(Controller)
    ASP.NET Core MVC 之视图组件(View Component)
    ASP.NET Core MVC 之局部视图(Partial Views)
    标签助手(TagHelper)
    ASP.NET Core MVC 之布局(Layout)
    ASP.NET Core MVC 之视图(Views)
    ASP.NET Core MVC 之模型(Model)
    九卷读书:淘宝从小到大的发展 -重读《淘宝技术这十年》
  • 原文地址:https://www.cnblogs.com/cxiaocai/p/11594151.html
Copyright © 2011-2022 走看看