mysql原理--事务

1.事务的起源
对于大部分程序员来说,他们的任务就是把现实世界的业务场景映射到数据库世界。比如银行为了存储人们的账户信息会建立一个 account 表:

CREATE TABLE account (
 id INT NOT NULL AUTO_INCREMENT COMMENT '自增id',
 name VARCHAR(100) COMMENT '客户名称',
 balance INT COMMENT '余额',
 PRIMARY KEY (id)
) Engine=InnoDB CHARSET=utf8;

狗哥和猫爷是一对好基友,他们都到银行开一个账户,他们在现实世界中拥有的资产就会体现在数据库世界的 account 表中。比如现在狗哥有 11 元,猫爷只有 2 元,那么现实中的这个情况映射到数据库的 account 表就是这样:
在这里插入图片描述
在某个特定的时刻,狗哥猫爷这些家伙在银行所拥有的资产是一个特定的值,这些特定的值也可以被描述为账户在这个特定的时刻现实世界的一个状态。随着时间的流逝,狗哥和猫爷可能陆续进行向账户中存钱、取钱或者向别人转账等操作,这样他们账户中的余额就可能发生变动,每一个操作都相当于现实世界中账户的一次状态转换。数据库世界作为现实世界的一个映射,自然也要进行相应的变动。现实世界中一
些看似很简单的状态转换,映射到数据库世界却不是那么容易的。比方说有一次猫爷在赌场赌博输了钱,急忙打电话给狗哥要借10块钱,不然那些看场子的就会把自己剁了。现实世界中的狗哥走向了ATM机,输入了猫爷的账号以及10元的转账金额,然后按下确认,狗哥就拔卡走人了。对于数据库世界来说,相当于执行了下边这两条语句:

UPDATE account SET balance = balance - 10 WHERE id = 1;
UPDATE account SET balance = balance + 10 WHERE id = 2;

但是这里头有个问题,上述两条语句只执行了一条时忽然服务器断电了咋办?把狗哥的钱扣了,但是没给猫爷转过去,那猫爷还是逃脱不了被砍死的噩运~ 即使对于单独的一条语句,我们前边唠叨 Buffer Pool 时也说过,在对某个页面进行读写访问时,都会先把这个页面加载到 Buffer Pool 中,之后如果修改了某个页面,也不会立即把修改同步到磁盘,而只是把这个修改了的页面加到 Buffer Poolflush链表 中,在之后的某个时间点才会刷新到磁盘。如果在将修改过的页刷新到磁盘之前系统崩溃了那岂不是猫爷还是要被砍死?或者在刷新磁盘的过程中(只刷新部分数据到磁盘上)系统奔溃了猫爷也会被砍死?

怎么才能保证让可怜的猫爷不被砍死呢?其实再仔细想想,我们只是想让某些数据库操作符合现实世界中状态转换的规则而已,设计数据库的大叔们仔细盘算了盘算,现实世界中状态转换的规则有好几条,待我们慢慢道来。

1.1.原子性(Atomicity
现实世界中转账操作是一个不可分割的操作,也就是说要么压根儿就没转,要么转账成功,不能存在中间的状态,也就是转了一半的这种情况。设计数据库的大叔们把这种要么全做,要么全不做的规则称之为 原子性 。

但是在现实世界中的一个不可分割的操作却可能对应着数据库世界若干条不同的操作,数据库中的一条操作也可能被分解成若干个步骤(比如先修改缓存页,之后再刷新到磁盘等),最要命的是在任何一个可能的时间都可能发生意想不到的错误(可能是数据库本身的错误,或者是操作系统错误,甚至是直接断电之类的)而使操作执行不下去,所以猫爷可能会被砍死。为了保证在数据库世界中某些操作的原子性,设计数据库的大叔需要费一些心机来保证如果在执行操作的过程中发生了错误,把已经做了的操作恢复成没执行之前的样子,这也是我们后边章节要仔细唠叨的内容。

1.2.隔离性(Isolation
现实世界中的两次状态转换应该是互不影响的,比如说狗哥向猫爷同时进行的两次金额为5元的转账(假设可以在两个ATM机上同时操作)。那么最后狗哥的账户里肯定会少10元,猫爷的账户里肯定多了10元。但是到对应的数据库世界中,事情又变的复杂了一些。为了简化问题,我们粗略的假设狗哥向猫爷转账5元的过程是由下边几个步骤组成的:
(1). 读取狗哥账户的余额到变量A中,这一步骤简写为 read(A)
(2). 将狗哥账户的余额减去转账金额,这一步骤简写为 A = A - 5
(3). 将狗哥账户修改过的余额写到磁盘里,这一步骤简写为 write(A)
(4). 读取猫爷账户的余额到变量B,这一步骤简写为 read(B)
(5). 将猫爷账户的余额加上转账金额,这一步骤简写为 B = B + 5
(6). 将猫爷账户修改过的余额写到磁盘里,这一步骤简写为 write(B)
我们将狗哥向猫爷同时进行的两次转账操作分别称为 T1T2 ,在现实世界中 T1T2 是应该没有关系的,可以先执行完 T1 ,再执行 T2 ,或者先执行完 T2 ,再执行 T1 ,对应的数据库操作就像这样:
在这里插入图片描述
但是很不幸,真实的数据库中 T1T2 的操作可能交替执行,比如这样:
在这里插入图片描述
如果按照上图中的执行顺序来进行两次转账的话,最终狗哥的账户里还剩 6 元钱,相当于只扣了5元钱,但是猫爷的账户里却成了 12 元钱,相当于多了10元钱,这银行岂不是要亏死了?

所以对于现实世界中状态转换对应的某些数据库操作来说,不仅要保证这些操作以 原子性 的方式执行完成,而且要保证其它的状态转换不会影响到本次状态转换,这个规则被称之为 隔离性 。这时设计数据库的大叔们就需要采取一些措施来让访问相同数据(上例中的A账户和B账户)的不同状态转换(上例中的 T1T2 )对应的数据库操作的执行顺序有一定规律,这也是我们后边章节要仔细唠叨的内容。

1.3. 一致性(Consistency
我们生活的这个世界存在着形形色色的约束,比如身份证号不能重复,性别只能是男或者女,高考的分数只能在0~750之间,人民币面值最大只能是100(现在是2024年),红绿灯只有3种颜色,房价不能为负的,学生要听老师话,吧啦吧啦有点儿扯远了~ 只有符合这些约束的数据才是有效的,比如有个小孩儿跟你说他高考考了1000分,你一听就知道他胡扯呢。数据库世界只是现实世界的一个映射,现实世界中存在的约束当然也要在数据库世界中有所体现。如果数据库中的数据全部符合现实世界中的约束(all defined rules),我们说这些数据就是一致的,或者说符合 一致性 的。

如何保证数据库中数据的一致性(就是符合所有现实世界的约束)呢?这其实靠两方面的努力:
(1). 数据库本身能为我们保证一部分一致性需求(就是数据库自身可以保证一部分现实世界的约束永远有效)。
我们知道 MySQL 数据库可以为表建立主键、唯一索引、外键、声明某个列为 NOT NULL 来拒绝 NULL 值的插入。比如说当我们对某个列建立唯一索引时,如果插入某条记录时该列的值重复了,那么 MySQL 就会报错并且拒绝插入。除了这些我们已经非常熟悉的保证一致性的功能, MySQL 还支持 CHECK 语法来自定义约束,比如这样:

 CREATE TABLE account (
 id INT NOT NULL AUTO_INCREMENT COMMENT '自增id',
 name VARCHAR(100) COMMENT '客户名称',
 balance INT COMMENT '余额',
 PRIMARY KEY (id),
 CHECK (balance >= 0) 
 );

上述例子中的 CHECK 语句本意是想规定 balance 列不能存储小于0的数字,对应的现实世界的意思就是银行账户余额不能小于0。但是很遗憾,MySQL仅仅支持CHECK语法,但实际上并没有一点卵用,也就是说即使我们使用上述带有 CHECK 子句的建表语句来创建 account 表,那么在后续插入或更新记录时, MySQL 并不会去检查 CHECK 子句中的约束是否成立。

虽然 CHECK 子句对一致性检查没什么卵用,但是我们还是可以通过定义触发器的方式来自定义一些约束条件以保证数据库中数据的一致性。

(2). 更多的一致性需求需要靠写业务代码的程序员自己保证。
为建立现实世界和数据库世界的对应关系,理论上应该把现实世界中的所有约束都反应到数据库世界中,但是很不幸,在更改数据库数据时进行一致性检查是一个耗费性能的工作,比方说我们为 account 表建立了一个触发器,每当插入或者更新记录时都会校验一下 balance 列的值是不是大于0,这就会影响到插入或更新的速度。仅仅是校验一行记录符不符合一致性需求倒也不是什么大问题,有的一致性需求简直变态,比方说银行会建立一张代表账单的表,里边儿记录了每个账户的每笔交易,每一笔交易完成后,都需要保证整个系统的余额等于所有账户的收入减去所有账户的支出。如果在数据库层面实现这个一致性需求的话,每次发生交易时,都需要将所有的收入加起来减去所有的支出,再将所有的账户余额加起来,看看两个值相不相等。这不是搞笑呢么,如果账单表里有几亿条记录,光是这个校验的过程可能就要跑好几个小时,也就是说你在煎饼摊买个煎饼,使用银行卡付款之后要等好几个小时才能提示付款成功,这样的性能代价是完全承受不起的。现实生活中复杂的一致性需求比比皆是,而由于性能问题把一致性需求交给数据库去解决这是不现实的,所以这个锅就甩给了业务端程序员。比方说我们的 account 表,我们也可以不建立触发器,只要编写业务的程序员在自己的业务代码里判断一下,当某个操作会将 balance 列的值更新为小于0的值时,就不执行该操作就好了嘛!

我们前边唠叨的 原子性 和 隔离性 都会对 一致性 产生影响,比如我们现实世界中转账操作完成后,有一个 一致性 需求就是参与转账的账户的总的余额是不变的。如果数据库不遵循 原子性 要求,也就是转了一半就不转了,也就是说给狗哥扣了钱而没给猫爷转过去,那最后就是不符合一致性需求的;类似的,如果数据库不遵循 隔离性 要求,就像我们前边唠叨 隔离性 时举的例子中所说的,最终狗哥账户中扣的钱和猫爷账户中涨的钱可能就不一样了,也就是说不符合 一致性 需求了。所以说,数据库某些操作的原子性和隔离性都是保证一致性的一种手段,在操作执行完成后保证符合所有既定的约束则是一种结果

那满足 原子性 和 隔离性 的操作一定就满足一致性 么?那倒也不一定,比如说狗哥要转账20元给猫爷,虽然在满足 原子性 和 隔离性 ,但转账完成了之后狗哥的账户的余额就成负的了,这显然是不满足 一致性 的。那不满足 原子性 和 隔离性 的操作就一定不满足一致性 么?这也不一定,只要最后的结果符合所有现实世界中的约束,那么就是符合 一致性 的。

1.4.持久性(Durability
当现实世界的一个状态转换完成后,这个转换的结果将永久的保留,这个规则被设计数据库的大叔们称为 持久性 。比方说狗哥向猫爷转账,当ATM机提示转账成功了,就意味着这次账户的状态转换完成了,狗哥就可以拔卡走人了。如果当狗哥走掉之后,银行又把这次转账操作给撤销掉,恢复到没转账之前的样子,那猫爷不就惨了,又得被砍死了,所以这个 持久性 是非常重要的。

当把现实世界的状态转换映射到数据库世界时, 持久性 意味着该转换对应的数据库操作所修改的数据都应该在磁盘上保留下来,不论之后发生了什么事故,本次转换造成的影响都不应该被丢失掉(要不然猫爷还是会被砍死)。

2.事务的概念
为了方便大家记住我们上边唠叨的现实世界状态转换过程中需要遵守的4个特性,我们把 原子性( Atomicity )、 隔离性 ( Isolation )、 一致性 ( Consistency )和 持久性 ( Durability )这四个词对应的英文单词首字母提取出来就是 A 、 I 、 C 、 D ,稍微变换一下顺序可以组成一个完整的英文单词:ACID 。想必大家都是学过初高中英语的, ACID 是英文 酸 的意思,以后我们提到 ACID 这个词儿,大家就应该
想到原子性、一致性、隔离性、持久性这几个规则。另外,设计数据库的大叔为了方便起见,把需要保证 原子性 、 隔离性 、 一致性 和 持久性 的一个或多个数据库操作称之为一个 事务 (英文名是: transaction )。

我们现在知道 事务 是一个抽象的概念,它其实对应着一个或多个数据库操作,设计数据库的大叔根据这些操作所执行的不同阶段把 事务 大致上划分成了这么几个状态:
(1). 活动的(active
事务对应的数据库操作正在执行过程中时,我们就说该事务处在 活动的 状态。
(2). 部分提交的(partially committed
当事务中的最后一个操作执行完成,但由于操作都在内存中执行,所造成的影响并没有刷新到磁盘时,我们就说该事务处在 部分提交的 状态。
(3). 失败的(failed
当事务处在 活动的 或者 部分提交的 状态时,可能遇到了某些错误(数据库自身的错误、操作系统错误或者直接断电等)而无法继续执行,或者人为的停止当前事务的执行,我们就说该事务处在 失败的 状态。
(4). 中止的(aborted
如果事务执行了半截而变为 失败的 状态,比如我们前边唠叨的狗哥向猫爷转账的事务,当狗哥账户的钱被扣除,但是猫爷账户的钱没有增加时遇到了错误,从而当前事务处在了 失败的 状态,那么就需要把已经修改的狗哥账户余额调整为未转账之前的金额,换句话说,就是要撤销失败事务对当前数据库造成的影响。书面一点的话,我们把这个撤销的过程称之为 回滚 。当 回滚 操作执行完毕时,也就是数据库恢复到了执行事务之前的状态,我们就说该事务处在了 中止的 状态。
(5). 提交的(committed
当一个处在 部分提交的 状态的事务将修改过的数据都同步到磁盘上之后,我们就可以说该事务处在了 提交的 状态。

随着事务对应的数据库操作执行到不同阶段,事务的状态也在不断变化,一个基本的状态转换图如下所示:
在这里插入图片描述
从图中大家也可以看出了,只有当事务处于提交的或者中止的状态时,一个事务的生命周期才算是结束了。对于已经提交的事务来说,该事务对数据库所做的修改将永久生效,对于处于中止状态的事务,该事务对数据库所做的所有修改都会被回滚到没执行该事务之前的状态。

3.MySQL中事务的语法
我们说 事务 的本质其实只是一系列数据库操作,只不过这些数据库操作符合 ACID 特性而已,那么 MySQL 中如何将某些操作放到一个事务里去执行的呢?我们下边就来重点唠叨唠叨。

3.1.开启事务
我们可以使用下边两种语句之一来开启一个事务:
(1). BEGIN [WORK];
BEGIN 语句代表开启一个事务,后边的单词 WORK 可有可无。开启事务后,就可以继续写若干条语句,这些语句都属于刚刚开启的这个事务。

(2). START TRANSACTION;
START TRANSACTION 语句和 BEGIN 语句有着相同的功效,都标志着开启一个事务。
不过比 BEGIN 语句牛逼一点儿的是,可以在 START TRANSACTION 语句后边跟随几个 修饰符 ,就是它们几个:
a. READ ONLY :标识当前事务是一个只读事务,也就是属于该事务的数据库操作只能读取数据,而不能修改数据。
其实只读事务中只是不允许修改那些其他事务也能访问到的表中的数据,对于临时表来说(我们使用CREATE TMEPORARY TABLE创建的表),由于它们只能在当前会话中可见,所以只读事务其实也是可以对临时表进行增、删、改操作的。
b. READ WRITE :标识当前事务是一个读写事务,也就是属于该事务的数据库操作既可以读取数据,也可以修改数据。
c. WITH CONSISTENT SNAPSHOT :启动一致性读(先不用关心啥是个一致性读,后边的章节才会唠叨)。

比如我们想开启一个只读事务的话,直接把 READ ONLY 这个修饰符加在 START TRANSACTION 语句后边就好,比如这样:START TRANSACTION READ ONLY;

如果我们想在 START TRANSACTION 后边跟随多个 修饰符 的话,可以使用逗号将 修饰符 分开,比如开启一个只读事务和一致性读,就可以这样写:START TRANSACTION READ ONLY, WITH CONSISTENT SNAPSHOT;或者开启一个读写事务和一致性读,就可以这样写:START TRANSACTION READ WRITE, WITH CONSISTENT SNAPSHOT;

不过这里需要大家注意的一点是, READ ONLYREAD WRITE 是用来设置所谓的事务 访问模式 的,就是以只读还是读写的方式来访问数据库中的数据,一个事务的访问模式不能同时既设置为 只读 的也设置为 读写 的,所以我们不能同时把 READ ONLYREAD WRITE 放到 START TRANSACTION 语句后边。另外,如果我们不显式指定事务的访问模式,那么该事务的访问模式就是 读写 模式。

3.2.提交事务
开启事务之后就可以继续写需要放到该事务中的语句了,当最后一条语句写完了之后,我们就可以提交该事务了,提交的语句也很简单:COMMIT [WORK]

COMMIT 语句就代表提交一个事务,后边的 WORK 可有可无。比如我们上边说狗哥给猫爷转10元钱其实对应 MySQL 中的两条语句,我们就可以把这两条语句放到一个事务中,完整的过程就是这样:

mysql> BEGIN;
mysql> UPDATE account SET balance = balance - 10 WHERE id = 1;
mysql> UPDATE account SET balance = balance + 10 WHERE id = 2;
mysql> COMMIT;

3.3.手动中止事务
如果我们写了几条语句之后发现上边的某条语句写错了,我们可以手动的使用下边这个语句来将数据库恢复到事务执行之前的样子:ROLLBACK [WORK]

ROLLBACK 语句就代表中止并回滚一个事务,后边的 WORK 可有可无类似的。比如我们在写狗哥给猫爷转账10元钱对应的 MySQL 语句时,先给狗哥扣了10元,然后一时大意只给猫爷账户上增加了1元,此时就可以使用 ROLLBACK 语句进行回滚,完整的过程就是这样:

mysql> BEGIN;
mysql> UPDATE account SET balance = balance - 10 WHERE id = 1;
mysql> UPDATE account SET balance = balance + 1 WHERE id = 2;
mysql> ROLLBACK;

这里需要强调一下, ROLLBACK 语句是我们程序员手动的去回滚事务时才去使用的,如果事务在执行过程中遇到了某些错误而无法继续执行的话,事务自身会自动的回滚。

我们这里所说的开启、提交、中止事务的语法只是针对使用黑框框时通过mysql客户端程序与服务器进行交互时控制事务的语法,如果大家使用的是别的客户端程序,比如JDBC之类的,那需要参考相应的文档来看看如何控制事务。

3.4.支持事务的存储引擎
MySQL 中并不是所有存储引擎都支持事务的功能,目前只有 InnoDBNDB 存储引擎支持(NDB存储引擎不是我们的重点),如果某个事务中包含了修改使用不支持事务的存储引擎的表,那么对该使用不支持事务的存储引擎的表所做的修改将无法进行回滚。比方说我们有两个表, tbl1 使用支持事务的存储引擎 InnoDBtbl2 使用不支持事务的存储引擎 MyISAM ,它们的建表语句如下所示:

CREATE TABLE tbl1 (
 i int
) engine=InnoDB;
CREATE TABLE tbl2 (
 i int
) ENGINE=MyISAM;

我们看看先开启一个事务,写一条插入语句后再回滚该事务, tbl1 和 tbl2 的表现有什么不同:

mysql> SELECT * FROM tbl1;
Empty set (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO tbl1 VALUES(1);
Query OK, 1 row affected (0.00 sec)
mysql> ROLLBACK;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM tbl1;
Empty set (0.00 sec)

可以看到,对于使用支持事务的存储引擎的 tbl1 表来说,我们在插入一条记录再回滚后, tbl1 就恢复到没有插入记录时的状态了。再看看 tbl2 表的表现:

mysql> SELECT * FROM tbl2;
Empty set (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO tbl2 VALUES(1);
Query OK, 1 row affected (0.00 sec)
mysql> ROLLBACK;
Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> SELECT * FROM tbl2;
+------+
| i |
+------+
| 1 |
+------+
1 row in set (0.00 sec)

可以看到,虽然我们使用了 ROLLBACK 语句来回滚事务,但是插入的那条记录还是留在了 tbl2 表中。

3.5.自动提交
MySQL 中有一个系统变量 autocommit

mysql> SHOW VARIABLES LIKE 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.01 sec)

可以看到它的默认值为 ON ,也就是说默认情况下,如果我们不显式的使用 START TRANSACTION 或者 BEGIN 语句开启一个事务,那么每一条语句都算是一个独立的事务,这种特性称之为事务的 自动提交 。假如我们在狗哥向猫爷转账10元时不以 START TRANSACTION 或者 BEGIN 语句显式的开启一个事务,那么下边这两条语句就相当于放到两个独立的事务中去执行:

UPDATE account SET balance = balance - 10 WHERE id = 1;
UPDATE account SET balance = balance + 10 WHERE id = 2;

当然,如果我们想关闭这种 自动提交 的功能,可以使用下边两种方法之一:
(1). 显式的的使用 START TRANSACTION 或者 BEGIN 语句开启一个事务。这样在本次事务提交或者回滚前会暂时关闭掉自动提交的功能。
(2). 把系统变量 autocommit 的值设置为 OFF ,就像这样: SET autocommit = OFF;
这样的话,我们写入的多条语句就算是属于同一个事务了,直到我们显式的写出 COMMIT 语句来把这个事务提交掉,或者显式的写出 ROLLBACK 语句来把这个事务回滚掉。

3.6.隐式提交
当我们使用 START TRANSACTION 或者 BEGIN 语句开启了一个事务,或者把系统变量 autocommit 的值设置为 OFF 时,事务就不会进行 自动提交 ,但是如果我们输入了某些语句之后就会 悄悄的 提交掉,就像我们输入了 COMMIT 语句了一样,这种因为某些特殊的语句而导致事务提交的情况称为 隐式提交 ,这些会导致事务隐式提交的语句包括:
(1). 定义或修改数据库对象的数据定义语言(Data definition language,缩写为: DDL )。
所谓的数据库对象,指的就是 数据库 、 表 、 视图 、 存储过程 等等这些东西。当我们使用 CREATE 、ALTER 、 DROP 等语句去修改这些所谓的数据库对象时,就会隐式的提交前边语句所属于的事务,就像这样:

BEGIN;
SELECT ... # 事务中的一条语句
UPDATE ... # 事务中的一条语句
... # 事务中的其它语句
CREATE TABLE ... # 此语句会隐式的提交前边语句所属于的事务

(2). 隐式使用或修改 mysql 数据库中的表
当我们使用 ALTER USER 、 CREATE USER 、 DROP USER 、 GRANT 、 RENAME USER 、 REVOKE 、 SET PASSWORD 等语句时也会隐式的提交前边语句所属于的事务。

(3). 事务控制或关于锁定的语句
当我们在一个事务还没提交或者回滚时就又使用 START TRANSACTION 或者 BEGIN 语句开启了另一个事务时,会隐式的提交上一个事务,比如这样:

 BEGIN;
 SELECT ... # 事务中的一条语句
 UPDATE ... # 事务中的一条语句
 ... # 事务中的其它语句
 BEGIN; # 此语句会隐式的提交前边语句所属于的事务

或者当前的 autocommit 系统变量的值为 OFF ,我们手动把它调为 ON 时,也会隐式的提交前边语句所属的事务。
或者使用 LOCK TABLES 、 UNLOCK TABLES 等关于锁定的语句也会隐式的提交前边语句所属的事务。

(4). 加载数据的语句
比如我们使用 LOAD DATA 语句来批量往数据库中导入数据时,也会隐式的提交前边语句所属的事务。

(5). 关于 MySQL 复制的一些语句
使用 START SLAVE 、 STOP SLAVE 、 RESET SLAVE 、 CHANGE MASTER TO 等语句时也会隐式的提交前边语句所属的事务。

(6). 其它的一些语句
使用 ANALYZE TABLE 、 CACHE INDEX 、 CHECK TABLE 、 FLUSH 、 LOAD INDEX INTO CACHE 、 OPTIMIZE TABLE 、 REPAIR TABLE 、 RESET 等语句也会隐式的提交前边语句所属的事务。

3.7.保存点
如果你开启了一个事务,并且已经敲了很多语句,忽然发现上一条语句有点问题,你只好使用 ROLLBACK 语句来让数据库状态恢复到事务执行之前的样子,然后一切从头再来,总有一种一夜回到解放前的感觉。所以设计数据库的大叔们提出了一个 保存点 (英文: savepoint )的概念,就是在事务对应的数据库语句中打几个点,我们在调用 ROLLBACK 语句时可以指定会滚到哪个点,而不是回到最初的原点。定义保存点的语法如下:SAVEPOINT 保存点名称;

当我们想回滚到某个保存点时,可以使用下边这个语句(下边语句中的单词 WORKSAVEPOINT 是可有可无的):ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称;

不过如果 ROLLBACK 语句后边不跟随保存点名称的话,会直接回滚到事务执行之前的状态。如果我们想删除某个保存点,可以使用这个语句:RELEASE SAVEPOINT 保存点名称;

下边还是以狗哥向猫爷转账10元的例子展示一下 保存点 的用法,在执行完扣除狗哥账户的钱 10 元的语句之后打一个 保存点 :

mysql> SELECT * FROM account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 狗哥 | 11 |
| 2 | 猫爷 | 2 |
+----+--------+---------+
2 rows in set (0.00 sec)
mysql> BEGIN;
Query OK, 0 rows affected (0.00 sec)
mysql> UPDATE account SET balance = balance - 10 WHERE id = 1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> SAVEPOINT s1; # 一个保存点
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 狗哥 | 1 |
| 2 | 猫爷 | 2 |
+----+--------+---------+
2 rows in set (0.00 sec)
mysql> UPDATE account SET balance = balance + 1 WHERE id = 2; # 更新错了
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> ROLLBACK TO s1; # 回滚到保存点s1处
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM account;
+----+--------+---------+
| id | name | balance |
+----+--------+---------+
| 1 | 狗哥 | 1 |
| 2 | 猫爷 | 2 |
+----+--------+---------+
2 rows in set (0.00 sec)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/297334.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JVM之内存模型、运行时的数据区域的划分、java的程序计数器作用等

JVM JVM内存模型运行时数据区域划分程序计数器(Program Counter Register) JVM内存模型 ​ 对于Java程序来说,在虚拟机自动内存管理机制下,不再需要像C/C程序开发程序员这样每一个new操作去写对应的delete/free操作,不…

大语言模型LLM微调技术:Prompt Tuning

1 预训练语言模型概述 1.1 预训练语言模型的发展历程 截止23年3月底,语言模型发展走过了三个阶段: 第一阶段 :设计一系列的自监督训练目标(MLM、NSP等),设计新颖的模型架构(Transformer&#…

在 docker 容器中配置双网卡,解决通讯的问题

目录 1. 查看当前网络信息 2. 创建自定义网络桥 3. 创建双网卡模式 4. 删除默认网卡 已经创建好了的 Docker 容器,要修改它的IP比较麻烦,网上找了几种不同的方法,经过试验都没有成功,下面通过配置双网上来解决 IP 的问题。…

vue3.x 的生命周期和钩子函数

什么是生命周期 Vue 是组件化编程,从一个组件诞生到消亡,会经历很多过程,这些过程就叫做生命周期。 你理解了什么是生命周期,你还了解一个概念“钩子函数”。 钩子函数: 伴随着生命周期,给用户使用的函数&…

TypeScript接口、对象

目录 1、TypeScript 接口 1.1、实例 1.2、联合类型和接口 1.3、接口和数组 1.4、接口和继承 1.5、单继承实例 1.6、多继承实例 2、TypeScript 对象 2.2、对象实例 2.3、TypeScript类型模板 2.4、鸭子类型(Duck typing) 1、TypeScript 接口 接口…

21.串的处理

题目 import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc new Scanner(System.in);String str sc.nextLine();char[] c str.toCharArray();int n c.length;StringBuilder st new StringBuilder();int i 0;while(i<n)…

element-ui组件DatePicker日期选择器移动端兼容

element-ui组件DatePicker日期选择器移动端兼容 css /** 移动端展示 **/ media screen and (max-width: 500px) {.el-picker-panel__sidebar {width: 100%;}.el-picker-panel {width: 400px!important;}.el-picker-panel__content {width: 100%;}.el-picker-panel__body{marg…

git打tag以及拉取tag

场景&#xff1a;某次git代码发布后定版记录&#xff0c;将发版所在的commit时候代码打上tag记录&#xff0c;方便后期切换到对应tag代码位置。 查看所有tag名 git tag// 1.1.0 // 1.0.0查看tag和描述 git tag -l -n//1.0.0 云监管一期项目完结 //1.1.0 …

[Flutter]WebPlatform上运行遇到的问题总结

[Flutter]WebPlatform上运行遇到的问题总结 目录 [Flutter]WebPlatform上运行遇到的问题总结 写在开头 正文 Q1、file_version_info.dart Q2、不兼容判断 Q3、跨域问题 Q4、flutter clean &#xff0c;无法删除build文件夹 其他 写在结尾 写在开头 Flutter项目已能在…

初中数学:几何题的相关解题原则总结

一、多问类型的几何题 我们做题&#xff0c;应该都遇到过这类几何题目&#xff0c;就是&#xff0c;三个小问&#xff0c;每个小问对应一个几何图像&#xff0c;而且&#xff0c;渐渐复杂。这种题目&#xff0c;大多数有一个变化的条件&#xff0c;比如&#xff0c;动点、角度…

C语言的前置声明

前置声明主要用于类与类之间的循环引用&#xff0c;且只是针对类指针或者引用有效&#xff0c;对完整的类字段无效。如下代码所示&#xff1a; #include <iostream> using namespace std;class B; class C;class A { public:B *b;C c; ///< 错误写法C* c1; ///< 正…

mongodb基本命令操作

1.创建数据库 语法 use 数据库名字例如:创建hero数据库 use hero查询当前数据库 db如果想查询所有的数据库 show dbs发现并没有刚刚创建的数据库,如果要显示创建的数据库,需要向表中插入一条记录 db.hero.insert({name: "zs",age: 20,country: "china&quo…

【论文精读】A Survey on Large Language Model based Autonomous Agents

A Survey on Large Language Model based Autonomous Agents 前言Abstract1 Introduction2 LLM-based Autonomous Agent Construction2.1 Agent Architecture Design2.1.1 Profiling Module2.1.2 Memory ModuleMemory StructuresMemory FormatsMemory Operations 2.1.3 Plannin…

easyrecovery16 (硬盘数据恢复软件)免费版

EasyRecovery是由球著名数据厂商Kroll Ontrack出品的一款便捷实用&#xff0c;功能强大的硬盘数据恢复软件。它能够全面恢复删除丢失数据&#xff0c;支持包括文档、表格、图片、音视频等各种文件类型。支持恢复不同存储介质数据&#xff1a;硬盘、光盘、U盘/移动硬盘、数码相机…

Docker、Docker-compose安装

安装Docker 1.卸载旧版 首先如果系统中已经存在旧的Docker&#xff0c;则先卸载&#xff1a; yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-latest-logrotate \docker-logrotate \docker-engine2.配置Docker的yum库 首先…

微服务实战系列之API加密

前言 随着一阵阵凛冽寒风的呼啸&#xff0c;新的年轮不知不觉滚滚而来。故事随着2023的远去&#xff0c;尘封于案底&#xff1b;希望迎着新年&#xff0c;绽放于枝头。在2024新岁启航&#xff0c;扬帆破浪之时&#xff0c;让烦恼抛洒于九霄&#xff0c;让生机蓬勃于朝朝暮暮。 …

一张图告诉你商标和版权的区别

版权和商标之间存在以下关系&#xff1a; 1.关联性&#xff1a;版权和商标权均为知识产权&#xff0c;受相关法律保护。 2.区别性&#xff1a;版权自动产生&#xff0c;且作用于已经完成的作品&#xff0c;而商标专用权需要注册取得&#xff0c;且作用于商标标识。 此外&…

算法通关村第二十关-黄金挑战图的常见算法

大家好我是苏麟 , 今天聊聊图的常见算法 . 图里的算法是很多的&#xff0c;这里我们介绍一些常见的图算法。这些算法一般都比较复杂&#xff0c;我们这里介绍这些算法的基本含义&#xff0c;适合面试的时候装*&#xff0c;如果手写&#xff0c;那就不用啦。 图分析算法&#xf…

文件夹重命名方法:提高效率减少错误,中英文批量翻译文件夹名称

在日常生活和工作中&#xff0c;经常要处理大量的文件夹&#xff0c;无论是整理电脑上的文件&#xff0c;还是为项目分类。如何快速、准确地重命名这些文件夹&#xff0c;对于提高工作效率和减少错误至关重要。现在来看下云炫文件管理器一些实用的文件夹重命名方法&#xff0c;…

STM32时钟树

一、四个时钟源 二、时钟树 各类时钟简括&#xff1a; 1.HSE时钟&#xff08;高速外部时钟&#xff09;&#xff1a;来源为外部无源晶振&#xff0c;通常速度8M。 2.HSI时钟&#xff08;高速内部时钟&#xff09;&#xff1a;来源为芯片内部&#xff0c;大小为8M&#xff0c;当…
最新文章