前言
事务是我们保证数据正确性的重要手段,只要和数据库打交道,就得理解它的 ACID 特性,这也是一个专业程序员应该掌握的基本技能。
事务是什么?
在数据库的世界里,我们最常打交道的是 SQL 操作语句。然而,SQL 语句并不是数据库的最小处理单位,事务才是。
事务包含了一条或多条 SQL 语句。这些 SQL 语句会被视为一个任务集合,而事务就保证了这个任务集合里的 SQL 要么全部执行完,要么都不执行,不会让数据处于一个中间操作状态。
数据库是面向多线程多用户设计的,同一时刻会被多个程序读取或修改。在并发操作时,需要考虑对同一资源的访问控制。而事务在这方面有完善的处理机制,后面将会提及到事务的隔离性。
另外,事务能保证数据的完整性,这包括逻辑上的完整以及物理上的完整性。
逻辑上的完整性是指不会存在中间状态的数据;只会成功或失败回滚。
物理上的完整性是指事务只要执行成功,那么即使重启也不会丢失执行结果。(当然,前提是数据文件没有损坏......)
事务的使用
事务的整体流程主要涉及到几个状态点:
(1)开始事务:BEGIN TRANSACTION;
(2)提交事务:COMMIT;
(3)回滚事务:ROLLBACK;
一般的,当我们在执行一条 SQL 语句时就已经默认帮我们开启了事务。若执行成功,则会提交事务。若执行失败,则会回滚事务。
其中,提交事务后,相对应的数据也会被持久化到硬盘上,即时重启也不会丢失。
事务的 ACID
前面提到过,事务能保证数据的完整性,它主要是通过 ACID 特性来保证的:
1、原子性(Atomicity)
事务是一个不可分割的单位,因此在一个事务里的所有操作要么全部生效,要么全部不生效。
2、一致性(Consistency)
也可以理解为是预期状态的正确性,即从一个正确的状态到另一个正确的状态,这里的状态往往是由业务来定义的。
比如转账中的一个扣钱一个加钱,是我们规定的一个数据流转,那么执行前的账户余额和转账后的账户余额就得满足加减特性,这就是所谓的业务正确。
3、隔离性(Isolation)
数据库是允许多个事务并发执行的,每个事务在执行时理想状态是互不影响。然而要达到这个效果就得串行执行事务,这样并发能力也会大大降低。
因此,为了兼顾执行效率,将互相影响的程度分为了 4 个隔离级别:
1)未提交读:
举个例子,当事务 A 对表 1 进行更新操作后,有事务 B 读取了更新后的数据,后面又由于某种原因,事务 A 进行了回滚。
这样对于事务 B 来讲就依赖了一个无效的回滚数据,从而后面所做出的决策,也不一定正确了,这就是所谓的脏读,也就是未提交读隔离级别,此级别数据一致性最差,但并发性最好。
2)已提交读:
如果想防止脏读,就需要等待其他事务提交后再进行读取操作。防止了无效的回滚情况,这就是已提交读隔离级别。
3)可重复读:
已提交读的隔离级别考虑到了数据回滚的无效性,却无法阻止事务的多次提交。
比如事务 A 不断的对表进行修改提交,那么事务 B 就会在不同的时间点读取到不同的数据。
为了让事务 B 在执行期间读取的数据都是一致的,就有了可重复读的隔离级别,即事务 B 在执行期间,其他事务不得进行修改操作。
4)可串行化:
上面的可重复读隔离级别保证了事务执行期间读取的一致性。然而这里并不包括插入、删除操作。
即会出现读多读少数据的情况,这种现象叫做幻读。
为了解决幻读,只得进行串行化执行事务,才能互不影响。而此时的事务并发性是最低的。
4、持久化(Durability)
最后一个事务特性就是持久化性。通过日志等手段,只要我们的事务提交成功了,那么就意味着这次的数据操作是成功的。即使下次重启了程序,也不会丢失此处的操作结果!
总结
事务的 ACID 特性保证了数据的完整性。其中,事务的隔离级别越高,数据一致性越好,但并发能力就越差。这是需要我们在实际开发中取舍的。像日志记录的读取,使用脏读就对总体影响不大。