数据库事务学习笔记

数据库事务学习笔记

水平有限,有错误之处望大神们指出,不胜感激

什么是事务

引用《高性能Mysql》中的定义:

事务就是一组原子性的sql查询,或者说一个独立的工作单元。如果数据库引擎能够成功的对数据库应用该组查询。如果其中有任何一条语句因为崩溃或其他原因无法执行,那么所有的语句都不会执行。也就是说,事务内的语句,要么全部执行成功,要么全部执行失败。

事务的特性

  • 原子性(Atomicity)
    一个事务中的所有sql操作是一个整体,要么全部成功提交,要么全部失败回滚,不允许中间状态。
    原子性的语义,只保证记录了回滚段,这个回滚段能够回滚到之前的版本。
    为了实现原子性,需要通过undo日志。将所有对数据的更新操作写入日志,如果一个事务中执行了一部分操作,后面的操作由于断电/系统崩溃等其他原因无法继续,则通过回溯日志,将已经执行过的操作撤销,从而达到“全部失败回滚”的目的。
    场景:数据库系统崩溃后重启,此时数据库处于不一致的状态,必须先执行一个crash recovery的过程,读取日志进行redo(重新执行将所有已经成功提交但为写入磁盘的操作,保证持久性),再对所有崩溃时未成功提交的事务进行undo(撤销所有执行了一部分但尚未比较的操作,保证原子性)。crash recovery结束后,数据库恢复到一致性状态。

  • 一致性(Consistency)
    书本定义:数据库总是从一个一致性的状态转换到另一个一致性的状态。
    就我个人的理解,事务的ACID中,一致性是最基本的属性,其他的三个属性都是为了保证一致性而存在的。首先我觉得我们可以把数据库的一致性状态和一个事务单元的一致性拆开来说。数据库的一致性就是一个系统的状态,一个合理的状态。而是否合理这表现在这个状态是否符合业务规则。比如说A给B转战,业务要求总额不变,那么这个一致性状态就是总金额相同。当我们的代码中写了A = A-100时,就必须再写上B = B+100才能保证一致性,否则数据库就处于不一致的状态。那么事务单元的一致性是什么?就是在事务开始和结束时,要么是A=100/B=0的状态要么是A=0/B=100的状态。这跟原子性的概念又有些重合了。但是在有些情况下,比如说事务1执行完A=A-100时,事务2执行了A=A+200,事务2结束,但是事务1回滚了到了A=100/B=0,此时整个数据库少了200,违背了一致性。所以事务如果具备一致性能保证什么?一致性保证一个事务单元全部操作结束了之后才可见,事务开始和结束之间的中间状态不会被其他事务所看到,开始和结束时的状态相同。结合上面来看,在强一致性的保证下,事务与事务之间保持happen-before关系,那么数据库一定是处于一致性的状态。

  • 隔离性(Isolation)
    书本定义:事务的执行不受其他事务的干扰,事务执行的中间结果对其他事务必须是透明的。
    这个的定义感觉又跟一致性很像,其实在序列化读写这个隔离级别下,就相当于保证了事务的强一致性。但是这样做的代价就是性能很差。
    所以隔离性的引入,其实是以性能为理由,对一致性的破坏。核心目的,就是尽可能的提高并行度。

  • 持久性(Durability)
    每一次的事务提交后就会保证不会丢失。这个没什么好说的。

事务的隔离级别

sql92标准里定义了四个级别

  • 读未提交:TRANSACTION_READ_UNCOMMITTED = 1
    事务中的修改,即使没有提交,对于其他事务也是可见的。一个事务已经开始写数据,则另外一个数据则不允许同时进行写操作,但允许其他事务读此行修改后却没有提交的数据。只加写锁,读不加锁。
    如果A事务读取到了B事务修改后但尚未提交的数据,而B事务进行了回滚,那么A事务读到的就是脏数据。这个就是脏读
    该级别是最低的隔离级别,但是从性能而言,并不比其他级别好太多。

  • 读已提交:TRANSACTION_READ_COMMITTED = 2
    大部分的数据库默认隔离级别。
    一个事务从开始到提交之前,中间状态对其他事务都是透明的。
    在RN级别中写入操作就立刻释放掉了锁,现在把释放锁的时机放在了事务提交之后,那么在事务完成之前,其他进程是无法对该行数据进行任何操作。读取的事务会允许其他事务访问该行数据,但未提交的写事务禁止其他事务方位该行。
    在该级别中,读锁可以被写锁升级,读读、读写可以并行,因此会造成一个问题,那就是不可重复读
    场景:A事务读取银行账户为100元,此时B事务将银行账户修改为50元并提交,A事务由于上一步判读该账户100元满足条件,执行update 余额 = 余额 -100。update操作其实是先读后写,这一步的读其实读到的是账户是50元,跟之前结果不同,导致最后账户中余额为-50元。

  • 可重复读取:TRANSACTION_REPEATABLE_READ = 4
    Mysql的默认隔离级别。
    RR级别对RC级别进行了升级,保证了在同一个事务中多次读到同样记录的结果是一致的。单纯从读写锁的角度来说,就是读锁不能被写锁升级,读读可以并行,读写不能并行。读取的事务允许其他事务访问该行数据进行读取,但不允许进行写操作。
    同时,由于读写锁是针对现有数据的update和delete,对于新增数据的insert操作没有控制,就造成了新的问题–幻读
    场景:A事务读取数据库发现没有记录6准备插入,B事务发现没有记录6插入了一条新记录6并提交,A事务插入记录6,发现主键冲突。insert包含了隐式的读取,在读取时发现之前没有检测到的记录。

  • 序列化:TRANSACTION_SERIALIZABLE = 8
    最高的隔离级别。
    通过强制事务串行执行,所以可能导致大量的超时和锁争用的问题。一般很少会用到该级别,只有在非常要保证强一致性而且可以接受没有并发的情况下,才考虑采用该级别。

隔离性扩展
  • MVCC/快照
    MVCC通常采取映射到四个隔离级别上的做法,一般是映射到读已提交级别和可重复读级别上。可重复读是通过mvcc来实现的又叫快照读。在事务中的读操作通过对当前数据库记录一个版本,以后的读操作只会读取记录的版本,相当于对数据库建立一个快照数据,因不用对数据进行加做就叫做乐观锁。快照读主要针对简单的select操作(不包括select … lock in share mode 和 select … for update)。
    核心思想是copy on write和无锁编程。
  • Next-key lock/当前读
    Next-key lock是将行锁(RecordLock)和间隙锁(GapLock)组合起来使用,锁定一个范围,包括记录本身,来解决幻读问题。
root@localhost : test 10:56:15>select * from t;
+------+
| a    |
+------+
|    1 |
|    3 |
|    5 |
|    8 |
|   11 |
+------+
section A:
root@localhost : test 10:56:27>start transaction;
Query OK, 0 rows affected (0.00 sec)
root@localhost : test 10:56:29>select * from t where a = 8 for update;
+------+
| a    |
+------+
|    8 |
+------+
section B:
root@localhost : test 10:54:50>begin;
Query OK, 0 rows affected (0.00 sec)
root@localhost : test 10:56:51>select * from t;
+------+
| a    |
+------+
|    1 |
|    3 |
|    5 |
|    8 |
|   11 |
+------+
root@localhost : test 10:56:54>insert into t values(2);
Query OK, 1 row affected (0.00 sec)
root@localhost : test 10:57:01>insert into t values(4);
Query OK, 1 row affected (0.00 sec)
++++++++++
root@localhost : test 10:57:04>insert into t values(6);

root@localhost : test 10:57:11>insert into t values(7);

root@localhost : test 10:57:15>insert into t values(9);

root@localhost : test 10:57:33>insert into t values(10);
++++++++++
上面全被锁住,阻塞住了

root@localhost : test 10:57:39>insert into t values(12);
Query OK, 1 row affected (0.00 sec)

因为InnoDB对行的for update查询使用了Next-key lock的算法,锁定的不是一行,而是一个范围。上面索引值有1,3,5,8,11,其记录的GAP的区间如下:是一个左开右闭的空间。
(-∞,1],(1,3],(3,5],(5,8],(8,11],(11,+∞)
特别需要注意的是,InnoDB存储引擎还会对辅助索引下一个键值加上gap lock。
这条sql语句锁住的是(5,8]这个范围,下一个键值是11,因此在(8,11]上加了gap lock。所以插入5-11之间的值都会被阻塞。
当查询的索引含有唯一属性的时候,Next-Key Lock 会进行优化,将其降级为Record Lock,即仅锁住索引本身,不是范围。但是,必须在查询条件中明确指定主键才能优化为行锁。

总结
在mysql中,提供了两种事务隔离技术,第一个是mvcc,第二个是next-key技术。这个在使用不同的语句的时候可以动态选择。不加lock inshare mode之类的就使用mvcc。否则使用next-key。mvcc的优势是不加锁,并发性高。缺点是不是实时数据。next-key的优势是获取实时数据,但是需要加锁。
同时需要注意几点:
1.事务的快照时间点是以第一个select来确认的。所以即便事务先开始。但是select在后面的事务的update之类的语句后进行,那么它是可以获取后面的事务的对应的数据。
2.mysql中数据的存放还是会通过版本记录一系列的历史数据,这样,可以根据版本查找数据。

死锁

未完待续

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值