目录
第一章 数据库基础知识
文章目录
- 目录
- 前言
- 数据库事务
-
- 一、定义
- 二、要求
- 三、影响
- 四、特性(ACID)
-
- (1)A:原子性 Atomicity
- (2)C:一致性 Consistency
- (3)I:隔离性 Isolation
-
- 事务的隔离级别及解决方案
-
- 1.未提交读
- 2.已提交读
- 3.可重复读
- 4.可串行化
- 总结
- (4)D:永久性 Durability
-
-
- 愿你享受温暖感受爱,sincerely,end.
-
前言
认为自己大学时学得不够深入,发现实际工作中需要用到它的地方很多。因此记下此文帮助自己梳理知识,也希望可以给需要的你提供帮助。自学之路,如有错误,请纠正,万分感谢!
来自笔者内心理解(以及结合相关文献 综合思考后)的正文:
数据库事务
一、定义
数据库事务是一个不可分割的工作单位,由事务开始与事务结束之间执行的全部数据库操作构成,这些操作要么全部执行,要么全部不执行。
二、要求
一个数据库事务可以包含多个查询、修改、删除、插入等数据库动作,它们要么作为一个整体完全得到确认,要么完全失败;一个事务只能包含对一个数据库实例的数据操作,跨多个数据库实例需要分布式事务的支持。
三、影响
数据库事务会给数据库并发操作带来一定影响,降低系统的并发能力。
四、特性(ACID)
(1)A:原子性 Atomicity
一个原子事务中的所有操作要么全部成功,要么全部失败。即整个事务中的每项任务都必须正确执行,如有任一任务执行失败,则整个事务就会被终止,此前对数据所作的任何修改都将被撤销(回滚);如果每项任务都执行成功,事务则会被提交。是数据库系统区别于其他一切文件系统的重要特性之一(文件系统出错不可恢复)。
看个小例子吧~
假如我们需要进行转账服务,以下表格中的每一步都需要保证全部正确,否则需要回滚到初始状态
First | 检查活期存款余额是否高于转账金额 |
---|---|
Second | 活期存款余额 减去 转账金额 |
Third | 转账账户余额 加上 转账金额 |
(2)C:一致性 Consistency
数据库总是从一个一致性的状态转换到另一个一致性的状态。
承接上个小例子~
我们的转账服务中转出账户的扣除金额与转入账户的转入金额必须保持一致。
Q: | 谁来保证? |
---|---|
A: | 事务系统 & 应用开发人员 |
Q: | 如何保证? |
A: | 事务系统:保证事务的原子性、隔离性和持久性 应用开发人员:保证数据库有适当的约束,并且事务中所实现的业务逻辑要与预期现实业务情况完全一致 |
(3)I:隔离性 Isolation
针对并发事务而言,事务必须在不干扰其他进程或事务的前提下独立执行。(有级别设置)即要隔离并发运行的多个事务之间的影响。
写在前面的两个关键:共享锁和排他锁,是实现隔离级别的两种锁模式,都为悲观锁①。
共享锁 [S锁_shared lock]:
是一种读锁。
当事务A对数据对象B加S锁时,事务A可以读B但不能修改B,其他事务也能对B加S锁,但不能加X锁,直到A释放B上的S锁。
这保证了其他事务在A释放B上的S锁之前对B可读,但不可写。
排他锁 [X锁_exclusive lock]:
是一种写锁。
当事务A对临界区B加X锁时,事务A可以读写B,但其他事务不能对B加任何锁(共享锁,排他锁),直到A释放B上的X锁。
这保证了其他事务在A释放B上的X锁之前不能对B进行读写。
总结:
共享锁保证大家可以一起做只读操作(e.g. select),不可以有人再写入;
排他锁保证只能一个人去处理数据(用于数据修改,e.g. insert、update、delete),其他人不能读写。
X锁比S锁有更高的优先级,一个X锁请求可能会被插入到S锁队列的前面,但S锁不能插入到X锁前面。
注释(可跳过):实现并发控制的主要手段大致可以分为乐观并发控制和悲观并发控制两种。
①悲观锁:
在操作数据时,认为此操作会出现数据冲突,所以在每次操作都需通过获取锁才能进行。
方法 由数据库自己实现,用的时候,直接调用数据库相关语句就可以。
场景 适用于冲突频率高、重试代价大的场景,为数据处理的安全提供了保证。
缺点 会降低并行性,需耗费较多的时间,增加产生死锁的机会。
乐观锁:
在操作数据时,不认为此操作会出现数据冲突(不加锁),在操作结束后再判断是否有冲突。
方法 需要我们自己实现,可以使用版本号等机制,数据库不自带。
场景 适用于读操作多、响应效率高的场景,可以提高程序的吞吐量。
缺点 锁的粒度如果掌握不好,更新失败的概率会比较高。
事务的隔离级别及解决方案
事务隔离级别 | 脏读 ② | 不可重复读③ | 幻读④ |
---|---|---|---|
未提交读[Read Uncommitted] | 允许 | 允许 | 允许 |
已提交读[Read Committed] | 禁止 | 允许 | 允许 |
可重复读[Repeatable Read] | 禁止 | 禁止 | 允许 |
可串行化[Serializable] | 禁止 | 禁止 | 禁止 |
上表中,在相同运行环境下,不同隔离级别的顺序按照并发性由高到低,隔离性由低到高排序。
注释(也许看完注释之后依旧云里雾里,that’s OK,我们还有小例子在后面):
②脏读:事务A读取到了事务B没有提交的数据
③不可重复读:在事务A中,两次读取同一数据,得到的内容不同
④幻读:在事务A中,相同操作读取两次,得到的记录数不同
由下列例子帮助我们更好的理解事务的隔离级别:
1.未提交读
加锁机制:在写事务时加行级共享锁,读事务时不加锁
例子:事务A修改了一条记录的内容,但是并没有提交(commit),在隔离级别为未提交读的情况下,事务B可以读取到A修改后并未提交的记录内容。(由于是共享锁,写事务未提交前其他事务仍然能读)
有何影响呢?一旦A执行回滚操作(没有commit之前都是可以回滚的),B之前所读取的记录内容则为脏数据,这就造成了脏读。[不仅脏读无法避免,不可重复读(下面2.已提交读中介绍)、幻读(下面3.可重复读中介绍)也无法避免]
2.已提交读
是如何避免脏读的呢?加锁机制:写事务时加行级排他锁,事务结束才释放;读事务时加行级共享锁,读完立即释放锁(不等到整个事务结束后才释放)。(确保写一行时其他事务无法读写此行,只有commit以后才可读写,读一行时其他事务只能读,无法写这一行)
例子:事务A查询一条记录后,并没有结束事务A,在隔离级别为已提交读的情况下,接着事务B修改了A刚才查询的那条记录(既然是已提交读,那B修改内容后需要commit)。
有何影响呢?当A又再次查询这条记录时,发现与之前查询的记录不同(因为事务A没结束的时候B修改了内容,导致事务A两次读取不一致)。前后查询的记录不一就造成了不可重复读。[不仅不可重复读无法避免,幻读(下面3.可重复读中介绍)也无法避免]
3.可重复读
是如何避免不可重复读的呢?加锁机制:写事务时加行级排他锁,读事务时加行级共享锁,都持续到事务结束才释放。(确保整个事务中写就是写,读就是读,整个事务读完之后才可以写)
例子:事务A根据条件查询一组记录,之后事务B在A查询条件的记录范围内插入一条记录。
有何影响呢?当A又使用相同的方式再次对表进行检索时,却发现了一条新纪录。这个新记录对A来说就像突然出现的一样,这就造成了幻读。(读写事务只是加了行级锁,其他事务虽然不能修改这些行,但是能添加新行,因此出现幻读现象)
4.可串行化
是如何避免幻读的呢?加锁机制:读事务时加表级共享锁,写事务时加表级排他锁。(避免其他事务对该表的操作)
总结
事务隔离级别 | 总结 | 数据库默认隔离级别 |
---|---|---|
未提交读[Read Uncommitted] | 事务A中的修改,即使没有提交,其他事务也可以看得到 | |
已提交读[Read Committed] | 事务A中的修改只有提交以后才能被其它事务看到 有可能发生不可重复读和幻读 |
SQLServer , Oracle |
可重复读[Repeatable Read] | 事务A中看到的每条记录的结果一致 有可能发生幻读 |
MySQL |
可串行化[Serializable] | 严格要求数据的一致性 靠大量加锁实现,导致大量的锁超时和锁征用问题,效率低下 |
(4)D:永久性 Durability
一旦事务提交成功,它对于数据的修改就会永久保存到数据库中,此时即使系统崩溃,修改的数据也不会丢失(磁盘损坏不包括,需要靠备份等)。