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

最近有在追踪一个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才能实现真正的框架层事务嵌套。

上一篇:java手动引入jar包


下一篇:关于 sql 行转列的感悟<转>