MySQL事务和锁机制

一、事务

(1)概述

事务是数据库区别于文件系统的重要特征之一。在文件系统中,如果正在写文件,但是操作系统奔溃了,这个文件就很有可能被破坏。当然,有一些机制可以把文件回复到某个时间点。不过,如果需要保证两个文件同步,这些文件系统可能就显得无能为力了,例如,当需要更新两个文件时,更新完一个文件后,在更新完第二个文件之前系统重启了,就会产生两个不同步的文件,因此这也就是数据库系统引入事务的主要目的

一个事务是由一条或者多条数据库操作SQL语句所组成的一个不可分割的单元,只有当事务中的所有操作都正常执行完毕后,整个事务才会被提交给数据库;如果由部分事务处理失败,那么事务就要回退到最初的状态。因此事务要么全部执行成功,要么全部失败

(2)ACID特性

每一个事务都必须满足下面的4个特性:

  • 事务的原子性(Atomic): 事务是一个不可分割的整体,事务必须具有原子特性,要么全执行,要么全不执行
  • 事务的一致性(Consistency): 一个事务执行之前和执行之后,数据库数据必须保持一致性状态。数据库的一致性状态必须由用户来负责,由并发控制机制实现
  • 事务的隔离性(Isolation): 当两个或者多个事务并发执行时,为了保证数据的安全性,将一个事务内部的操作与其他事务的存在隔离起来,不被其他正在执行的事务看到,使得并发执行的各个事务之间不能相互影响 | 锁 + MVCC
  • 事务的持久性(Durability): 事务完成(commit)后,DBMS保证它对数据库中的数据是永久性的,即使数据库因为故障出错,也应该能够快速恢复数据

(3)事务并发存在的问题

如果事务处理不经过隔离,并发执行事务是通常会发生以下问题:

  • 脏读(Dirty Read): 一个事务读取了另一个事务为提交的数据,例如当事务A和事务B并发执行时,当事务A更新后,事务B查询读取到事务A尚未提交的数据,此时事务A回滚,则事务B读到的数据就是无效的脏读数据(事务B读取到来事务A未提交的数据
  • 不可重复读(NonRepeatable Read): 一个事务的操作导致另一个事务前后两处读取到不同的数据。即当事务A和事务B并发执行时,当事务B查询读取到数据后,事务A更新操作更改事务B查询到的数据,此时事务B再次去读取该数据,发现前后两处读的数据不一样(事务B读取了事务A已经提交的数据
  • 虚读 / 幻读(Phantom Read): 一个事务的操作导致另一个事务前后两次查询的结果 数据量不同 ,即当事务A和事务B并发执行时,当事务B查询读取数据后,事务A新增或者删除了一条满足事务B查询条件的记录,此时事务B再去查询,发现查询到前一次不存在的记录,或者前一次查询的一些记录不见了(事务B读取了事务A新增的数据,或者读不到事务A删除的数据

(4)事务的隔离级别

MySQL默认支持四种隔离级别,如下图:

隔离级别脏读不可重复读虚读 / 幻读
未提交读:TRANSACTION_READ_UNCOMMITTED可以可以可以
已提交读:TRANSACTION_READ_COMMITTED不可以可以可以
可重复读:TRANSACTION_REPEATABLE_READ不可以不可以update 可以
串行化: TRANSACTION_SERIALIZABLE不可以不可以不可以
  • 串行化是最高的事务级别,由于事务隔离级别越高,为避免冲特所花费的性能也越多
  • 在“可重复读”级别中,实际上是可以解决部分的虚读问题,但是不能防止update更新产生的虚读问题,还是要串行化隔离级别

select @@transation_isolation; mysql默认的隔离级别:可重复读在这里插入图片描述

我们现在一一列举各种隔离级别会出现的结果

  • 未提交读 READ_UNCOMMITTED
    在这里插入图片描述

  • 已提交读 READ_COMMITTED
    在这里插入图片描述

  • 可重复读 REPEATABLE-READ,这里对于幻读就不演示了,可以自行验证
    在这里插入图片描述

  • 串行化 SERIALIZABLE
    在这里插入图片描述

二、锁机制

(1)锁的力度

锁的力度分为两种:

  • 表级锁:对整张表加锁,开销小,加锁块,不会出现死锁;锁的力度大,发送锁冲突的概率高,并发度低
  • 行级锁:对某行记录加锁。开销大,加锁慢,会出现死锁;锁定力度最小,发生锁冲突的概率最低,并发度高
    • InnoDB行锁是通过给索引上的索引项加锁来实现的,而不是给表的行记录加锁实现的,这就意味着只有通过索引条件检索数据,InnoDB才使用行级锁,否则InnoDB将使用表锁。
    • 由于InnoDB的行锁实现是针对索引字段添加的锁,不是针对行记录加的锁,因此虽然访问的是InnoDB引擎下表的不同行,但是如果使用相同的索引字段作为过滤条件,依然会发生锁冲突,只能串行进行,不能并发进行。
    • 即使SQL中使用了索引,但是经过MySQL的优化器后,如果认为全表扫描比使用索引效率更高,此时会放弃使用索引,因此也不会使用行锁,而是使用表锁,比如对一些很小的表,MySQL就不会去使用索引。

(2)表的分类: 排他锁、共享锁

  • 共享锁(Shared)
  • 排他锁(Exclusive):又称X锁,写锁
  • 共享锁(Shared):又称S锁,读锁

X和S锁之间由以下关系:SS可以兼容,SX、XX、XS之间是互斥的

  • 一个事务对数据对象O加了S锁,可以对O进行读取操作但不能进行更新操作。加锁期间其他事务能对O加S锁但不能加X锁
  • 一个事务对数据对象O加了X锁,就可以对O进行读取和更新。加锁期间其他事务不能对O加任何锁
显示加锁语句
select ... lock in share mode;  #强制获取共享锁
select ... for update;	   #获取排他锁

下面我们来演示一下,两种锁的效果

假设我们开启了两个事务,默认工作在隔离级别为可重复读,两个事务先后执行以下语句 select * from stu where id = 2 for update,根据上述推导,XX是不可兼容的,所以前一个执行sql语句的事务会对当前查询的记录加一把排他锁,后执行sql语句的事务肯定会被阻塞;若后执行sql语句的事务换成执行... lock in share mode,同样也是阻塞,因为XS也是不兼容的;若后执行的事务获取其他记录的锁,如id = 3 for update,该记录没有加X锁,因此可以执行
在这里插入图片描述
我们再来看看另外一条sql语句 select .. name = 'aaa' for update
在这里插入图片描述
select 'bbb'竟然也会阻塞!!!
这里注意:InnoDB的行锁是加在索引项上面的,并不是单纯的给行记录加锁;如果过滤条件没有索引的话,name只是普通字段,使用的就是表锁,而不是行锁!!!
这里如果我们给name字段添加索引的话,就是加行锁,使得’bbb’能够执行
在这里插入图片描述
我们来看看隔离级别为 SERIALIZABLE的结果
在这里插入图片描述

(3)串行化解决幻读(虚读)问题 :间隙锁

当我们用范围条件而不是等值条件去查询数据时,并请求共享或者排他锁时,InnoDB会给符合条件的已有数据记录的索引项加锁;对于键值在条件范围内但是并不存在的记录,叫做“间隙(GAP)”,InnoDB也会对这个“间隙”加锁,这种锁机制就是所谓的间隙锁。

目的:防止幻读,以满足川=串行化隔离级别的要求

锁的三张算法:

  • record lock:行锁
  • gap lock:间隙锁
  • next-key lock:record lock + gap lock

示例1 范围查询:按过滤条件分为:使用主键索引 或者 辅助索引
在这里插入图片描述
在这里插入图片描述


示例二 等值查询
在这里插入图片描述
在这里插入图片描述

(4)意向共享锁和意向排他锁

在使用表锁的时候,涉及到的效率问题
要获取一张表的S或者X锁,需要检查这张表没有被其他事务获取过X锁,这表里的数据没有被其他事务获取过行锁X锁,那么如果这张表有非常多的数据,是一行一行的检查???
意向共享锁和意向排他锁就是用来解决这个问题

  • 意向共享锁(IS锁): 事务计划给记录加行共享锁,事务在给一行记录加共享锁前,必须先取得该表的IS锁
  • 意向排他锁(IX锁): 事务计划给记录加行排他锁,事务在给一行记录加排他锁前,必须先取得该表的IX锁

当我们需要获取表的X锁时,不需要再检查表中的哪些行被(X或者S)锁占用,只需要检查IX和IS锁即可!

如下图的 X和S都是表锁

XIXSIS
XConflictConflictConflictConflict
IXConflict兼容Conflict兼容
SConflictConflict兼容兼容
ISConflict兼容兼容兼容

1、意向锁是由InnoDB存储引擎获取行锁之前自己获取的
2、意向锁之间都是兼容的,不会产生冲突
3、意向锁存在的意义是为了更高效的获取表锁
4、意向锁是表级锁,协调表锁和行锁的共存关系,主要目的是显示事务正在锁定某行或者试图锁定某行。

(5)死锁

MyISAM表锁是deadlock free的,这是因为MyISAM总是一次获得所需的所有锁,要么全部满足,要么等待,因此不会发生死锁。但在InnoDB中,出来的单个SQL组成的事务外,锁是逐步获得的,即锁的力度比较小,发生死锁也是可能的,如下:

在这里插入图片描述
在这里插入图片描述

死锁问题一般都是我们自己的应用造成的,和多线程编程的死锁情况相似,大部分都是由于我们多个线程在获取多个锁资源的时候,获取的顺序不同而导致的死锁问题。因此我们应用在对数据库的多个表做更新的时候,不同的代码段,应对这些表按相同的顺序进行更新操作,以防止锁冲突导致死锁问题。

(6)锁的优化建议

  1. 尽量使用较低的隔离级别
  2. 设计合理的所有并尽量使用索引访问数据,使锁更加准确,减少锁冲突的机会提高并发能力
  3. 选择合理的事务大小,小事务发生锁冲突概率小
  4. 不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对于一个表而言,尽可能以固定的顺序存取表中的行,这样也能减少死锁的发生
  5. 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响
  6. 不要申请超过实际需要的锁级别
  7. 除非必须,查询时不要显示加锁

三、多版本并发控制(MVCC)

MVCC(Multi-Version Concurrency Control,简称MVCC),是MySQL中基于乐观锁理论实现隔离级别的方式,用于已提交读可重复读隔离级别的实现,也经常被称为多版本并发控制。MVCC机制会生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别的一致性读取。从用户的角度来看,就是数据库提供同一组数据的多个版本(系统版本号和事务版本号)。

MVCC多版本控制的读操作分为两类:

  • 快照读:读的是记录的可见版本,不用加锁,如select
  • 当前读:读的是记录的最新版本,并且返回当前的记录,如insert,delete,select,update,select … lock in share mode / for update

MVCC: 每一行记录实际上有多个版本,每个版本的记录除了数据本身之外,增加了其它字段

  • DB_TRX_ID:记录当前事务ID
  • DB_ROLL_PTR:指向undo log日志上数据的指针

已提交读: 每次执行语句的时候都重新生成一次快照(Read View),每次select查询时。
可重复读: 同一个事务开始的时候生成一个当前事务全局性的快照(Read View),第一次select查询时。

快照读取原则:

  1. 版本未提交无法生成快照
  2. 版本已提交,但是在快照创建后提交,无法读取
  3. 版本已提交,但是在快照创建前提交,可以读取
  4. 当前事务内自己更新,可以读到

在这里插入图片描述

InnoDB提供了两个读取操作:

  • 锁定读: S、X锁
  • 非锁定读: 其依赖于 undo log 回滚日志

undo log:回滚日志,保存了事务发生之前的数据的一个版本,用于执行时的回滚操作,同时也是实现多版本并发控制(MVCC)下的关键技术
在这里插入图片描述

redo log:重做日志,用于记录事务操作的变化,确保事务的持久性。redo log是在事务开始后就开始记录,不管事务是否提交都会记录下来,在异常发生时(如数据持久化过程中掉电),InnoDB会使用redo log恢复到掉电前的时刻,保证数据的完整性。InnoDB修改操作数据时,不是直接修改磁盘上的数据,实际只是修改Buffer Pool中的数据。InnoDB总是先把Buffer Pool中的数据改变记录到redo log中,用来进行崩溃后的数据恢复。 优先记录redo log,然后再由专门的线程将Buffer Pool中的脏数据刷新到磁盘上。
在这里插入图片描述

四、MySQL优化问题

对于MySQL优化问题分为3种:

  • SQL和索引的优化: 慢查询日志 -》 根据表的数据量等设置合理的慢查询时间 -》 记录慢查询sql -》 explain 分析sql执行计划 -》 优化措施
  • 应用上的优化:
    • 连接数据库:访问频繁,引入连接池中间件
    • 引入缓存(存储热点数据) redis(重点!!!)
      • 缓存数据一致性问题?
      • 缓存穿透
      • 缓存雪崩
      • redis还有哪些功能?
  • mysql server上的优化: 各种参数配置!
    • MySQL的查询缓存 query_cache_typequery_cache_size
    • 索引和数据缓存, innodb_buffer_pool_size越高,磁盘IO越少
    • MySQL线程缓存thread_cache_size
    • 并发连接数量和超时时间 max_connectionswait_timeout

五、总结

  • 事务的特征 ACID—— 原子性、一致性、隔离性、持久性
  • 事务并发的问题——脏读、不可重复度、虚读/幻读
  • 事务的隔离级别——未提交读、已提交读、可重复读、串行话
  • 锁的力度——行级锁、表级锁
  • 锁的分类——排他锁、共享锁(意向共享锁、意向排他锁)
  • 使用非索引字段获取排他锁时,实际上是直接加了表锁
  • 串行化解决的幻读问题——间隙锁(范围查询、等值查询)
  • 锁的优化建议——低隔离级别、设计合理索引、使用相同顺序、尽可能等值查询
  • 可重复读和已提交读隔离级别 通过MVCC机制中的快照读(非锁定读)保证,依赖于undo log回滚日志
  • MYSQL优化——sql和索引、应用层、mysql server

🌻🌻🌻以上就是有关于MySQL事务和锁机制的内容,如果聪明的你浏览到这篇文章并觉得文章内容对你有帮助,请不吝动动手指,给博主一个小小的赞和收藏 🌻🌻🌻

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

leisure-pp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值