事物的定义:

事务是指以执行start transaction命令开始,到执行commit或者rollback命令结束之间的全部 SQL 操作,如果这些 SQL 操作全部执行成功,则执行commit命令提交事务,表示事务执行成功;如果这些 SQL 操作中任一操作执行失败,则执行rollback命令回滚事务,表示事务执行失败,并将数据库回滚到执行start transaction命令之前的状态。特别地,在现阶段的 MySQL 数据库中,仅 InnoDB 和 NDB 两个存储引擎是支持事务的。



使用代码:
from django.db import transaction
with transaction.atomic():  #都在中,要么都成功,要么都失败
    author_detail=models.AuthorDetail.objects.create(addr='xxx',phone='123',sex=1)
    raise Exception('抛了异常')
author = models.Author.objects.create(name='llqz',age=19,author_detail=author_detail)

#2 视图函数装饰器,这一个视图函数都在一个事物中
#@transaction.atomic
#def index(request):
# return Httpresponse('ok')

3 整个http请求,在事物中,在settings.py中配置
DATABASES = {
'default': {
...
'PORT': 3306,
'ATOMIC_REQUEST': True,

}
}
'ATOMIC_REQUEST':True
设置为True统一一个http请求对应的所有sql都放在一个事物中执行(要摸都成功,要摸都失败)

事务的ACID是什么?

原子性(Atomicity)

实现原理:undo dog(回滚日志)

是当事务回滚时能够撤销所有已经成功执行的 SQL 语句。InnoDB 实现回滚,靠的是undo log:当事务对数据库进行修改时,
InnoDB 会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将
数据回滚到修改之前的样子。当数据不成功时,InnoDB 会根据undo log的内容做与之前相反的工作。比如insert:delete,delete:
insert,update执行和update相反的命令,将数据改回去。

持久性(Durability)

实现原理:redo log(重做日志)

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中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。

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

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

刷脏是随机 IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序 IO。
刷脏是以数据页(Page)为单位的,MySQL 默认页大小是 16 KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效 IO 大大减少。
我们知道,在 MySQL 中还存在binlog(二进制日志)也可以记录写操作并用于数据的恢复,但二者是有着根本的不同的:

作用不同:redo log是用于crash recovery的,保证 MySQL 宕机也不会影响持久性;binlog是用于point-in-time recovery的,保证服务器可以基于时间点恢复数据,此外binlog还用于主从复制。

层次不同:redo log是 InnoDB 存储引擎实现的,而binlog是 MySQL 的服务器层实现的,同时支持 InnoDB 和其他存储引擎。

内容不同:redo log是物理日志,内容基于磁盘的Page;binlog的内容是二进制的,根据binlog_format参数的不同,可能基于 SQL 语句、基于数据本身或者二者的混合。

写入时机不同:binlog在事务提交时写入;redo log的写入时机相对多元:
前面曾提到当事务提交时会调用fsync对redo log进行刷盘,这是默认情况下的策略,修改innodb_flush_log_at_trx_commit参数可以改变该策略,但事务的持久性将无法保证。
除了事务提交时,还有其他刷盘时机,如master thread每秒刷盘一次redo log等,这样的好处是不一定要等到commit时刷盘,commit速度大大加快。

隔离性(Isolation):要求同一时刻只能有一个事物对数据进行写操作,InnoDB是通过锁机制来实现

锁机制原理:事物想要修改数据,必须先获得锁,才能修改,并且在该事务操作期间,被修改的数据是锁定的,只有当改事物完成修改成功或回滚后,释放出锁,其他事物去获得这把锁,才能对数据进行操作。

锁的级别:

按照力度:可分为表锁,行锁以及位于二者之间的锁。
表锁:在操作数据时会锁定整张表的数据,并发能力差
行锁:在操作数据时,只锁定需要操作的数据,并发性能好。
注意:MyIsam只支持表锁。而InnoDB,即支持表锁也支持行锁(出于性能考虑一般使用行锁)。
如何查看锁信息:有多种方法可以查看 InnoDB 中锁的情况,例如

select * from information_schema.innodb_locks; # 查询锁的概况
show engine innodb status; # 查询 InnoDB 整体状态,其包括锁的情况

脏读、不可重复读和幻读

脏读:事务A读取到了事务B未提交的数据(脏数据)
不可重复读:事务A先后两次读取同一个数据,两次读取的数据不一样,这种现象叫做不可重复读。

脏读与不可重复读的区别在于:脏读读到的是未提交的数据,而后者读到的是已经提交的数据

幻读:在事务A中按照某个条件(查询0<id>5的所有用户的余额)先后两次查询数据库,两次查询结果的条数不同,这种现象叫做幻读。

不可重复读与幻读的区别:前者是数据变了,后者是数据的行数变了

事物的隔离级别

 

 

 

可以通过如下两个命令分别查看全局隔离级别和本次会话的隔离级别:

select @@global.tx_isolation # 查询全局隔离级别
select @@tx_isolation # 查询本次会话隔离级别


InnoDB 默认的隔离级别是RR,后文会重点介绍RR。需要注意的是,在 SQL 标准中,RR是无法避免幻读问题的,但是 InnoDB 实现的RR避免了幻读问题。

MVCC(多版本的并发控制协议)

(一个事务)写操作对(另一个事务)写操作的影响:锁机制保证隔离性
(一个事务)写操作对(另一个事务)读操作的影响:MVCC 保证隔离性

MVCC最大的优点是读不加锁,因此读写不冲突,并发性能好。
InnoDb如何实现MVCC:主要是依靠数据的隐藏列(也可以称之为标记位)和undo log。
数据的隐藏列:包括该行数据的版本号,删除时间,只想undo log指针等。当读取数据时,MySQL可以通过隐藏列判断是否需要回滚并找到回滚需要的undo log,从而实现MVCC。

 

小结

概括来说,InnoDB 实现的RR,通过锁机制、数据的隐藏列、undo log和类next-key lock,实现了一定程度的隔离性,可以满足大多数场景的需要。不过需要说明的是,RR虽然避免了幻读问题,但是毕竟不是Serializable,不能保证完全的隔离,下面是一个例子,大家可以自己验证一下。

 

一致性(Consisitency)

一致性,是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变)。



一次封锁和两段锁???


4 (拓展)事物的隔离级别是什么?
快照读(读取历史数据的方式):就是selete操作。

当前读:特殊的读操作。insert,update,和delete操作,属于当前读,处理的都是当前的数据,需要加锁。

事务的隔离级别实际上都是定义了当前读的级别,MYSQL为了减少锁处理(包括等待其他锁的时间),提升并发能力,引入了快照读的概念,使得selete不用加锁。而update、insert等这些当前读,就需要另外的模块来解决。因为更新数据、插入数据是针对当前数据的,所以不能以快照的历史数据为参考



行锁可以防止不同事务版本的数据修改提交时造成数据冲突的情况。但如何避免别的事务插入数据就成了问题。行锁防止别的事务修改或删除,间隙锁防止别的事务新增,行锁和间隙锁结合形成的的`next-key lock`锁就共同解决了`RR`级别在写数据时的幻读问题。

 

(五)什么是乐观锁,悲观锁,mysql如何实现悲观锁和乐观锁?

悲观锁:当数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理的过程中,将数据处于锁定状态。

悲观锁的实现:往往依靠数据库提供的锁机制(也只有数据库提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)

在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时要加锁,其它事务无法修改这些数据;修改删除数据时也要加锁,其它事务无法读取这些数据







乐观锁:比较宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作做大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制在一定程度上解决了这个问题。

- 乐观锁大多是基于数据版本(`version`)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个`version`字段来实现。
- 读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

 

posted on 2020-11-10 22:59  輪滑少年  阅读(157)  评论(0编辑  收藏  举报