MySQL数据库——事务(ACID特性、事务并发问题、事务隔离级别、锁类型、MVCC机制)

发布时间:2023-11-27 18:00

事务

MySql 数据库设计了事务隔离机制、锁机制、MVCC多版本并发控制隔离机制,用一整套机制来解决多事务并发问题。

事务的ACID特性

事务有四大特征:简称ACID特征
原子性(Atomicity):保证事务是一个不可分割的整体
例:支付操作必须全部完成,不能只完成一部分
一致性(Consistency):使数据库从一个一致性状态转移到另一个一致性状态
例:支付操作要么支付成功,要么支付失败。如果只成功一部分那么就要回滚(rollback)至未支付的状态
隔离性(Isolation):不同事务之间不能互相干扰
例:你支付时候别人也可以支付。你们两个支付行为不会互相影响
持久性(Durability):事务一旦提交,对于数据库中数据的改变是永久的
例:支付成功后,不能出现你拿着商品走了,商家的钱又跑到你账户上,让商家过把眼瘾的事情

并发事务处理带来的问题

更新丢失(Lost Update)或脏写
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题–最后的更新覆盖了由其他事务所做的更新。
多个事务对同一个数据的修改会导致数据覆盖
脏读
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致的状态;这时,另一个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此作进一步的处理,就会产生未提交的数据依赖关系。这种现象被形象的叫做“脏读”。
一个事务读到另一个事务修改后还未提交的数据
事务A读取到了事务B已经修改但尚未提交的数据,还在这个数据基础上做了操作。此时,如果B事务回滚,A读取的数据无效,不符合一致性要求。
不可重复读
一个事务在读取某些数据后,再次读取该数据,发现读出的数据已经发生了改变。
一个事务在两次读取同一个数据,前后结果不一致
事务A内部的相同查询语句在不同时刻读出的结果不一致,不符合隔离性
幻读
事务A读取数据后,B修改了数据。之后A再次读取数据,发现两次数据量不一样。与不可重复读类似,但表述的方向不同。不可重复读指读取的数据内容,幻读则指读取数据的行数。
事务A读取到了事务B提交的新增数据,不符合隔离性

事务隔离级别

隔离级别 脏读 不可重复读 幻读
读未提交 可能 可能 可能
读已提交 不可能 可能 可能
可重复读 不可能 不可能 可能
可串行化 不可能 不可能 不可能
读未提交:事务A可以读取到事务B还未commit的数据。可能会出现事务A读取到事务B的数据之后,事务B进行回滚,导致A的数据不正确。(脏读)
读已提交:事务A可以读取到事务B已commit的数据。也就是可能出现事务A读到一个数据,之后B事务commit修改了该数据,导致事务A读出两次数据不一致的情况。(不可重复读)
可重复读:事务A不能读取到事务B已commit的数据(MVCC多版本并发控制保证),但是MVCC只能保证无法读取到B修改的数据,但是如果B插入新的数据事务A仍然可以感知到,因此仍有幻读的问题。(幻读)
可串行化:事务A操作的时候导致事务B无法操作相同的数据。只要事务A操作,就对数据加悲观锁,导致事务B无法进行操作。只有事务A释放锁之后事务B才可以执行。
查看当前数据库的事务隔离级别

show variables like ‘tx_isolation’;

设置事务隔离级别

set tx_isolation=‘REPEATABLE-READ’;

Mysql默认的事务隔离级别:可重复读。用Spring开发程序时,如果不设置隔离级别,默认使用Mysql设置的隔离级别。

锁详解

锁分类:

  • 从性能上分为乐观锁(通过版本比对实现)和悲观锁
  • 从数据库操作的类型区分,分为读锁和写锁(都属于悲观锁)
    • 读锁(共享锁,S锁):针对同一份数据,多个读操作可以同时进行而不会互相影响
    • 写锁(排它锁,X锁):当前写操作没有完成前,会阻断其他写锁和读锁

从锁的粒度上来区分,分为表锁和行锁

  • 表锁:每次操作锁住整张表。开销小,加锁快;不会出现死锁;锁的粒度大,发生锁冲突的概率最高,并发度最低。一般用在整张表数据迁移的场景。
  • 页锁:BDB引擎支持,InnoDB和MyISAM都不支持
  • 行锁:每次操作只锁一行数据。开销大,加锁慢;锁的粒度最小,发生锁冲突的概率最低,并发度最高。
  • 间隙锁:锁定一个范围,但不包含记录本身
  • 临键锁:间隙锁+行锁,锁定一个范围,并且锁定记录本身

注意:无索引的行锁会升级为表锁。InnoDB的行锁是针对索引加的锁,不是针对记录加的锁。并且该索引不能失效,否则都会从行锁升级为表锁

MyISAM和InnoDB对比

MyISAM InnoDB
不支持事务 支持事务
锁的最小粒度为表锁 锁的最小粒度为行锁
非聚集索引 支持聚集索引

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

锁优化建议

  • 尽可能让所有数据检索都通过索引来完成,避免无索引行锁升级为表锁
  • 合理设计索引,尽量缩小锁的范围
  • 尽可能减少检索条件范围,避免间隙锁
  • 尽量控制事务大小,减少锁定资源量和时间长度,涉及事务加锁的sql尽量放在事务最后执行
  • 尽可能低级别事务隔离

MVCC多版本并发控制机制

Mysql在读已提交和可重复读隔离级别下都实现了MVCC机制。

undo日志版本链和read view机制

undo日志版本链是指一行数据被一个事务修改后,Mysql会保留修改前的数据作为undo回滚日志,并且使用两个隐藏字段trx_id和roll_pointer把这些undo日志串联起来形成一个历史记录版本链。
同时,在可重复读隔离级别,当事务开启之后执行任何查询sql时都会为当前事务生成一个read-view,该视图在事务结束之前都不会变化。(如果是读已提交的隔离级别则在每次执行查询sql时都会重新生成read-view)
read-view组成
read-view由执行查询时所有未提交事务的事务id,将id组成一个数组,并且和一个已创建的最大事务id组成。事务内的任何sql查询结果都需要从对应版本链中的最新顺序和read-view中的事务id进行对比,最终得到快照结果。

简化模型为:[min_trxid,…],max_trxid

根据这个模型将事务可以分为三部分
已提交事务部分:事务id < min_trxid的部分
未提交和已提交事务部分:min_trxid<=事务id<=max_trxid的部分
未开始事务:事务id>=max_trxid的部分
版本链比对规则
在进行版本链匹配时,根据以下规则确定当前版本数据是否对该事务是可见的。

  • 如果row的trx_id落在绿色部分(trx_id < min_id),表示这个版本是已提交的事务生成的,这个数据是可见的
  • 如果row的trx_id落在红色部分(trx_id > max_id),表示这个版本是由将来启动的事务生成的,是不可见的(若row的trx_id就是当前自己的事务则是可见的)
  • 如果row的trx_id落在黄色部分(min_id <= trx_id <= max_id),那就包括两种情况
    • 若row的trx_id在视图中的未提交事务数组中,表示这个版本是由还没提交的事务生成的,不可见(若row的trx_id就是当前事务则是可见的)
    • 若row的trx_id不在视图数组中,表示这个版本是已经提交了的事务生成的,可见。

注意:

  • Mysql的事务id是递增生成的(严格按照事务的启动顺序分配id),因此可以根据上述原则进行事务类型的划分
  • begin并不是事务的起点,在执行到它们之后的第一个修改操作InnoDB表的语句,事务才真正启动,才会向Mysql申请事务id

总结

MVCC机制的实现就是通过read-view机制与undo版本链比对机制,使得不同的事务会根据数据版本链比对规则读取同一条数据在版本链上的不同版本数据。

ItVuer - 免责声明 - 关于我们 - 联系我们

本网站信息来源于互联网,如有侵权请联系:561261067@qq.com

桂ICP备16001015号