当前位置:首页 > SQL > 正文内容

MYSQL事务嵌套,PHP事务嵌套,THINKPHP事务嵌套

高老师4年前 (2021-09-24)SQL2851

最近有在追踪一个tp3的事务问题,正好看到事务嵌套的问题,于是整理了出来,本来想等待同事整理,白嫖他,结果等了个寂寞。

(1).参考事务嵌套的错误SQL:

### 事务1开启
BEGIN;

 ## 事务1修改数据
 UPDATE hqjf_job_num  SET wx_uname='蒋琦1024' where id = 602;
 
 ### 事务2开启
 BEGIN;
 
 ### 事务2提交
 COMMIT; 
 
### 事务1回滚
ROLLBACK;

我们期望的结果:update语句不会执行成功,实际执行成功了

出现问题的原因:BEGIN语句会隐式的执行事务提交,相当于第二个BEGIN执行的时候提交了第一个事务.

(2).哪些SQL语法会隐式事务提交事务呢?

参考Mysql官方文档:

https://dev.mysql.com/doc/refman/5.7/en/implicit-commit.html

(2.1).DDL 

ALTER DATABASE ... UPGRADE DATA DIRECTORY NAME, ALTER EVENT, ALTER PROCEDURE, ALTER SERVER, ALTER TABLE, ALTER TABLESPACE, ALTER VIEW, CREATE DATABASE, CREATE EVENT, CREATE INDEX, CREATE PROCEDURE, CREATE SERVER, CREATE TABLE, CREATE TABLESPACE, CREATE TRIGGER, CREATE VIEW, DROP DATABASE, DROP EVENT, DROP INDEX, DROP PROCEDURE, DROP SERVER, DROP TABLE, DROP TABLESPACE, DROP TRIGGER, DROP VIEW, INSTALL PLUGIN, RENAME TABLE, TRUNCATE TABLE, UNINSTALL PLUGIN.

(2.2).USER|MODIFY TABLES

ALTER USER, CREATE USER, DROP USER, GRANT, RENAME USER, REVOKE, SET PASSWORD.

(2.3).TRANSACTION|LOCK TABLES

BEGIN, LOCK TABLES, (if the value is not already 1), START TRANSACTION, UNLOCK TABLES. SET autocommit = 1,UNLOCK TABLES

(2.4).DATA LOADING STATEMENTS

LOAD DATA

(2.5).ADMINISTRATIVE STATEMENTS

ANALYZE TABLE, CACHE INDEX, CHECK TABLE, FLUSH, LOAD INDEX INTO CACHE, OPTIMIZE TABLE, REPAIR TABLE, RESET

(2.6).REPLICATION CONTROL STATEMENTS

START SLAVE, STOP SLAVE, RESET SLAVE, CHANGE MASTER TO

好家伙原来这多SQL操作都会隐式提交事务

同时上面的文档中提到:

Transactions cannot be nested. This is a consequence of the implicit commit performed for any current transaction when you issue a START TRANSACTION statement or one of its synonyms.

事务不能嵌套。 这是当您发出 START TRANSACTION 语句或其同义词之一时对任何当前事务执行的隐式提交的结果。

(3).设置autocommit并不能解决上面的事务嵌套问题,好好了解下autocommit到底是啥

首先要知道什么叫自动提交。就是自动提交事务啦。瓜娃子。

(3.1).假设开启事务自动提交的时候,你执行一个SQL如下:

UPDATE hqjf_job_num  SET wx_uname='蒋琦104' where id = 6956;

实际上已经等价于执行了如下SQL:

BEGIN;
UPDATE hqjf_job_num  SET wx_uname='蒋琦104' where id = 6956;
COMMIT;

只不过是MYSQL帮你的SQL自动加了事务并且提交了。

如果你开启了事务自动提交且自己使用了事务操作,MYSQL就会乖乖听你的,不会乱自动提交,除非你自己隐式提交。

(3.2).假设关闭事务自动提交的时候,你执行一个SQL如下:

UPDATE hqjf_job_num  SET wx_uname='蒋琦104' where id = 6956;

实际上根本不会执行成功,关闭事务自动提交后MYSQL要求你必须自己手动提交事务,否则SQL没有提交也就不会更新咯

老高你的内容我看不懂,给我推荐1个详细的autocomit的文章,好的,给你。直达地址:https://blog.csdn.net/wx145/article/details/82740737

上面我们得出的结论是MYSQL是不支持事务嵌套的,特别注意是不支持的,不支持的,不支持的!别看其他文章瞎说,看官方文档。

(4).MYSQL不支持事务嵌套,如果模拟事务嵌套的效果

(4.1).例子SQL:

### 开启事务
BEGIN;

### 建立事务保存点a
SAVEPOINT a;

### 更新数据名称为1024
UPDATE hqjf_job_num  SET wx_uname='1024'  WHERE id=7638;

### 建立事务保存点b
SAVEPOINT b;

### 更新数据名称为2048
UPDATE hqjf_job_num  SET wx_uname='2048'  WHERE id=7638;

### 建立事务保存点d
SAVEPOINT c;

### 回滚到事务保存点
ROLLBACK TO SAVEPOINT a;

### 提交事务
COMMIT;

假设以上数据的原始wx_uname的原始值为空

ROLLBACK TO SAVEPOINT a;则数据不会修改
ROLLBACK TO SAVEPOINT b;则数据会被修改为1024
ROLLBACK TO SAVEPOINT c;则数据会被修改为2048

看看上面的SQL代码的执行顺序吧:

上面的SQL在执行到ROLLBACK TO SAVEPOINT a的时候回跳到建立事务保存点a的位置,然后执行剩下的COMIT语句,因此示例的SQL不会修改任何数据

(5).PHP框架中解决MYSQL事务嵌套的方案(TP6)

开启事务示例:

/**
 * 启动事务
 * @access public
 * @return void
 * @throws \PDOException
 * @throws \Exception
 */
public function startTrans(): void
{
    try {
        $this->initConnect(true);
        ++$this->transTimes;
        if (1 == $this->transTimes) {
            $this->linkID->beginTransaction();
        } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
            $this->linkID->exec(
                $this->parseSavepoint('trans' . $this->transTimes)
            );
        }
        $this->reConnectTimes = 0;
    } catch (\Throwable | \Exception $e) {
        if ($this->transTimes === 1 && $this->reConnectTimes < 4 && $this->isBreak($e)) {
            --$this->transTimes;
            ++$this->reConnectTimes;
            $this->close()->startTrans();
        } else {
            if ($this->isBreak($e)) {
                // 尝试对事务计数进行重置
                $this->transTimes = 0;
            }
            throw $e;
        }
    }
}

开启事务时统一递增事务次数

第一次开启事务则真正调用MYSQL开启事务

第二次或以上开启事务分情况:支持savepoint时调用MYSQL创建事务保存点,不支持时则相当于啥也不干,


执行事务回滚示例:

/**
 * 事务回滚
 * @access public
 * @return void
 * @throws \PDOException
 */
public function rollback(): void
{
    $this->initConnect(true);
    if (1 == $this->transTimes) {
        $this->linkID->rollBack();
    } elseif ($this->transTimes > 1 && $this->supportSavepoint()) {
        $this->linkID->exec(
            $this->parseSavepointRollBack('trans' . $this->transTimes)
        );
    }
    $this->transTimes = max(0, $this->transTimes - 1);
}

执行事务回滚时事务次数统一减1

如果事务次数为1则真正提交MYSQL让事务回滚

如果事务次数大于1并且支持savepoint则回滚事务到事务保存点


执行事务提交的示例:

/**
 * 用于非自动提交状态下面的查询提交
 * @access public
 * @return void
 * @throws \PDOException
 */
public function commit(): void
{
    $this->initConnect(true);
    if (1 == $this->transTimes) {
        $this->linkID->commit();
    }
    --$this->transTimes;
}

只有事务次数为1的时候才会真正提交MYSQL事务

通过框架层的支持,你虽然包含了多层事务,但是本质上你只会真正开启1次事务,提交1次事务,配合savepoint实现事务嵌套的效果。和上面我们模拟事务嵌套的效果一致。

我看到很多PHP事务嵌套没有使用savepoint的实现,严格来说不算是事务嵌套,比如下面的问题:

// 开启主事务
Db::startTrans();
// 开启子事务
Db::startTrans();
// 执行UPDATE语句
// 回滚子事务
Db::rollback();
// 提交主事务
Db::rollback();

最终结果导致主事务提交后子事务的SQL也执行了,因为子事务开启和回滚是虚拟的,什么也没做。当然部分实现中只要子事务回滚强制让主事务也回滚,这样失去的嵌套的意义。

所以支持saveponit才能实现真正的框架层事务嵌套。

扫描二维码推送至手机访问。

版权声明:本文由高久峰个人博客发布,如需转载请注明出处。

本文链接:https://blog.20230611.cn/post/356.html

分享给朋友:

“MYSQL事务嵌套,PHP事务嵌套,THINKPHP事务嵌套” 的相关文章

主键primary key是否必须和auto_increment 一起出现

主键primary key是否必须和auto_increment 一起出现

    一般情况下,绝大部分,我们的主键是数字,1 2 3 4...所以我们才让它递增.这并不意味着,他们两个必须要绑定在一起使用.例如我还想将用户表的email字段设置为主键,但是并没有必要为其设置自增。    因此可...

 mysql储存引擎,mysql的5种储存引擎

mysql储存引擎,mysql的5种储存引擎

1.MyISAM 建立一个MyISAM引擎的表时,就会在本地磁盘上建立三个文件,.frm格式文件,存储表定义;.MYD格式文件,存储数据;MYI格式文件,存储索引;方便数据迁移,我只需将mysql安装目录下data文件中的表文件复制即可完成数据迁移,之前在搬迁多个dedecms中深有体会。 ...

mysql触发器,mysql触发器语法,mysql创建触发器

mysql触发器,mysql触发器语法,mysql创建触发器

    触发器是一种特殊的事务,可以监听到Mysql的(insert/update/delete)的操作并触发相应的(insert/update/delete)操作.    触发器的创建主要有4个要素:(1).监听地点(...

 mysql count 性能优化,mysql count(*)优化

mysql count 性能优化,mysql count(*)优化

1.很多人认为count查询非常快,但是在加上筛选条件那就是未必的了!测试:user表中4000w数据(1).SELECT  count(*)   from user;   用时0.00s (2).SELECT...

mysql构建海量表,mysql 海量数据创建

mysql构建海量表,mysql 海量数据创建

海量表,数据量较大,并且每个字段的值具有唯一性。如果你创建的海量表只是数据多,索引是毫无意义的。构建海量时我们要使用存储过程。学习自韩顺平(1).创建一个测试数据库create  database  testdb;set names gbk;use testdb(2).创建dep...

inner join 和 form a,b区别和速度

inner join 和 form a,b区别和速度

在项目中发现大量的form连接表,就开始质疑inner join 和 form a,b的性能问题。找到一份有价值的资料,特别记录:ANSI SQL规范首选INNER JOIN语法。此外,尽管使用WHERE子句定义联结的确比较简单,但是使用明确的联结语法能够确保不会忘记联结条件,有时候这样做也能影响性...