一、事务概述
所谓事务就是用户定义的一个数据库操作序列,使用事务可以确保同时发生的行为与数据库有效性不发生冲突,并且维护数据的完整性,确保数据的有效性。
1.1 为什么要引入事务
事务处理机制在程序开发过程中有着非常重要的作用,它可以是整个系统更加安全。例如,在银行处理转账业务时,如果A账户的金额刚被转走,而B账户还没来得及接收就停电。或者A账户的金额在转出过程因错误而未能转出,而B账户却已经完成转入工作。这会给银行和个人带来很大的经济损失。采用事务处理机制,一旦转账过程发生意外,则整个转账业务将会全部撤销,不做任何处理,确保数据的一致性和有效性。
1.2 MySQL事务处理机制
MySQL系统具有事务处理功能,能够保证数据库操作的一致性和完整性,使用事务可以确保同时发生的行为与数据的有效性不发生冲突。
在MySQL中,不是所有的存储引擎都支持事务,如InnoDB和BDB支持,但MyISAM和MEMORY则不支持。
二、事务的 ACID 特性
事务是有限的数据库操作序列组成,但并不是任意的数据库操作序列都能成为事务,为了保护数据的完整性,一般要求事务具有以下4个特征。
2.1 原子性(Atomic)
一个事务是一个不可分割的工作单位,事务在执行时,应该遵循“要么不做,要么全做”(Nothing or All)的原则,即不允许事务部分的做完,即使因故障而使事务未能完成,它执行的部分结果将被取消。
保证原子性是数据系统本身的职责,由 DBMS 的事务管理子系统实现。
2.2 一致性(Consistency)
事务对数据库的作用是使数据库从一个一致状态转变到另一个一致状态。
所谓数据库的一致状态是指数据库中的数据满足完整性约束。例如,在银行企业中,“从账户A转移金额R到账户B”是一个典型的事务,这个事务包含两个操作,从账务A中减去金额R何在账户B中增加金额R,如果只执行其中一个操作,则数据库处于不一致状态,财务会出现问题,也就是说,两个操作要么全做,要么全部做,否则就不能成为事务。可见事务的一致性和原子性是密切相关的。
确保单个事务的一致性是编写事务的应用程序员的职责,在系统运行中,是由 DBMS 的完整性子系统实现的。
2.3 隔离性(Isolation)
如果多个事务并发执行,应像各个事务独立执行一样,一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用对并发的其他事务是隔离的。并发控制就是为了保证事务间的隔离性。
隔离性是由 DBMS 的并发控制子系统实现的。
2.4 持久性(Durability)
最后,一个事务一旦提交,他对数据库中的数据的改变应该是持久的。如果提交一个事务后计算机瘫痪,或者数据库应故障而收到破坏,那么重启数据库后,DBMS 也应该能够恢复,该事务的结果将依然是存在的。
三、事务的定义
一个事务可以是一组 SQL 语句、一条 SQL 语句或整个程序,一个应用程序可以包含多个事务。
事务的开始和结束可以由用户显式控制。如果用户没有显式地定义事务,则由 DBMS 按照默认规则自动划分事务。在 MySQL 系统中,定义事务地语句主要有以下3条:start transaction、commit、rollback。
3.1 开始事务
start transaction 语句表示一个用户字定义事务的开始。语法格式如下。
start transaction | begin work;
begin work 语句可以用来替代 start transaction语句。
MySQL 使用的是平面事务模型,因此嵌套的事务是不被允许的。在第一个事务里使用 start transaction 命令后,当第二个事务开始时,系统会自动提交第一个事务。
3.2 结束事务
commit 语句用于结束一个用户定义的事务,保证对数据的修改已经成功写入数据库。此时事务正常结束。语法格式如下。
commit [work] [and [no] chain] [[no] release];
and chain 子句可选,会在当前事务结束时,立刻启动一个新事务,并且新事务与刚刚结束的事务有相同的隔离等级。release子句在终止了当前事务后,会让服务器断开与当前客户端的连接。包含 no 关键词可以抑制 chain或release完成。
下面这些 MySQL语句运行时都会隐式地执行一个commit命令。
(1)drop database / drop table
(2)create index / drop index
(3)alter table / rename table
(4)lock tables / unlock tables
(5)set @@autocommit=1
3.3 撤销事务
rollback 语句用于事务地撤销,它撤销事务所做的修改,并结束当前这个事务。语法格式如下。
rollback [work] [and [on] chain] [[no] release];
3.4 回滚事务
除了撤销整个事务,用户还可以使用 rollback to 语句使事务回滚到某个点,在这之前需要使用 savepoint 语句来设置保存点。savepoint语法格式如下。
savepoint identifier;
其中,indentifier 为保存点地名称。
rollback to savepoint 语句会像已命名地保存点回滚一个事务。如果在保存点被设置后,当前事务对数据做了修改,则这些修改会在回滚中被撤销。语法格式如下。
rollback [work] to savepoint identifier;
当事务回滚到某个保存点后,在该保存点之后设置地保存点将被删除。release savepoint 语句会从当前事务地一组保存点中删除已命名地保存点。不出现提交或回滚。如果保存点不存在,会出现错。其语法格式如下。
release savepoint identifier;
例如,下面几个语句说明了有关事务地处理过程。
1. start transaction; 2. update ...; 3. delete ...; 4. savepoint s1; 5. delete ...; 6. rollback work to savepoint s1; 7. insert ...; 8. commit work;
在以上语句中,第一行语句标志事务的开始;第二、三行语句对数据进行了修改,但没有提交;第四行设置了一个保存点;第五行删除了数据但是没有提交;第六行将事务回滚到保存点s1,这时第五行所做的操作被撤销了;第七行修改了数据;第八行结束了这个事务,这时,第二、第三、第七行对数据库进行的修改被持久化。
3.5 改变 MySQL 地自动提交模式
在 MySQL 中,当一个会话开始时,系统变量@@aotocommit地值是1,及自动提交功能是打开的,当用户每执行一条 SQL 语句后,该语句对数据库地修改就立即被提交成为持久性修改保存到磁盘上,一个事务也就结束了。因此,用户必须关闭自动提交,事务才能由多条 SQL 语句组成,使用语句“@@autocommit=0;”,执行此语句后,必须明确地指示每个事务地终止,事务中的 SQL 语句对数据库所做的修改才能成为持久化修改。例如,执行如下语句:
set @@autocommit=0; delete from student where sno = '20200717'; select * from student;
从执行结果发现,表中已经删去了一行。但是,这个修改并没有持久化,因为自动提交已经关闭了。用户可以通过 rollback 撤销这一修改,或者使用commit语句持久化这一修改。
若想恢复事务地自动提交,执行如下语句即可。
set @@autocommit=1;
例:编写转账业务地存储过程,要求bank表中的currentMoney字段值不能小于1,具体代码如下所示。
mysql> create database banklink; -- 创建数据库 banklink mysql> use banklink; mysql> if exsits (select * from sysobjects where name = 'bank') then -> drop table bank; mysql> create table bank -> ( customerName varchar(10), -> currentMoney decimal(13,2)); mysql> insert into bank values ('张三', 1000); mysql> insert into bank values ('李四', 1); mysql> select * from bank; mysql> delimiter // mysql> create procedure banktrans() -> begin -> declare money decimal(13,2) default 0.0; -> start transaction; -> update bank set currentMoney = currentMoney - 1000 -> where customerName = '张三'; -> update bank set currentMoney = currentMoney + 1000 -> where customerName = '李四'; -> select currentMoney into money from bank where cuntomerName = '张三'; -> if money < 1 then -> begin -> select '交易失败,回滚事务' -> rollback; -> end; -> else -> begin -> select '交易成功,提交事务,写入硬盘,永久保存'; -> commit ; -> end; -> end if; -> end // mysql> delemiter ;
结束!