深刻理解MySql事物隔离级别与锁机制

244 阅读6分钟

概述

我们的数据库一般都会并发执行多个事务,多个事务可能会并发的对相同的一批数据进行增删改查操作,可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。这些问题的本质都是数据库的多事务并发问题,为了解决多事务并发问题,数据库设计了事务隔离机制、锁机制、MVCC多版本并发控制隔离机制,用一整套机制来解决多事务并发问题。

事务及其ACID属性(MyISAM不支持)

事务是由一组SQL语句组成的逻辑处理单元,事务具有以下4个属性,通常简称为事务的ACID属性。

1.原子性(Atomicity):原子性是在操作层面上,在一次事务中,所有的操作要么全都执行或者全都不执行

2.一致性(Consistent):一致性是在数据层面,在一次事务中,所有操作过的数据必须要保持一致

3.隔离性(Isolation):不同的事物之间操作环境相互独立,执行期间互不影响

4.持久性(Durable):一个操作提交了事物之后,数据是永久修改的,即使系统故障了也能够保持修改后的数据

并发事务处理带来的问题

1.更新丢失(lost update)或者脏写:后面事物的更新操作覆盖了前面事物的更新操作

2.脏读:事务A读取到了事务B已经修改但尚未提交的数据(事物B可能因为异常回滚数据),不符合隔离性

3.不可重复读:事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性

4.幻读:事务A读取到了事务B提交的新增数据,不符合隔离性

事物的隔离级别

锁详解

锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除了传统的计算资源(如CPU、RAM、I/O等)的争用以外,数据也是一种供需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。

锁分类(MyISAM只支持表锁,不支持行锁)

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

乐观锁:数据库加版本,基于版本去更新数据,比如事物A和事物B都对同一条记录都进行update操作,如果当前的表记录版本是1的话,此时两
个事物读取到的表数据记录都是版本1,那么我事物A根据版本1修改了数据并把版本改为2,那这个时候事物B再根据版本1修改的话就修改失败
了,这就是乐观锁

悲观锁:对于innodb来说,它有行锁的概念,比如事物A和事物B都对同一条记录都进行update操作,事物A开启事物对此记录进行修改,但并
没有提交事物,这个时修改的这条记录是会被锁住的,如果事物B这个时候也对此记录进行修改,它会一直阻塞在那等待事物A的提交,这种锁会涉及到事物之间的相互等待 这就叫做
悲观锁
  

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

读锁(共享锁,S锁(Shared)):针对同一份数据,多个读操作可以同时进行而不会互相影响
写锁(排它锁,X锁(eXclusive)):当前写操作没有完成前,它会阻断其他写锁和读锁

从对数据操作的粒度分,分为表锁和行锁

表锁

每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;一般用在整表数据迁移的场景

1.加表锁的时候直接找到这张表上锁,加一个锁标记就完事了,所以开销小,加锁快
2.死锁是指两个事物锁了对方持有着的资源,对于表锁来说整个表都是同一个事物的,不存在死锁问题
3.因为是对整张表做锁操作,所以他的颗粒度相对于行锁要大的多
4.因为他的颗粒度大,那么每个事物对这张表做出写操作,都可能生锁冲突
5.因为对整个表加了锁,只有当前事物能够操作,所以并发度低
‐建表SQL
CREATE TABLE `mylock` (
 `id` INT (11) NOT NULL AUTO_INCREMENT,
 `NAME` VARCHAR (20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE = MyISAM DEFAULT CHARSET = utf8;
‐‐插入数据
INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('1','a');
INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('2','b');
INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('3','c');
INSERT INTO`test`.`mylock` (`id`, `NAME`) VALUES ('4','d');

手动增加表锁

如果我加了读锁,那么对这张表的所有写操作是做不了的,一般用在数据迁移

lock table 表名称read(write),表名称2 read(write)

行锁

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

1.加行锁需要先找某一行的数据记录,如果没有索引的话那找的就会更慢,所以加锁慢,开销大

2.在两个事物操作中,事物A操作了表C的id=1记录,事物A并未提交,事物B操作了表C的id=2的记录,事物B并未提交,如果这个时候事物A
要操作表C的id=2的记录,需要等待着事物B释放id=2的记录的锁,而事物B要操作了表C的id=1记录,需要等待事物A释放id=1记录的锁,这
个时候就会形成死锁

3.因为行锁是针对表里每一条记录进行加锁的,相对于表锁对整张表数据加锁颗粒度最小

4.同表锁正好相反,因为行锁对表记录加锁,那就意味着只有两个事物操同一个表记录的时候才会发生锁冲突

5.因为行锁的锁定颗粒度下,只有当你同时操作同一个记录的时候才会有阻塞等待,操作其他记录都不影响的,所以他的并发度高


总结:

MyISAM在执行查询语句SELECT前,会自动给涉及的所有表加读锁,在执行update、insert、delete操作会自动给涉及的表加写锁。InnoDB在执行查询语句SELECT时(非串行隔离级别),不会加锁。但是update、insert、delete操作会加行锁。

简而言之,就是读锁会阻塞写,但是不会阻塞读。而写锁则会把读和写都阻塞。