最近有在追踪一个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才能实现真正的框架层事务嵌套。
where与having非常类似.都能筛选数据.表达式完全一致. 但是职责的确不同.where负责对表中的字段进行筛选,having负责对where筛选后的结果集再次筛选。这也就是where不能使用别名字段来筛选的原因,因为数据中没有这个字段。&n...
我们要明白Mysql字段的长度能存多少东西,首先需要明白Mysql是计算字节长度,还是计算字符长度。在mysql4.x的版本长度代表的是字节长度.例如在mysql4.x的版本中varchar(10)能储存的中英文长度如下:(1).采用ISO8859-1编码方式时,一个中/英文都只占一个字节;(2)....
需求查询出存在商品的商品分类. 先看看分类表:id(分类的id) catename(分类名) 1 手机 2 &n...
Left join:即左连接,是以左表为基础,根据ON后给出的两表的条件将两表连接起来。结果会将左表所有的查询信息列出,而右表只列出ON后条件与左表满足的部分。左连接全称为左外连接,是外连接的一种。Right join:即右连接,是以右表为基础,根据ON后给出的两表的条件将两表连接起来。结果会将右表...
有很多集成环境安装完成之后是没有快捷方式的,例如西部数码的网站管理助手4.0,、 更或者是护卫神PHP套件都是一样的。安装完成最多给你安装一个PhPmyadmin让你管理Mysql,但是对于经常使用命令行的我们来说是非常不方面的,而且还必须安装PhPmyadmin来管理。下面就让我们自己手...
我们从一个结果集中查询信息一般都是select * from (select...),每次都要编写from (select...)非常麻烦,于是我们将结果集保存起来,这就是视图的便利。创建视图的命令为:create view &nb...