什么是事务
数据库事务(Database Transaction),就是并发控制的基本单位,是指作为单个逻辑工作单元执行的一系列操作,其中的操作要么完全执行,要么完全地不执行。
以整个转账过程为例,我们希望转账和收账操作要么同时成功,要么同时失败。这时,事务就可以帮助我们。
数据库事务具有ACID属性
ACID
原子性(Atomicity)
原子性是指事务的所有操作要么全部完成,要么全部失败。事务在执行时发生错误的话,会回滚到事务开始前的状态。
回滚
所有事务进行的修改都会先记录到这个回滚日志中,然后在对数据库中的对应行进行写入。
一致性(Consistency)
一致性是指事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。
保证数据库一致性是指当事务完成时,必须使所有数据都具有一致的状态(如主键约束、外键约束以及一些约束检查等等)。在关系型数据库中,所有的规则必须应用到事务的修改上,以便维护所有数据的完整性。
我的理解是,已转账为例,转账前后,转账和收账人账户余额里存储的数据都是数字,具体的数字会变化,但是数据类型始终都是数字,并且转账前后两个账户的总余额应当不变。
隔离性(Isolation)
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
事务隔离性的四个级别
读未提交 RAED UNCOMMITED
使用查询语句不会加锁,可能会读到未提交的行,也就是脏读(Dirty Read)。
比如说,现有空表。事务a向表中插入值1
但未提交,事务b此时读取表,却读到了1
,这就是脏读。
读提交 READ COMMITED
只对记录加记录锁,而不会在记录之间加间隙锁,所以允许新的记录插入到被锁定记录的附近,所以再多次使用查询语句时,可能得到不同的结果,即不可重复读(Non-Repeatable Read)。
比如说在事务a中查询了一张表,返回了一个结果;然后在事务b中修改了表中被查询的数据并提交了,此时事务a再查询这张表,查询的结果则是事务b修改后的数据,这样事务a两次相同查询操作的返回结果却不同,也就是不可重复读。
可重复读 REPEATABLE READ
多次读取同一范围的数据会返回第一次查询的快照,不会返回不同的数据行,但是可能发生幻读(Phantom Read)。
比如说,先在事务a中查询空表,返回空;然后在事务a中向表中的主键列id
添加值1
并提交,再在事务a中查询该表,仍然返回空,也就是说可以重复读了。
但是,此时我们再在事务a中向表中id
列插入1
,则会出错,因为表中已经有id=1
,然而我们读到地结果却是空,这就是幻读。
串行化 SERIALIZABLE
InnoDB 隐式地将全部的查询语句加上共享锁,解决了幻读的问题。
持久性(Durability)
再来了解ACDI特性的最后一个成员,持久性。持久性就是说事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
事务的使用
事务控制语句:
- 开启一个事务
BEGIN
或START TRANSACTION
; - 提交事务
COMMIT
,也可以使用 COMMIT WORK,二者是等价的; - 回滚事务
ROLLBACK
也可以使用 ROLLBACK WORK,二者是等价的; -
SAVEPOINT identifier
,SAVEPOINT
允许在事务中创建一个保存点,一个事务中可以有多个SAVEPOINT
; -
RELEASE SAVEPOINT identifier
删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常; -
ROLLBACK TO identifier
把事务回滚到标记点; -
SET TRANSACTION
用来设置事务的隔离级别。
MYSQL 事务处理主要有两种方法:
1、用 BEGIN
, ROLLBACK
, COMMIT
来实现
-
BEGIN
开始一个事务 -
ROLLBACK
事务回滚 -
COMMIT
事务确认
2、直接用 SET 来改变 MySQL 的自动提交模式: -
SET AUTOCOMMIT=0
禁止自动提交 -
SET AUTOCOMMIT=1
开启自动提交
例子
BEGIN;
INSERT INTO student
VALUES (13, '霉霉', 39, '女');
ROLLBACK;
SELECT * FROM student;
INSERT INTO student
VALUES (13, '霉霉', 39, '女');
INSERT INTO score
VALUE (5, 13, 1001, 99);
COMMIT;
因为ROLLBACK
语句,上面代码中的查询student
语句返回的结果没有id=13
的记录:
在ROLLBACK
语句后,又进行了插入值的操作,再查看,发现均已成功添加:
MySQL默认的隔离等级为REPEATABLE-READ
,不能脏读,可重复读,有可能会幻读。
现在开启两个事务举例。
在上一个例子中已经向student
表中添加了id=13
的记录,id
为主键。
现在事务a中向student
表再插入id=13
的记录话,可想而知,会报错:
-- 事务a
BEGIN;
INSERT INTO score
VALUE (6, 13, 1001, 99);
INSERT INTO student
VALUES (13, '霉霉', 39, '女');
COMMIT;
再在事务a中查询
score
表:
-- 事务a
SELECT * FROM test.score;
再在事务b中查询
score
表:
-- 事务b
SELECT * FROM test.score;
可以发现,在事务b中查询
score
表的结果,没有id=6
的记录,因为当前隔离等级不可脏读,也就是说事务a中的INSERT INTO score
操作没有提交。这是因为后面student
表的插值操作失败了,事务的原子性决定`score·表的差值操作也不会被提交。
在事务a中将student
中id
为1的学生的名字改为Mere:
-- 事务a
UPDATE student
SET name = 'Mere'
WHERE id = 13;
再在事务b中查询
student
表:
可以看到,事务b中该条记录没有被修改,这就是可重复读。