目录

Database_transaction

数据库系统在很多方面都很容易出错,譬如:

  1. 软硬件故障
  2. 应用程序崩溃
  3. 意外的网络中断

为了实现可靠性,系统需要处理这些故障,而事务(transaction)一直是简化这些问题的首选机制。

事务是应用程序将多个读写操作组合成一个逻辑单元的一种形式。事务中的所有读写操作被视为单个操作来执行。整个事务要么成功提交(commit),要么失败回滚(rollback)。回滚带来的好处是,应用程序的错误处理变得简单多了,因为他不用担心部分失败的情况。

事务所提供的安全保证,通常由众所周知的首字母缩略词 ACID 来描述,ACID 代表 原子性(Atomicity),一致性(Consistency),隔离性(Isolation) 和 持久性(Durability)。

原子性在计算机领域中基本是指不可拆分的最小操作单元,比如下面的伪代码就不是原子操作的

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()
}

扯远了,现在我们说回数据库事务的原子性。数据库原子性描述了当客户端进行了多次写入,但在一些写操作处理发生了故障的情况,且这些写操作属于同一个事务中,如果终止该事务,数据库必须丢弃或者撤销该事务过程中的所有写入操作。 通俗来说就是一个事务完成后,提交之后所有的写入不丢失,回滚之后丢弃所有的写入。

一致性的定义是,对数据的一组特定约束必须始终成立。而这一点的保证主要是由数据库的客户来保证。举两个例子吧;

  1. 一个会计系统中,所有账户整体上必须借贷相抵。
  2. 数据库有两张表,一张学生表和班级表。班级表有两个字段班级ID和班级名,学生表两个字段学生ID和学生所属班级名。如果我们修改了班级表中某个班级的名字,但是相应的学生表的所属班级名没有变更,就属于违反了事务的一致性。