来了解一下数据库事务吧

23 阅读4分钟

来了解一下数据库事务吧

Whoami:5年+金融、政府、医疗领域工作经验的DBA
Certificate:OCP、PCP
Skill:Oracle、Mysql、PostgreSQL
Platform:CSDN、墨天伦、公众号(呆呆的私房菜)

业务范围:数据库安装部署、日常维护、主备切换、故障处理、性能优化、技术培训等。
需要的伙伴或者商业合作请移步 公众号【呆呆的私房菜】获取联系方式。

阅读本文可以了解到数据库事务的相关内容,如事务概念、事务隔离级别、事务ACID特性等。

01 数据库事务概述

数据库事务是单个逻辑工作单元执行的一系列操作,是对数据库进行读或写的一个操作序列。当然,操作只有两种结果,一个是提交,一个是回滚。

# 事务提交
BEGIN;
DML OPERATION;
COMMIT;

# 事务回滚
BEGIN;
DML OPERATION;
ROLLBACK;

02 事务块管理语句

常用的数据块管理语句如下:
start transaction: 此命令开始一个新的事务块;
begin: 初始化一个事务块,它和start transaction是一样的;
commit: 提交事务;
rollback: 回滚事务;
set transaction: 设置当前事务的特性,对后面事务无影响;
savepoint: 大事务通过创建保存点把操作分成几部分来避免失败回滚整个事务。

03 事务ID解析

在PostgreSQL数据库中,每个事务都有一个唯一的事务ID,称为xid;
通过【begin-commit/rollback】执行的一组的语句会被当作一个事务;
不显式指定【begin-commit/rollback】的单条语句也是一个事务;
数据库中的事务id是不断递增的;
可以通过txid_current()函数获取当前的事务id;

04 事务隔离级别

SQL标准定义了四个事务隔离级别:

隔离级别脏读幻读不可重复读
读未提交可能可能可能
读已提交不可能可能可能
可重复读不可能可能不可能
可串行化不可能不可能不可能

Read uncommitted(读未提交)
一个session的事务即使没有commit,也对其他session可见,这其实是dirty read。

Read committed(读已提交)
某session事务只有提交了,才能对其他session可见;未提交的事务其他session不可见。

Repeatable read(可重复读)
确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。

Serializable(可序列化)
当前事务的所有语句智能看到这个事务执行的第一个查询或数据修改语句之前提交的行。

在PostgreSQL数据库中实际上只有3种独立的隔离级别,即读已提交、可重复读、可串行化。读未提交的效果实际上与读已提交效果一致。

下面我们通过实验来体验下PostgreSQL隔离级别效果。

读已提交实验:
## 1. 创建一张表并插入4条数据
postgres=*# create table test1 (id int, name varchar(10));
CREATE TABLE
postgres=*# insert into test1 values (1, 't1'), (2, 't2'),(3, 't3');
INSERT 0 3

## 2. 开启会话1
postgres=# begin;
BEGIN
postgres=*# insert into test1 values (4, 't4');
INSERT 0 1
postgres=*# select * from test1;
 id | name
----+------
  1 | t1
  2 | t2
  3 | t3
  4 | t4
(4 rows)

## 3. 开启会话2,观察到在会话1中未提交的事务是无法看到的,即没有产生脏读
begin;
select * from test1;
postgres=# begin;
BEGIN
postgres=*# select * from test1;
 id | name
----+------
  1 | t1
  2 | t2
  3 | t3
(3 rows)

## 4. 会话1提交事务
postgres=# commit;

## 5. 此时会话2中的事务可以看到会话1事务中提交的事务
ostgres=*# select * from test1;
 id | name
----+------
  1 | t1
  2 | t2
  3 | t3
  4 | t4
(4 rows)
可重复度实验:

## 调整postgresql.conf配置,设置default_transaction_isolation='repeatable read';
## reload配置生效
pg_ctl reload

## 1. 查看当前数据情况
postgres=# select * from test1;
 id | name
----+------
  1 | t1
  2 | t2
  3 | t3
  4 | t4
(4 rows)

## 2. 开启会话1
postgres=# begin;
BEGIN
postgres=*# insert into test1 values (5, 't5');
INSERT 0 1

## 3. 开启会话2,会话2中的事务无法看到会话1中的事务
postgres=# begin;
BEGIN
postgres=*# select * from test1;
 id | name
----+------
  1 | t1
  2 | t2
  3 | t3
  4 | t4
(4 rows)

## 4. 会话1提交事务
postgres=*# commit;
COMMIT

## 5. 会话2查看test1表数据,发现会话1提交的事务并没有查到,此时产生了幻读现象,说明读到的数据是真实数据的幻象
postgres=*# select * from test1;
 id | name
----+------
  1 | t1
  2 | t2
  3 | t3
  4 | t4
(4 rows)
可序列化实验:

## 调整postgresql.conf配置,设置default_transaction_isolation='serializable';
## reload配置生效
pg_ctl reload

## 1. 查看当前数据情况
postgres=# select * from test1;
postgres=# select * from test1;
 id | name
----+------
  1 | t1
  2 | t2
  3 | t3
  4 | t4
  5 | t5
(5 rows)

## 2. 开启会话1
postgres=# update test1 set name = 'tt5' where id  = 5;
UPDATE 1
postgres=# select * from test1;
 id | name
----+------
  1 | t1
  2 | t2
  3 | t3
  4 | t4
  5 | tt5
(5 rows)

## 3. 开启会话2
postgres=*# select * from test1;
 id | name
----+------
  1 | t1
  2 | t2
  3 | t3
  4 | t4
  5 | t5
(5 rows)

## 4. 会话1提交事务
postgres=*# commit;
COMMIT

## 5. 会话2更新数据
postgres=*# update test1 set name = 't555' where id = 5;
ERROR:  could not serialize access due to concurrent update
postgres=!# update test1 set name = 't444' where id = 5;
ERROR:  current transaction is aborted, commands ignored until end of transaction block