Home » Code » pdo-mysql事务的使用

pdo-mysql事务的使用

关于自动提交

pdo-mysql中的PDO::ATTR_AUTOCOMMIT跟mysql自身的autocommit是两回事,这个首先要清楚。mysql自身的autocommit默认是打开的,值为ON。PDO::ATTR_AUTOCOMMIT这个值默认是1,也就是是打开的,跟mysql一致。

mysql_autocommit

在mysql中,默认情况下,每一条SQL语句都是一个小小的事务,语句执行没有错误这个事务就提交,发生错误这个事务就不提交,这就是autocommit=1自动提交打开的效果。如果将autocommit设置为0,即使SQL语句没有错误,这个事务也不会提交,除非手动执行commit使更改生效或者执行rollback放弃更改,这样这个事务才算提交了。这就是autocommit=0关闭自动提交的效果。很显然,大多数情况下,我们都想让语句执行没错误就生效,也就是让事务自动提交。否则每次都得commit一下,太麻烦。因此,默认配置是合理的。

有时候,我们想让多条SQL语句形成一个事务,可以设置autocommit=0,这样除非手动commit或者rollback,否则其中执行的SQL语句都是不会被提交的,也就相当于这多条SQL语句形成了一个事务了。但这样做之后记得把autocommit=1改回来,否则其他地方的SQL就可能不生效了(它们之后没有显式执行commit)。如果不改动autocommit=0这个配置,可以先执行begin或者start transaction命令,这样除非碰到commit/rollback,否则这中间的语句也是不会提交的,一样达到多条SQL语句形成一个事务的目的。推荐使用后者。

PDO::ATTR_AUTOCOMMIT是PDO的一个属性,它的值其实就是影响着运行时mysql autocommit的值,二者是一致的,估计就是执行了”set autocommit=PDO::ATTR_AUTOCOMMIT”。当它为1时,PDO对象执行的SQL直接生效,当它为0时,需要执行一下’commit’命令才会生效(不是使用commit(),如果要使用这个得先beginTransaction())。因此一般保持默认的1即可。

关于事务操作的写法

事务操作,如果是PDO,建议使用beginTransaction()及相关方法来进行,当然直接使用上边说的命令操作mysql也是可以的。当有语句发生错误时希望执行回滚操作,如何能知道是否发生了错误呢?在这里还有一个重要属性就是事务中发生了错误程序作何反应,通过PDO::ATTR_ERRMODE来设置,默认值是0(ERRMODE_SILENT),表示静默不会有任何反应,这时候所执行的SQL语句如果使用了prepare(),execute()时会返回false,受影响的记录数是0,不会对数据库做出任何更改。如果错误模式是1(ERRMODE_WARNING),会发出提醒,程序不会终止但返回结果跟静默模式是一样的。再或者错误模式是2(ERRMODE_EXCEPTION),会抛异常直接终止了脚本,更不会对数据库做出更改了。

因此,怎么写得根据ERRMODE的值来决定,如果是静默模式,出错在PDOStatement对象进行execute()时返回false,如果没有考虑这个返回值直接返回rowCounts()或者没有prepare直接使用PDO对象进行了query()或者exec(),返回结果会是0或者false,如果是0就不好判断了,因为有的语句本身没出错受影响记录就是0。如果是提醒模式,又屏蔽了提醒,结果跟静默模式一样。因此最好是使用抛异常模式,然后进行try。见过一些人写的代码,ERRMODE是抛异常,却是根据返回值来判断是否出错,这明显是有问题的。因为抛出异常就终止了,根本没有走到返回值给你判断那一步。

关于事务嵌套

首先要知道,mysql是不支持事务嵌套(nest)的。每开启一个新的事务(set autocommit=0或者begin/start transaction)都会隐藏的提交上一个事务。见文档

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.

但实际写程序的过程中,经常有意无意的就造成了嵌套。比如在A函数中进行了事务操作,事务操作调用了B函数,B函数又进行事务操作,就造成了嵌套。如果不进行处理,A函数中的事务在B中开启事务时就给就提交了,实际我们是希望保持着外边的“大事务”不被提交的。以下是一个可行的办法,简单封装一下,用一个属性存层级,只有第一层进行真正的事务开启,内部层就不进行了,只作层级的加减。这样的话,得保证beginTransaction()、commit()/rollBack()成套出现,否则就乱了不灵了。

public function beginTransaction()
{
    ++$this->transactions;
    if ($this->transactions == 1) {
        //只有第一层才真正开启事务
        return $this->pdo->beginTransaction();
    }
    return true;
}

public function commit()
{
    if ($this->transactions == 1) {
        $this->transactions = 0;
        return $this->pdo->commit();
    } else {
        --$this->transactions;
        return true;
    }
}

public function rollBack()
{
    if ($this->transactions == 1) {
        $this->transactions = 0;
        return $this->pdo->rollBack();
    } else {
        --$this->transactions;
        return true;
    }
}

参考自:https://segmentfault.com/a/1190000002411193

Leave a Reply

Your email address will not be published. Required fields are marked *

*

Time limit is exhausted. Please reload CAPTCHA.