数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务,必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性。事务是数据库运行中的一个逻辑工作单位,由DBMS中的事务管理子系统负责事务的处理。


1 . 事务的四个基本特性

a. 原子性(Atomicity): 确保事务内的所有操作都完全成功; 否则事务终止于异常点,并且回滚事务内中止点之前的所有操作, 恢复为事务之前的状态。
b. 一致性(Consistency): 保证数据库状态在一个事务执行之前和执行之后都必须处于一致性状态。(比如数据约束一致,(账户间转账,总金额在转账前后相等!))
c. 隔离性(Isolation): 并发的事务之间需要是相互隔离的, 一个事务内部的操作及正在操作的数据中间态自己私有,不被其它企图进行修改的事务看到。
d. 持久性(Durability): 当系统或介质发生故障时,确保已经提交的事务的数据更新不被丢失。即一旦一个事务提交,DBMS保证它对数据库中数据的改变应该是永久性的,即对已提交事务的更新能恢复,持久性通过数据库备份和恢复来保证。

2 . 几种数据库常见由隔离性保证不完全而导致的读写问题

a. 更新丢失: 两个事务都同时更新一行数据,一个事务对数据的更新把另一个事务对数据的更新覆盖了。这是因为系统没有执行任何的锁操作,没有保证上面的隔离性导致的此问题。
b. 脏读: 一个事务读取到了另一个事务未提交的数据操作结果。这是相当危险的,因为很可能所有的操作都被回滚。隔离性保证的程度不够所致。
c. 虚读: 事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读该数据时得到与前一次不同的值。
d. 幻读: 事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据(这里并不要求两次查询的SQL语句相同)。这是因为在两次查询过程中有另外一个事务插入数据造成的。

3 . 事务的隔离级别(分别是对以上四个特性保证到不同程度而划分出的级别)
a. 读未提交(Read Uncommitted): 允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
b. 读提交(Read Committed): 允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
c. 可重复读取(Repeatable Read): 禁止不可重复读取和脏读取,但是有时可能出现幻读数据。这可以通过“共享读锁”和“排他写锁”实现。读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
d. 序列化(Serializable): 提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

常见数据库事务隔离级别: Postgres默认是 ‘Read Committed’; Mysql默认是”Repeatable Read”;

PG: select * from current_setting('transaction_isolation');
MySQL: show variables like '%tx_isolation%';

4 . 数据库隔离级别实现之锁介绍

===概念上
a. 锁: 对并发访问的共享数据使用锁, 以达到对数据控制按照正常逻辑进行的增删改查的结果!
b. 乐观锁: 乐观锁是假设事务冲突很少发生, 大多是基于数据版本(Version)记录机制实现, 不需要数据库花费开销为数据加锁, 但应用程序或者操作者需要判断更新前的数据状态来做出不同处理.
c. 悲观锁: 悲观锁则假设事务冲突经常发生,具有强烈的独占和排他特性。通过数据库的锁机制进行加锁处理
,在自己事务中将所操作数据处于锁定状态, 避免并发修改引起的数据异常!

===数据库实现中
d. 读锁:也叫共享锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S 锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
e. 写锁:又称排他锁、X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。

5 . 事务操作示例(每个例子中会开多个会话表示不同的事务)
先创建个示例操作表

-- Table: test."user"

-- DROP TABLE test."user";

CREATE TABLE test."user"
(
    id bigint NOT NULL DEFAULT nextval('user_id_seq'::regclass),
    username character varying(24) COLLATE pg_catalog."default" NOT NULL,
    password character varying(64) COLLATE pg_catalog."default" NOT NULL,
    ctime timestamp without time zone,
    utime timestamp without time zone,
    noticenum integer NOT NULL DEFAULT 0,
    CONSTRAINT id PRIMARY KEY (id),
    CONSTRAINT unq_name UNIQUE (username)
)
WITH (
    OIDS = FALSE
)
TABLESPACE pg_default;

ALTER TABLE test."user"
    OWNER to hello_user;

a> Postgres把”Read Uncommitted”与”Read Committed”合在一起了,即使设置了读未提交级别但是和读已提交是一样的效果;

b> 测试读已提交”Read Committed”

A: begin; // 等同于 BEGIN TRANSACTION;
A: SET TRANSACTION  ISOLATION LEVEL READ COMMITTED;
A: insert into "user" values(1, 'hisoka', 'welcome', null, null, 5);
A: select * from "user"; //有一条记录
B: select * from "user"; //无记录
B: begin;
B: SET TRANSACTION  ISOLATION LEVEL READ COMMITTED;
B: select * from "user"; //无记录
A: COMMIT; // 等同于 END TRANSACTION;
B: select * from "user"; //有一条记录
B: END;
--遗留不可重复读问题
A: begin; 
A: select noticenum from "user" where id = 1;// 值为5
B: update "user" set noticenum = 10 where id = 1;
A: select noticenum from "user" where id = 1;// 值为10
A: END;
--同一事务内,读取同一条数据记录竟然读到的状态不一致,这有问题啊

--遗留幻读问题
A: begin; 
A: SET TRANSACTION  ISOLATION LEVEL READ COMMITTED;
A: select count(*) from "user";// 有一条记录
B: insert into "user" values(2, 'hinsteny', 'welcome', null, null, 5);
A: select count(*) from "user";// 怎么变两条记录了啊
A: END;
--同一事务内,读取一个表内的统计信息,查询条件相同但是两次结果不同,这有问题啊

c> 测试可重复读”Repeatable Read”

A: begin; // 等同于 BEGIN TRANSACTION;
A: SET TRANSACTION  ISOLATION LEVEL Repeatable Read;
A: select noticenum from "user" where id = 1;// 值为10
A: select count(*) from "user"; //有两条记录
B: update "user" set noticenum = 15 where id = 1;
B: insert into "user" values(3, 'hh', 'welcome', null, null, 5);//无记录
A: select noticenum from "user" where id = 1;// 依旧值为10
--成功避免不可重复读问题的发生
A: select count(*) from "user"; //依旧只有两条记录
--成功避免了幻读问题的发生
A: END;

PS: 在 PostgreSQL 里,你可以请求设置四种可能的事务隔离级别中的任意一种。但是在内部,实际上只有两种独立的隔离级别,分别对应读已提交和可串行化。如果你选择了读未提交的级别,实际上你用的是读已提交,在你选择可重复读级别的时候,实际上你用的是可串行化,所以实际的隔离级别可能比你选择的更严格。这是 SQL 标准允许的:四种隔离级别只定义了哪种现像不能发生,但是没有定义那种现像一定发生。PostgreSQL 只提供两种隔离级别的原因是,这是把标准的隔离级别与多版本并发控制架构映射相关的唯一合理方法。因为PostgreSQL内部利用多版本并发控制(MVCC)来维护数据的一致性,使用多版本并发控制比锁定模型的主要优点是在 MVCC 里,对检索(读)数据的锁请求与写数据的锁请求不冲突,所以读不会阻塞写,而写也从不阻塞读。