一、什么是事务

事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);

事务的四大特性:

  • 原子性(Atomicity):事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
  • 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
  • 隔离型(Isolation):一个事务的执行不能被其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
  • 持久性(Durability):指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

个人认为这四大特性总结起来就是两种:

可靠性:原子性、一致性、持久性可以归纳为可靠性。可靠就是要保证数据的一致与不丢失。数据库要保证数据的一致,就要处理commit与rollBack;显然处理commit指令的时候需要记录要提交哪些数据,rollback的时候需要知道回退的原数据。mysql中commit需要redo log,rollBack 对应undo log

并发控制(隔离性):当多个并发请求过来,并且其中有一个请求是对数据修改操作的时候会有影响,为了避免读到脏数据,所以需要对事务之间的读写进行隔离,至于隔离到啥程度得看业务系统的场景了,实现这个就得用MySQL 的隔离级别。

二、redo log 与undo log

1、redo log 

redo log和undo log都属于InnoDB的事务日志。redo log 主要实现数据的持久化

InnoDB作为MySQL的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘IO,效率会很低。为此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。

如上图所示mysql采用redo log来处理该问题:当数据修改时,除了修改Buffer Pool中的数据,还会在redo log Buffer 中记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果MySQL宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因MySQL宕机而丢失,从而满足了持久性要求。

MySQL支持用户自定义在commit时如何将log buffer中的日志刷log file中。这种控制通过变量 innodb_flush_log_at_trx_commit 的值来决定。该变量有3种值:0、1、2,默认为1。但注意,这个变量只是控制commit动作是否刷新log buffer到磁盘。

  • 当设置为1的时候,事务每次提交都会将log buffer中的日志写入os buffer并调用fsync()刷到log file on disk中。这种方式即使系统崩溃也不会丢失任何数据,但是因为每次提交都写入磁盘,IO的性能较差。
  • 当设置为0的时候,事务提交时不会将log buffer中日志写入到os buffer,而是每秒写入os buffer并调用fsync()写入到log file on disk中。也就是说设置为0时是(大约)每秒刷新写入到磁盘中的,当系统崩溃,会丢失1秒钟的数据。
  • 当设置为2的时候,每次提交都仅写入到os buffer,然后是每秒调用fsync()将os buffer中的日志写入到log file on disk。

既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:

  • 刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序IO。
  • 刷脏是以数据页(Page)为单位的,MySQL默认页大小是16KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效IO大大减少。

2、undo log

undo log 的写入时机与redo log一致。

InnoDB实现回滚,靠的是undo log:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。

undo log属于逻辑日志,它记录的是sql执行相关的信息。当发生回滚时,InnoDB会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。以update操作为例:当事务执行update时,其生成的undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态。

三、Mysql的锁机制

当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,mysql的锁机制可以达到该目的

1. 按照锁的粒度分数据库锁有哪些?锁机制与InnoDB锁算法

在关系型数据库中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。

MyISAM和InnoDB存储引擎使用的锁:

  • MyISAM采用表级锁(table-level locking)。
  • InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁

行级锁:行级锁是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。

特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

表级锁:表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。

特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。

页级锁 :页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。

特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

从锁的类别上来讲,有共享锁和排他锁。

  • 共享锁(S锁): 又叫做读锁。当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个。事务T对数据对象A加上共享锁,则事务T可以读A但不能修改A,其他事务只能再对A加共享锁,而不能加排他锁,直到T释放A上的共享锁。这保证了其他事务可以读A,但在T释放A上的共享锁之前不能对A做任何修改。
  • 排他锁(X锁): 又叫做写锁。当用户要进行数据的写入时,对数据加上排他锁。排他锁只可以加一个。若事务T对数据对象A加上排他锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的排他锁之前不能再读取和修改A。

2、InnoDB锁的特性

由于 MySQL 的Innodb引擎的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。

在不通过索引条件查询的时候,InnoDB使用的是表锁!

当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论 是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。
即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同 执行计划的代价来决定的,如果 MySQL 认为全表扫 效率更高,比如对一些很小的表,它 就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。因此,在分析锁冲突时, 别忘了检查 SQL 的执行计划(explain查看),以确认是否真正使用了索引。

三、Mysql的隔离机制

Read uncommitted 读未提交:READ UNCOMMITTED级别忽略其它事务放置的锁。使用READ UNCOMMITTED级别运行的事务,能够读取尚未由其它事务提交的改动后的数据值,这些行为称为“脏”读。我们所说的脏读,两个并发的事务,事务A可以读取到事务B未提交的数据。假设事务A回滚,事务B就读取了一行没有提交的数据。这种数据我们觉得是不存在的。

Read committed 读提交:一个事务只能读取另一个事务已经提交的修改。其避免了脏读,但仍然存在不可重复读和幻读问题。大多数数据库的默认级别就是Read committed。比方Sql Server , Oracle。

Repeatable read 反复读:该级别指定了在当前事务提交之前,其它不论什么事务均不能够改动或删除当前事务已读取的数据。并发性低于 READ COMMITTED。由于已读数据的共享锁在整个事务期间持有,而不是在每一个语句结束时释放。这个隔离级别仅仅是说,不可以改动和删除,可是并没有强制不能插入新的满足条件查询的数据行。所以会产生“幻读”;Mysql的默认隔离级别就是Repeatable read

Serializable 串行读:完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

隔离级别 读数据一致性 脏读 不可重复读 幻读
未提交读(Read uncommitted) 最低级别隔离,只能保证不读取物理上损坏的数据
已提交读(Read committed) 语句级别
可重复读(Repeatable read) 事务级别
可序列化(Serializable) 最高级别,事务级
  • 脏读(Drity Read):某个事务已更新一份数据,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
  • 不可重复读(Non-repeatable read):在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新了原有的数据。不可重复读主要针对的是update与delete
  • 幻读(Phantom Read):在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。幻读主要是针对insert;

1、mysql解决幻读的方式:MVCC

在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。 在可重读Repeatable reads事务隔离级别下:

  • SELECT时,读取创建版本号<=当前事务版本号,并且会移除版本号为空或>当前事务版本号的数据行。
  • INSERT时,保存当前事务版本号为行的创建版本号
  • DELETE时,保存当前事务版本号为行的删除版本号
  • UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行

举例说明MVCC如何避免幻读的:事务A读取age<20的数据,返回5条,Mysql为其创建的事务版本号是10001;此时事务B插入age=18的一条数据,Mysql为其创建的事务版本号是10001;紧接着事务A再次查询age<20的数据,返回依然是5条,也就是事务B新插入的数据对于事务A来说是隔离的。

由此我们发现在RR级别中,通过MVCC机制,虽然让数据变得可重复读,并且避免的幻读,但我们读到的数据可能是历史数据,是不及时的数据,不是数据库当前的数据!这在一些对于数据的时效特别敏感的业务中,就很可能出问题。对于这种读取历史数据的方式,我们叫它快照读 (snapshot read),而读取数据库当前版本数据的方式,叫当前读 (current read)。很显然,在MVCC中是采取的快照读;如果要实现当前读就需要使用锁机制。

最新文章

  1. 大三那年在某宝8块钱买的.NET视频决定了我的职业生涯
  2. mac-android-虚机加速
  3. varnish 的一个配置
  4. IOS开发 模型赋值 runtime
  5. boost::xml——基本操作以及中文乱码解决方案
  6. wpf 动画
  7. Spring 集成 Dubbo
  8. Angular4 后台管理系统搭建(2) - flexgrid 单元格模板 wjFlexGridCellTemplate 的坑
  9. qt 拖拽 修改大小(二)
  10. Linux内存管理 (17)KSM
  11. redis pipeline 独占链接
  12. docker machine 使用教程
  13. Unicode与中文转换工具类方法(转)
  14. Canvas动画 位图缓存提高效率和对应的内存问题
  15. FPM打包工具使用
  16. React——JSX
  17. UVA 1341 - Different Digits(数论)
  18. 基于struts2--实现文件上传下载
  19. java grpc简单例子
  20. 推荐两份学习 Kotlin 和机器学习的资料

热门文章

  1. JDK源码阅读-ByteBuffer
  2. ffmpeg第4篇:为视频添加动态水印
  3. 微信小程序优化:实现picker组件中input输入框禁止输入,而只能通过picker组件选择日期
  4. js 表格插入指定行
  5. docker轻量级监控-sysdig
  6. 框架进行时——SSM整合基础环境搭建
  7. 使用NATAPP内网穿透工具
  8. nginx错误分析 `104: Connection reset by peer`
  9. LeetCode-二叉树的深度
  10. UI透明欺诈