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

    尽量放在事务最后执行

    尽可能低级别事务隔离

      

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

  • 相关阅读:
    ASP.NET MVC 学习之路由(URL Routing)
    Linux 部署ASP.NET SQLite 应用 的坎坷之旅 附demo及源码
    linux解压zip文件
    /bin/sh^M:解释器错误:没有那个文件或目录
    mysql查看连接情况
    linux编译qt
    没有可用的软件包 xxx,但是它被其它的软件包引用了
    什么是人月
    qt linux 打包
    linux里安装使用svn
  • 原文地址:https://www.cnblogs.com/cxiaocai/p/11594151.html
Copyright © 2011-2022 走看看