MySQL锁与事务隔离

锁分类

读锁(共享锁):针对同一份数据,多个读操作可以同时进行而不会互相影响
写锁(排它锁):当前写操作没有完成前,它会阻断其他写锁和读锁。
表锁:每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲 突的概率最高,并发度最低。

  • 加表锁:lock table 表名称 read(当前session中插入或者更新锁定的表都会报错,其他session插入或更新则会等待)、write(当前session对该表的增删改查都没有问题,其他session对该表的所有操作被阻塞,包括不能查询);
  • 查看所有表锁:show open tables;
  • 解开加过的表锁:unlock tables;

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行增删改 操作前,会自动给涉及的表加写锁,串行化的。也可以打破这个限定,MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。

  • 当concurrent_insert设置为0时,不允许并发插入。
  • 当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个进程读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。
  • 当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。

可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入的锁争用。例如,将concurrent_insert系统变量设为2,总是允许并发插入;同时,通过定期在系统空闲时段执行OPTIMIZE TABLE语句来整理空间碎片,收回因删除记录而产生的中间空洞。

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

事务属性

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

  • 原子性(Atomicity) :事务是一个原子操作单元,其对数据的修改,要么全都执 行,要么全都不执行。
  • 一致性(Consistent) :在事务开始和完成时,数据都必须保持一致状态。这意 味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性;事务结束 时,所有的内部数据结构(如B树索引或双向链表)也都必须是正确的。
  • 隔离性(Isolation) :数据库系统提供一定的隔离机制,保证事务在不受外部并 发操作影响的“独立”环境执行。这意味着事务处理过程中的中间状态对外部是 不可见的,反之亦然。
  • 持久性(Durable) :事务完成之后,它对于数据的修改是永久性的,即使出现系 统故障也能够保持。

并发事务处理带来的问题

  • 更新丢失(Lost Update) 当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每 个事务都不知道其他事务的存在,就会发生丢失更新问题–最后的更新覆盖了由其 他事务所做的更新。
  • 脏读(Dirty Reads) 一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数 据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控 制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提 交的数据依赖关系。这种现象被形象的叫做“脏读”。 一句话:事务A读取到了事务B已经修改但尚未提交的数据,还在这个数据基 础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。
  • 不可重读(Non-Repeatable Reads)
    一个事务在读取某些数据后的某个时间,再次读取以前读过的数据,却发现 其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不 可重复读”。 一句话:事务A读取到了事务B已经提交的修改数据,不符合隔离性 。
  • 幻读(Phantom Reads) 一个事务按相同的查询条件重新读取以前检索过的数据,却发现其他事务插 入了满足其查询条件的新数据,这种现象就称为“幻读”。 一句话:事务A读取到了事务B提交的新增数据,不符合隔离性。select 某记录是否存在,不存在,准备插入此记录,但执行 insert 时发现此记录已存在,无法插入,此时就发生了幻读。

事务隔离级别

“脏读”、“不可重复读”和“幻读”,其实都是数据库读一致性问题,必须由数 据库提供一定的事务隔离机制来解决。 数据库的事务隔离越严格,并发副作用越小,但付出的代价也就越大,因为事务隔 离实质上就是使事务在一定程度上“串行化”进行,这显然与“并发”是矛盾的。 同时,不同的应用对读一致性和事务隔离程度的要求也是不同的,比如许多应用 对“不可重复读"和“幻读”并不敏感,可能更关心数据并发访问的能力。

  • 常看当前数据库的事务隔离级别: show variables like ‘tx_isolation’;
  • 设置事务隔离级别:set tx_isolation=‘REPEATABLE-READ’;
事务隔离级别 脏读 不可重复读 幻读
读未提交(read-uncommitted)
不可重复读(read-committed)
可重复读(repeatable-read)
串行化(serializable)

MVCC(multi-version concurrency control)

undo log:如果因为某些原因导致事务失败或回滚了,可以借助该undo进行回滚。它是逻辑日志。可以认为当delete一条记录时,undo log中会记录一条对应的insert记录,反之亦然,当update一条记录时,它记录一条对应相反的update记录。

  • undo log是采用段(segment)的方式来记录的(rollback segment回滚段),每个回滚段中有1024个undo log segment。每个undo操作在记录的时候占用一个undo log segment。rollback segment,默认值为128,可通过变量 innodb_undo_logs设置。undo log默认存放在共享表空间中
  • undo log也会产生redo log,因为undo log也要实现持久性保护。当事务提交之后,undo log并不能立马被删除,而是放入待清理的链表,由purge线程判断是否由其他事务在使用undo段中表的上一个事务之前的版本信息,决定是否可以清理undo log的日志空间。
  • 通过undo log记录delete和update操作的结果发现:(insert操作无需分析,就是插入行而已)
    delete操作实际上不会直接删除,而是将delete对象打上delete flag,标记为删除,最终的删除操作是purge线程完成的。
    update分为两种情况:update的列是否是主键列。
    如果不是主键列,在undo log中直接反向记录是如何update的。即update是直接进行的。
    如果是主键列,update分两部执行:先删除该行,再插入一行目标行。

MVCC:了解了undolog后,所谓的多版本控制就好理解了。执行查询时,生成一致性视图,即获取当前所有未提交事务ID的数组与当前最大的事务ID。再次查询时,遍历所有行,获取当前行的事务ID。注:通过undo log回滚记录在事务前状态。

是否大于一致性视图中最大事务 是否在一致性视图中所有未提交事务ID 是否回滚到事务前状态 下一步操作
大于 拿回滚后记录的事务ID继续对照表进行判断
小于等于 拿回滚后记录的事务ID继续对照表进行判断
小于等于 当前记录就是我们期望的结果

最终获取到与第一次时查询一致的结果。
无索引行锁会升级为表锁:锁主要是加在索引上,如果对非索引字 段更新, 行锁可能会变表锁

间隙锁

前文已经提到过幻读,幻读只有在串行化的事务级别下可避免,但是现实是我们基本不可能用这个隔离级别,那如何解决幻读呢,MySql为我们提供了间隙锁。
首先看一个例子,注意查询中带有for update 是当前读哟,可重复读隔离级别下也会是这个结果。
MySQL锁与事务隔离
本来sessionA在T1时刻期望将d=5这一行锁住,继续做其他操作,但最终发现T3、T5都发现,有人新增或修改了d=5的行,这是幻读产生的一种情况,就是没有锁住期望的行。通过间隙锁就可以解决。

加间隙锁,包含了两个“原则”、两个“优化”和一个“bug”。
原则1:加锁的基本单位是next-key lock。希望你还记得,next-key lock是前开后闭区间。
原则2:查找过程中访问到的对象才会加锁。
优化1:索引上的等值查询,给唯一索引加锁的时候,next-key lock退化为行锁。
优化2:索引上的等值查询,向右遍历时且最后一个值不满足等值条件的时候,next-key lock退化为间隙锁。
一个bug:唯一索引上的范围查询会访问到不满足条件的第一个值为止。

lock in share mode只锁覆盖索引,但是如果是for update就不一样了。 执行 for update时,系统会认为你接下来要更新数据,因此会顺便给主键索引上满足条件的行加上行锁。锁是加在索引上的;同时,它给我们的指导是,如果你要用lock in share mode来给行加读锁避免数据被更新的话,就必须得绕过覆盖索引的优化,在查询字段中加入索引中不存在的字段。

间隙锁的引入,可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的。
如果想进一步了解,请移步这篇博客,例子好理解。
https://www.cnblogs.com/gaosf/p/11149600.html