Database_transaction
目录
为什么需要事务
数据库系统在很多方面都很容易出错,譬如:
- 软硬件故障
- 应用程序崩溃
- 意外的网络中断
为了实现可靠性,系统需要处理这些故障,而事务(transaction)一直是简化这些问题的首选机制。
事务是应用程序将多个读写操作组合成一个逻辑单元的一种形式。事务中的所有读写操作被视为单个操作来执行。整个事务要么成功提交(commit),要么失败回滚(rollback)。回滚带来的好处是,应用程序的错误处理变得简单多了,因为他不用担心部分失败的情况。
ACID是什么
事务所提供的安全保证,通常由众所周知的首字母缩略词 ACID 来描述,ACID 代表 原子性(Atomicity),一致性(Consistency),隔离性(Isolation) 和 持久性(Durability)。
原子性(Atomicity)
原子性在计算机领域中基本是指不可拆分的最小操作单元,比如下面的伪代码就不是原子操作的
int a = 1;
a++;
虽然看起来a++只有一行,但是他基本上可以分为三个步骤
步骤1(load)
将a这个变量从内存加载到寄存器中
步骤2(operate)
将寄存器中保存的a的值加1
步骤3(store)
将寄存器中的值存储到内存中
因此在大部分场景下,如果我们两个线程一个对a自增n次,另一个对a自减n次,那么最后a的值可能并不是最初的值,这是因为自增和自减少并不是原子操作,彼此之间会互相干涉。
譬如下面的go代码, 我们运行100多万次,你会发现a的值最后并不是初始值0
package domain_test
import (
"sync"
"testing"
)
var (
a int
wg sync.WaitGroup
)
func add() {
a++
}
func sub() {
a--
}
func TestAtomic(t *testing.T) {
for i := 0; i < 1<<20; i++ {
wg.Add(2)
go func() {
add()
wg.Done()
}()
go func() {
sub()
wg.Done()
}()
}
wg.Wait()
}
扯远了,现在我们说回数据库事务的原子性。数据库原子性描述了当客户端进行了多次写入,但在一些写操作处理发生了故障的情况,且这些写操作属于同一个事务中,如果终止该事务,数据库必须丢弃或者撤销该事务过程中的所有写入操作。 通俗来说就是一个事务完成后,提交之后所有的写入不丢失,回滚之后丢弃所有的写入。
一致性(Consistency)
一致性的定义是,对数据的一组特定约束必须始终成立。而这一点的保证主要是由数据库的客户来保证。举两个例子吧;
- 一个会计系统中,所有账户整体上必须借贷相抵。
- 数据库有两张表,一张学生表和班级表。班级表有两个字段班级ID和班级名,学生表两个字段学生ID和学生所属班级名。如果我们修改了班级表中某个班级的名字,但是相应的学生表的所属班级名没有变更,就属于违反了事务的一致性。