能够让你清楚地了解事务SQL全面教程五:事务(1)管理

本文已参与好文召集令活动,点击查看:后端、大前端双赛道投稿,2万元奖池等你挑战!

事务用来管理数据更新,确保数据的变更写入到数据库还是回退到数据更新之前的状态。

简要

  • 事务是需要在同一个处理单元中执行的一系列更新处理的集合。通过使用事务,可以对数据库中的数据更新处理的提交和取消进行管理。
  • 事务处理的终止指令包括COMMIT(提交处理)和ROLLBACK(取消处理)两种。

使用事务处理(transaction processing),确保成批的SQL操作要么完全执行,要么完全不执行,来维护数据库的完整性。

什么是事务——transaction?

在RDBMS中,事务是对表中数据进行更新的单位。事务是需要在同一个处理单元中执行的一系列更新处理的集合

事务处理可以保证一组操作不会中途停止,它们要么完全执行,要么完全不执行(需要明确指示)。执行成功提交给(写到)数据库中,发生错误则进行回退(撤销),数据库恢复到事务开始之前的状态。

事务是指作为单个逻辑工作单元执行的一系列操作。这些操作要么全做,要么全不做。是一个不可分割的工作单元。

事务是合并到一个逻辑单元中的一系列命令,事务是作为一个整体执行的。主要包含如下几个概念:

  • 事务(transaction)指一组SQL语句;
  • 回退(rollback)指撤销指定SQL语句的过程;也叫回滚。
  • 提交(commit)指将未存储的SQL语句结果写入数据库表;即将提交当前事务,事务提交后无法撤销。
  • 保留点(savepoint)指事务处理中设置的临时占位符(placeholder),可以实现回退到某个保留点(而不是回退整个事务)。或翻译为保存点。

有的RDBMS在保留点的基础上,支持多级事务multi-level transactions

也就是,事务是一组可执行的SQL语句,且可以提交或回滚SQL语句的执行结果,提交或回滚后表示一个事务的结束。

只读事务

如果一个事务仅仅是检索数据而不是更新数据库,这种类型的事务被称为只读事务(read-only transaction)

创建事务

在DBMS中,一个完整事务过程如下:

事务开始语句:
  DML语句1;
  DML语句2;
  DML语句3;
  ...
事务结束语句(COMMITROLLBACK);
复制代码

也就是需要使用事务开始语句和事务结束语句,将一系列DML语句(INSERT/UPDATE/DELETE语句)括起来,从而实现一个事务处理。

事务开始语句

实际上,在标准SQL中并没有定义事务的开始语句,而是由各个DBMS自己来定义的。

SQL Server、PostgreSQL开始一个事务

BEGIN TRANSACTION;
复制代码

目前所知,仅SQL Server中可以简写为:BEGIN TRAN

PostgreSQL也支持BEGIN;START TRANSACTION;开启一个事务。

MySQL、MariaDB开启一个事务

START TRANSACTION;
复制代码

MySQL中也支持使用BEGIN;BEGIN WORK开启一个事务,它是START TRANSACTION;别名,推荐使用START TRANSACTION;

防止begin开启事务,和begin...end结构块混淆,通常不推荐使用。尤其是存储过程中,会将begin解析为begin...end,必须使用START TRANSACTION;

Oracle开启一个事务

SET TRANSACTION;
复制代码

DB2 无事务开始语句。旧版本中Oracle似乎也没有事务开始语句,至少12c可以显式使用事务开始语句

SQL Server、PostgreSQL、MySQL/MariaDB、Oracle等的事务示例

例如,实现如下事务,将运动T恤的销售单价降低1000,并将T恤衫的销售单价上浮1000,各个RDBMS中的事务使用如下:

--SQL Server, PostgreSQL
BEGIN TRANSACTION;  -- SQL Server可简写为BEGIN TRAN; -- PostgreSQL中可以直接写 BEGIN;

    -- 运动T恤的销售单价下调1000
    UPDATE Product
       SET sale_price = sale_price - 1000
     WHERE product_name = '运动T恤';

    -- T恤的销售单价上浮1000
    UPDATE Product
       SET sale_price = sale_price + 1000
     WHERE product_name = 'T恤';

COMMIT;

--MySQL
START TRANSACTION;

    -- 运动T恤的销售单价下调1000
    UPDATE Product
       SET sale_price = sale_price - 1000
     WHERE product_name = '运动T恤';

    -- T恤的销售单价上浮1000
    UPDATE Product
       SET sale_price = sale_price + 1000
     WHERE product_name = 'T恤';

COMMIT;

--Oracle
SET TRANSACTION; -- 此句可省略
    -- 运动T恤的销售单价下调1000
    UPDATE Product
       SET sale_price = sale_price - 1000
     WHERE product_name = '运动T恤';

    -- T恤的销售单价上浮1000
    UPDATE Product
       SET sale_price = sale_price + 1000
     WHERE product_name = 'T恤';

COMMIT;

-- DB2
    -- 运动T恤的销售单价下调1000
    UPDATE Product
       SET sale_price = sale_price - 1000
     WHERE product_name = '运动T恤';

    -- T恤的销售单价上浮1000
    UPDATE Product
       SET sale_price = sale_price + 1000
     WHERE product_name = 'T恤';

COMMIT;
复制代码

COMMIT——提交处理

COMMIT是提交事务所包含的全部更新处理的结束指令。一旦提交,则表示事务处理结束,也就无法恢复到事务开始前的状态。

通常在提交之前要确认是否真的需要提交更新。一定要仔细确认再提交。

ROLLBACK——取消处理,回滚事务

ROLLBACK是取消事务所包含的全部更新处理的结束指令。一旦回滚,数据库就会恢复到事务开始之前的状态。

--SQL Server, PostgreSQL
BEGIN TRANSACTION;  

    -- 运动T恤的销售单价下调1000
    UPDATE Product
       SET sale_price = sale_price - 1000
     WHERE product_name = '运动T恤';

    -- T恤的销售单价上浮1000
    UPDATE Product
       SET sale_price = sale_price + 1000
     WHERE product_name = 'T恤';

ROLLBACK; -- 撤销修改
复制代码

进行提交/回滚是需要有对应的事务开始语句(BEGIN TRANSACTION),若没有,在SQL Server下报错,PostgreSQL下产生警告,MariaDB/MySQL可正常执行。

这些是否产生提示或错误,应该都可以通过模式或错误提示进行设置。通常最好事务的开始和结束(commit/rollback)一起出现。

-- SQL Server下
Msg 3903, Level 16, State 1, Line 19
The ROLLBACK TRANSACTION request has no corresponding BEGIN TRANSACTION.

Msg 3902, Level 16, State 1, Line 22
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.

-- PostgreSQL
警告:  没有事物在运行中
COMMIT

警告:  没有事物在运行中
ROLLBACK
复制代码

自动提交事务、隐式事务和显式事务

autocommit transaction modeimplicit transaction modeexplicit transaction mode

需要清楚DBMS中事务的开始和结束:是否知道某个DBMS中事务是什么时候开始的,可以很简单的测试一个人的数据库知识储备的高低。

自动提交事务、隐式事务和显式事务

事务并没有标准的开始指令。因此不同DBMS的事务开始会有所不同。

  • 通常,事务的开始无需指令。主要分为两种情况:

    • 每条SQL语句就是一个事务(自动提交模式——Auto Commit,一条语句就是一个事务)
    • 直到用户执行COMMIT或者ROLLBACK为止算作一个事务 (隐式事务模式——implicit transaction,通常从第一条语句开始,或COMMIT/ROLLBACK事务结束语句之后,隐含地开启一个事务,直到执行COMMIT/ROLLBACK结束事务。Oracle中在执行DDL语句之前也会提交一个事务,而不仅仅遇到COMMIT后提交)。

    隐式事务中是隐式的开启事务,然后进行显式提交(explicit commit)结束事务。

    但是由于Oracle中对事务的处理,在执行DDL语句之前会隐式提交(implicit commit)一个事务。这就可能会导致隐式事务和隐式提交的混淆。

  • 使用明确的事务开始和结束指令的事务,则为显式事务(显式事务模式——explicit transaction,显式的使用事务开始和事务结束语句,通常实际会比较复杂,尤其是Oracle中,在显式事务中遇到隐式提交的DDL语句时)。

只有显式事务模式才能完全由我们控制事务的开始和结束。

显式事务是使用最广泛的模式,SQL编写中最好多加应用。

对于事务的处理和实现机制,不同RDBMS都有所不同,具体要看相应的文档。

SQL Server、PostgreSQL和MySQL/MariaDB等使用的是自动提交模式。每一条DML语句都一个事务(成功则提交)。这种情况就需要注意DELETE语句,执行删除后将无法回滚到删除前的状态。

自动提交模式下的(每条)语句会被看不见的BEGIN TRANSACTIONCOMMIT TRANSACTION包围,执行完就提交。即自动提交模式下,执行每条SQL语句之前是事务的开始,每条语句执行完是事务的结束。

注意事务的开始结束与执行时的SQL编辑器处理逻辑、批SQL语句执行的混淆。

如果不是自动提交,即使使用DELETE语句删除了数据表,也可以通过ROLLBACK命令取消该事务的处理,恢复表中的数据。

Oracle数据库使用的是隐式事务模式,在执行第一条语句之前开始一个事务,当遇到COMMIT/ROLLBACK之后或DDL语句之前结束一个事务。

事务模式都可以通过配置进行修改。

注:通常,SELECT语句查询表也会开启和处于一个事务中

如果不是从表中select,在隐式事务中则不会开启一个事务,如SELECT GETDATE();/SELECT CURRENT_DATE;SELECT 1, 'ABC';

SELECT查询也作为事务处理的原因在于,需要通过事务的隔离级别,做到数据读取的正确;以及查询大多数需要和更改数据一起作为整体进行事务操作。

为什么使用显式事务或隐式事务?

在自动提交模式下,可以说能够做到所有在隐式和显式事务中做的事情,那么为什么还要使用隐式和显式事务模式呢?

最重要的一点是:自动提交事务仅仅用于单个查询。如果一个事务需要涉及多个查询,则必须使用隐式或显式事务才能实现。保证多条数据操作语句执行的原子性和完整性。

还有一条就是上面提到的。使用显式事务,可以完全控制事务的开始和结束;使用隐式事务,也可以由自己控制事务的结束。

用户事务

user transaction或UserTransaction,即用户事务。在SQL标准或各DBMS官方文档中没有对用户事务的专门介绍。从查找的资料看,似乎来自java有关数据库编程JDBC中的说法。

用户事务允许(应用程序)显式地管理事务边界,即用户事务可以控制事务的开始、结束,和获取事务状态等。

可以认为用户事务等同于显式事务

附:PostgreSQL中开启隐式事务

在命令行中关闭自动提交

\set AUTOCOMMIT off

-- \set AUTOCOMMIT on
复制代码

在pgAdmin 4中,在有运行小三角的右侧下拉中,可以关闭"自动提交":

PostgreSQL中自动提交的禁用似乎只是在客户端管理工具中实现。即PostgreSQL数据库服务器端不提供关闭"自动提交"模式的功能(推出过,后面移除了这个功能)。

客户端通过在语句最开始发送"BEGIN TRANSACTION"实现关闭自动提交的效果。

附:SQL Server中开启隐式事务

以SQL Server开启隐式事务为例做个介绍

-- 开启隐式事务
Set Implicit_Transactions On;
-- 关闭隐式事务
Set Implicit_Transactions Off;
复制代码

如下,开启隐式事务模式,执行DML、DDL操作,最后执行回滚。可以看到所有的修改都没有被提交:

Set Implicit_Transactions On;

select * from Productcopy;
Update Productcopy set sale_price=2120 where product_id='0011';
Alter table Productcopy add test_col varchar(12) default null;
create table DDLTestTable (id int primary key identity);
rollback;
select * from Productcopy;

Set Implicit_Transactions Off;
复制代码

如果将上面的回滚改为commit提交,则可以看到所有的更改。

可以看到SQL Server中的隐式事务模式,只有在显式的遇到commit/rollback才会结束。

在SSMS中设置打开"隐式事务模式":

顶部菜单栏:Tools-> Options -> Query Execution -> SQL Server -> ANSI。如下,选中SET IMPLICIT_TRANSACTIONS

在SQL Server中,可以通过全局变量@@TRANCOUNT获取当前已经开启的事务数

附:MySQL/MariaDB中开始隐式事务

SET AUTOCOMMIT命令开启或关闭自动提交,实现关闭或开启隐式事务。默认autocommit为ON。

-- 关闭自动提交,开启隐式事务
SET AUTOCOMMIT = OFF;
-- SET autocommit = {0 | 1 | OFF | ON}
复制代码

MySQL/MariaDB中查看当前的自动提交模式

MariaDB [test]> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | ON    |
+---------------+-------+
1 row in set (0.001 sec)
复制代码