初探 Django 事务
数据库事务(简称:事务)是数据库管理系统(DBMS)执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。试想下面这个例子:
当A需要在商店中购买商品,需花费100元,A通过手机扫码完成支付,涉及到金额的变化如下:
- 该人账户减少100元
- 商店账户增加100元
如果这两步不能同时在系统上完成,则有可能出现两种异常情况:
- 该人账户余额没有减少,商店账户增加100元
- 该人账户减少100元,商店账户没有增加。
无论是哪一种,对于金融行业来说,都是无法容忍的。如果支付宝频繁出现此类事情,我想大家可能慢慢又会使用纸币或者刷卡吧~
无论是账户增加还是减少,从数据库层面都是几条数据库操作序列的组合。解决此类问题的思路也很简单:
要么需求的一组数据库操作序列都成功,如果其中有一个异常,则此组操作不生效(通常叫做回滚)。
ACID
数据库事务满足 ACID 原则:
- 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
- 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。
相对于上面的付款案例如下:
- 原子性:确保不管交易过程中发生了什么意外状况(服务器崩溃、网络中断等),不能出现A账户少了100,但商店没到帐,或者A账户没变,但商铺收到了100(数据不一致)。
- 隔离性:如果A在转账100给商店(T1),同时B又在转账200给A(T2),不管T1和T2谁先执行完毕,最终结果必须是A账户增加100,B账户减少200 。
- 一致性:确保钱不会在系统内凭空产生或消失。
- 持久性:确保如果事务1刚刚提交,数据库就发生崩溃,执行的结果依然会保持在数据库中。
原子性
如果需要保证原子性,需要确保一组数据库操作在都成功时才会被提交保存,只要中途有异常,则所有操作都不生效。
begin # 事务开始
update table
update table
update table
# 任何一个一场都会导致所有 update 撤销
commit # 事务结束,提交
隔离性
关于隔离性的四个强度,有以下四个级别:
- READ UNCOMMITED :未提交读。未提交的修改对于其他事务是可见的
- READ COMMITED :提交读。一个事务从开始到提交之前,对于其他事务都是不可读的。
- REPEATABLE READ:可重复读。Mysql 默认事务隔离级别。同一事物中多次读取同样记录的结果一致。
- SERIALIZABLE :可串行读。最严格的级别,读写都会上锁。相当于串行执行事务
隔离强度越高,对数据库服务器的 CPU 内存占用越高(管理锁),并行能力也会减弱。应该根据实际情况进行使用。
持久性
如果事务的数据还没有真正被写入磁盘时,数据库服务崩溃了,事务会保证数据不会丢,当管理员重启了数据库后,它会保证:
- 成功提交的事务,数据会保存到磁盘
- 未提交的事务,相应的数据会回滚
数据库(以包含Innodb引擎举例)是通过事务日志的方式来达到这两个功能的。
事务的每一个操作(增/删/改)产生一条日志,内容组成大概如下:
- LSN:一个按时间顺序分配的唯一日志序列号,靠后的操作的LSN比靠前的大。
- TransID:产生操作的事务ID。
- PageID:被修改的数据在磁盘上的位置,数据以页为单位存储。
- PrevLSN:同一个事务产生的上一条日志记录的指针。
- UNDO:取消本次操作的方法,按照此方法回滚。
- REDO:重复本次操作的方法,如有必要,重复此方法保证操作成功。
数据库会通过解析事务日志,将修改真正落到磁盘上,随后清理事务日志(正常情况下)
这是数据库在IO与安全性两者权衡的折中方法。如果每次都写入,会占据大量随机的磁盘IO,性能很差。如果每次不写入,服务器崩溃会造成数据丢失。
折中的方法:
- 将数据的变更以事务日志的方式,按照时间先后追加到日志缓冲区,由特定算法写入事务日志,这是顺序IO,性能较好
- 通过数据管理器解析事务日志,由特定的算法择机(一般等到较为空