文章目录
认识事务
事务就是一组原子性的SQL查询,或者说是一个独立的工作单元。如果数据库引擎能够成功地对数据库应用该组查询的全部语句,那么就执行该组查询。如果其中有任何一条语句因为崩溃或其他原因无法执行,那么所有的语句都不会执行。也就是说,事务内的语句,要么全部执行成功,要么全部执行失败。
事务的ACID特性
银行应用是解释事务的一个经典例子。假设一个银行的数据库有两张表:支票(checking)表和储蕃(savings) 表。现在要从用户Jane的支票账户转移200美元到她的储著账户,那么需要至少三个步骤:
- 检查支票账户的余额高于200美元。
- 从支票账户余额中减去200美元。
- 在储著账 户余额中增加200美元。
事务都是具有ACID特性的。具体如下:
(InnoDB引擎能够完全符合ACID的特性。)
- 原子性(atomicity)
一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性。
- 一致性(consistency)
数据库总是从一个-致性的状态转换到另外一个一致性的状态。在前面的例子中,一致性确保了,即使在执行第三、四条语句之间时系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。
- 隔离性(isolation) ,别称并发控制,可串行化,锁等
通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外一个账户汇总程序开始运行,则其看到的支票账户的余额并没有被减去200美元。后面我们讨论隔离级别(Isolation level)的时候,会发现为什么我们要说“通常来说”是不可见的。
- 持久性(durability)
事务一旦提交,其结果就是永久性的。即使发生宕机等故障,数据库也能将数据恢复。需要注意的是,只能从事务本身的角度来保证结果的永久性。例如,在事务提交后,所有的变化都是永久的。即使当数据库因为崩溃而需要恢复时,也能保证恢复后提交的数据都不会丢失。但若不是数据库本身发生故障,而是- -些外部的原因,如RAID卡损坏、自然灾害等原因导致数据库发生问题,那么所有提交的数据可能都会丢失。因此持久性保证事务系统的高可靠性(High Reliability),而不是高可用性(High Availabiliy)。对于高可用性的实现,事务本身并不能保证,需要-些系统共同配合来完成。
事务的分类
- 扁平事务
- 带有保存点的扁平事务
- 链事务
- 嵌套事务
- 分布式事务
(1) 扁平事务
其实就是简单的事务模型,由start开始,commit结束。如下所示:
如果中间带有很多的SQL请求,但是只有最后一个失败,那么就需要全部回滚。代价非常大。
这时可能的情况就是也许我们只回滚到倒数第二个请求,然后重试最后一个就能够成功了。但是扁平事务不支持部分回滚。
(2) 带有保存点的扁平事务
解决了上面提到的那种窘境。带有了保存点以支持回滚部分,而不是全部。
(3) 链事务
保存点模式的一种变种。带有保存点的扁平事务,当发生系统崩溃时,所有的保存点都将消失,因为其保存点是易失的(volatile),而非持久的(persistent). 这意味着当进行恢复时,事务需要从开始处重新执行,而不能从最近的-一个保存点继续执行。
链事务的思想是:在提交-一个事务时,释放不需要的数据对象,将必要的处理上下文隐式地传给下一个要开始的事务。注意,提交事务操作和开始下- -个事务操作将合并为一个原子操作。这意味着下一个事务将看到上一个事务的结果,就好像在一个事务中进行的一样:
与带有保存点的扁平事务不同的是,链事务只能回滚到上一个点。对于锁也是在执行commit后就释放了当前事务所持有的锁。
(4)嵌套事务
InnoDB使用带有保存点的扁平事务来模拟实现。但是会不支持并行性。
(5) 分布式事务
就是一个在分布式环境下的扁平事务。
Mysql的隔离性一般是通过锁来实现。那么我们先来介绍一下锁,然后来讨论隔离性的几个级别。
开发多用户、数据库驱动的应用时,最大的一个难点是:一方面要最大程度地利用数据库的并发访问,另外一方面还要确保每个用户能以-致的方式读取和修改数据
。为此就有了锁(locking)的机制,同时这也是数据库系统区别于文件系统的一个关键特性
人们认为行级锁总会增加开销。实际上,只有当实现本身会增加开销时,行级锁才会增加开销
。InnoDB 存储引擎不需要锁升级,因为一个锁和多个锁的开销是相同的
。
一:Mysql InnoDB
InnoDB的锁
- MyISAM引擎:表锁 。插入会有很大的问题。
- SQL Server :页锁,但是在特定场景下会升级。
- InnoDB :行锁。几乎没有开销。
lock 和 latch
- latch:要求锁定的时间特别短。如果长就会影响性能。没有死锁检测机制
- lock:就是锁。commit /rollback时释放。
锁的分类
- 共享锁(S Lock)
- 排他锁 (X Lock)
都是行锁哦~~
什么是意向锁?
意向锁意味着事务希望在更细粒度(ine granularity).上进行加锁。
若将上锁的对象看成-棵树,那么对最下层的对象上锁,也就是对最细粒度的对象进行上锁,那么首先需要对粗粒度的对象上锁。例如图6-3,如果需要对页上的记录r进行上X锁,那么分别需要对数据库A、表、页上意向锁IX,最后对记录r上X锁。若其中任何一个部分导致等待,那么该操作需要等待粗粒度锁的完成
。
举例来说,在对记录r加X锁之前,已经有事务对表1进行了S表锁,那么表1上已存在S锁,之后事务需要对记录r在表1.上加上Ix,由于不兼容,所以该事务需要等待表锁操作的完成。
其实意向锁就是表级别的锁,只要有两种:
- 1)意向共享锁(IS Lock),事务想要获得-一张表中某几行的共享锁
- 2)意向排他锁(IX Lock),事务想要获得一张表中某几行的排他锁
一致性非锁定读(如果有锁,是如何读取数据的?)
一致性的非锁定读(consistent nonlocking read)是指InnoDB存储引擎通过行多版本控制(multi versioning)的方式来读取当前执行时间数据库中 SQLQuery行的数据如果读取的行正在执行DELETE或UPDATE操作,这时读取操作不会因此去等待行上锁的释放。相反地,InnoDB 存储引擎会去读取行的一个快照数据。快照数据就是当前行的某个历史版本,具体读取哪个版本还需要看具体是那个隔的级别。
一致性锁定读
对读加S 锁。
自增长与锁(为什么在创建表的时候要加上主键?而且还是auto_increatment)
在InnoDB存储引擎的内存结构中,对每个含有自增长值的表都有- -个自增长计数器(auto-increment counter)。当对含有自增长的计数器的表进行插人操作时,这个计数器会被初始化,执行如下的语句来得到计数器的值:
SELECT MAX(auto_ inc_ col) PROM t FOR UPDATE:
插人操作会依据这个自增长的计数器值加1赋予自增长列。这个实现方式称做AUTO-INC Locking.这种锁其实是采用- -种特殊的表锁机制
,为了提高插入的性能,锁不是在-个事务完成后才释放,而是在完成对自增长值插入的SQL语句后立即释放。
因为是表锁,那么自然并发性就会收到影响。
外键与锁
在InnoDB存储引擎中,对于一个外键列,如果没有显式地对这个列加索引,InnoDB 存储引擎自动对其加一个索引,因为这样可以避免表锁
对于外键值的插入或更新,首先需要查询父表中的记录,即SELECT父表。但是对于父表的SELECT操作,不是使用一致性非锁定读的方式,因为这样会发生数据不-致的问题
,因此这时使用的是SELECT-LOCK IN SHARE MODE方式,即主动对父表加一个S锁。如果这时父表上已经这样加X锁,子表上的操作会被阻塞。
在上述的例子中,两个会话中的事务都没有进行COMMIT或ROLLBAGK操作,而会话B的操作会被阻塞.这是因为id为3的父表在会话A中已经加了一个X锁,而.此时在会话B中用户又需要对父表中id为3的行加-一个s锁,这时INSERT的操作会被阻塞。设想如果访问父表时,使用的是-致性的非锁定读,这时Session B会读到父表有id=3的记录,可以进行插人操作。但是如果会话A对事务提交了,则父表中就不存在id为3的记录。数据在父、子表就会存在不-致的情况。
锁的算法(这个玩意儿还得要就一下有什么卵用)
- Record Lock :单个行记录上的锁
- Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
- Next-Key Lock:Record Lock + Gap Lock,锁定一个范围,并且锁定记录本身。
死锁的解决
- 超时机制
- 等待图:其实很简单,就是下面的这样
复杂一点就是:
死锁的检测:通过锁管理器->(检测)->等待图
超时解除,尽量回滚执有最少行级锁的事务
锁升级
行锁->页锁->表锁的过程。
事务的特性-隔离性之隔离级别
在SQL的标准中定义了四种隔离级别。每一个级别都规定了一个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。当然,较低的隔离级别就会支持更高的并发。系统开销也会更低。
具体如下:
- READ UNCOMITTED (未提交读)
- READ COMMITTED (提交读)
- REPEATABLE READ (可重复读)
- SERIALIZABLE (可串行化)
(1) READ UNCOMITTED (未提交读)
其他事务会读取到本事务没有提交的数据。
这种现象也称之为脏读。会导致很多问题,性能也不是特别高。除非真的有非常必要的理由,在实际应用中一般很少使用。
(2) READ COMMITTED (提交读)
就是能够读取到其他事务已经提交了的数据。
(Mysql默认不是这个,为啥我觉得这个是最合理的呐??)这个级别有时候也叫做不可重复读(nonrepeatableread)
,因为两次执行同样的查询,可能会得到不一样的结果。(就是函数可能会变得不幂等)
举例:A查询数据库得到数据,B去修改数据库的数据,导致A多次查询数据库的结果都不一样【危害:A每次查询的结果都是受B的影响的,那么A查询出来的信息就没有意思了】
(3) REPEATABLE READ (可重复读)
Mysql InnoDB引擎默认级别!该级别保证了在同一个事务中多次读取同样记录的结果是一致的。但是又无法解决幻读的问题:
-
幻读:指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row),也就是会多出来几行数据。
-
幻读与不可重复读的区别:
幻读是只读一次,不可重复读是多次
。
InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC, Multiversion Concurrency Control)解决了幻读的问题
(4) SERIALIZABLE (可串行化)
SERIALIZABLE是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说,SERIALIZABLE 会在读取的每- -行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的- -致性而且可以接受没有并发的情况下,才考虑采用该级别。
前面提到了MVCC,OK,那么什么是MVCC呐?它又是怎样解决幻读的呐?
MVCC :多版本并发控制(依据高性能而来,可能与现在的实现不同,但是原理是一样的)
InnoDB为数据库中存储的每一行添加两个字段。分别是:行的创建时间,行的过期时间(或删除时间)。但是他们不表示时间的概念,而是事务ID
。每开始一一个新的事务,整个系统的版本号都会自动递增。下面看一下在 REPEATABLE READ 隔离级别下,MVCC具体是如何操作的。
MVCC只在REPEATABLE READ 和READ COMITTED 两个隔离级别下工作。其他两个隔离级别都和MVCC不兼容生,
因为READ UNCOMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁。
由此可见,它是能够解决幻读这个问题的。
Mysql 是如何实现事务的
原子性、一致性、 持久性通过数据库的 redolog 和 undolog来完成。redolog称为重做日志,用来保证事务的原子性和持久性。undolog用来保证事务的一致性。
tmd感觉越学越多啊。搞不动了,下来再整一下。
Redis
Redis 事务的执行流程由MULTI开始,由EXEC命令结束。我们一点一点来看:
事务的实现
将命令放入队列---->> 不间断执行事务。
具体一点的数据结构:
Redis不支持事务回滚
。如果出现命令错误,他也只是继续执行。原因:(1)与主旨不符。 (2)编程错误产生的呢
Redis 如何确保一致性?----出错处理
- 入队错误:拒绝执行。
- 执行错误:出错的命令不会被执行。
- 服务器停机:持久化,若没有就空白。
隔离性:单线程+不间断执行。
持久性:由持久化模式决定。
OceanBase
有时间再来写吧。