Thinkphp6 分布式事务异常处理 1440 XAER_DUPID: The XID already exists

Mysql分布式事务,前提条件

MySQL中只有当隔离级别设置为Serializable的时候才能使用分布式事务。

执行两个命令确认环境

show variables like 'innodb_support_xa';

在这里插入图片描述

show variables like '%tx_iso%';

在这里插入图片描述

异常信息

根据官方分布式示例

public function test()
    {
        Db::transactionXa(function () {
            $updateTime = time();
            Db::connect('cdpf_driver')->table('cdpf_driver_user_login_his')->where(["id" => 3128])->update(["login_time" => $updateTime]);
            Db::connect('cheduo')->table('users')->where(["id" => 2])->update(["updated_at" => date("Y-m-d H:i:s", $updateTime)]);
        }, [Db::connect('cdpf_driver'),Db::connect('cheduo')]);
}

如果是同一个服务,执行会出现
异常提示:

SQLSTATE[XAE08]: <>: 1440 XAER_DUPID: The XID already exists

解决办法:

方案一:单独编写,不要用thinkphp封装好的

public function test()
    {
        $xid = uniqid('xa');
        $updateTime = time();

        // 第一个数据库连接
        $cdpfDriverXid = "cdpf_driver_{$xid}";
        $cdpfDriverDb = Db::connect('cdpf_driver');
        $cdpfDriverConnect = $cdpfDriverDb->connect();

        // 第二个数据库连接
        $cheduoXid = "cheduo_{$xid}";
        $cheduoDb = Db::connect('cheduo');
        $cheduoConnect = $cheduoDb->connect();
        try {

            // 第一个事务开始
            $cdpfDriverConnect->exec("XA begin '$cdpfDriverXid'");
            $cdpfDriverDb->table('cdpf_driver_user_login_his')->where(["id" => 3128])->update(["login_time" => $updateTime]);
            $cdpfDriverConnect->exec("XA END '$cdpfDriverXid'");
            // 到这里挂起

            // 第二个事务开始
            $cheduoConnect->exec("XA begin '$cheduoXid'");
            $cheduoDb->table('users')->where(["id" => 2])->update(["updated_at" => date("Y-m-d H:i:s", $updateTime)]);
            $cheduoConnect->exec("XA END '$cheduoXid'");
            // 到这里挂起

            // 预执行与提交
            $cdpfDriverConnect->exec("XA PREPARE '$cdpfDriverXid'");
            $cdpfDriverConnect->exec("XA COMMIT '$cdpfDriverXid'");

            $cheduoConnect->exec("XA PREPARE '$cheduoXid'");
            $cheduoConnect->exec("XA COMMIT '$cheduoXid'");
        } catch (\Exception $ex) {
            echo $ex->getMessage();

            // 注意:其实如果语句本身存在问题,并没有走到 XA END。这里回滚会抛出异常
            $cdpfDriverConnect->exec("XA ROLLBACK '$cdpfDriverXid'");
            $cheduoConnect->exec("XA ROLLBACK '$cheduoXid'");
        }
        return "success";
    }

方案二,改动源码

namespace think\db;

/**
 * 数据库连接基础类
 * @property PDO[] $links
 * @property PDO   $linkID
 * @property PDO   $linkRead
 * @property PDO   $linkWrite
 */
abstract class PDOConnection extends Connection
{
	/**
     * 执行数据库Xa事务
     * @access public
     * @param  callable $callback 数据操作方法回调
     * @param  array    $dbs      多个查询对象或者连接对象
     * @return mixed
     * @throws PDOException
     * @throws \Exception
     * @throws \Throwable
     */
    public function transactionXa(callable $callback, array $dbs = [])
    {
        $xid = uniqid('xa');

        if (empty($dbs)) {
            $dbs[] = $this;
        }

        foreach ($dbs as $key => $db) {
            if ($db instanceof BaseQuery) {
                $db = $db->getConnection();

                $dbs[$key] = $db;
            }

            $db->startTransXa($db->getConfig('hostname').'_'.$db->getConfig('database').'_'.$xid);
        }

        try {
            $result = null;
            if (is_callable($callback)) {
                $result = $callback($this);
            }

            foreach ($dbs as $db) {
                $db->prepareXa($db->getConfig('hostname').'_'.$db->getConfig('database').'_'.$xid);
            }

            foreach ($dbs as $db) {
                $db->commitXa($db->getConfig('hostname').'_'.$db->getConfig('database').'_'.$xid);
            }

            return $result;
        } catch (\Exception | \Throwable $e) {
            foreach ($dbs as $db) {
                $db->rollbackXa($db->getConfig('hostname').'_'.$db->getConfig('database').'_'.$xid);
            }
            throw $e;
        }
    }
}

下面来分析原因:
其实从错误提醒这里很好得知
分布式唯一ID已经存在了

跟踪源码

namespace think\db;

/**
 * 数据库连接基础类
 * @property PDO[] $links
 * @property PDO   $linkID
 * @property PDO   $linkRead
 * @property PDO   $linkWrite
 */
abstract class PDOConnection extends Connection
{
	/**
     * 执行数据库Xa事务
     * @access public
     * @param  callable $callback 数据操作方法回调
     * @param  array    $dbs      多个查询对象或者连接对象
     * @return mixed
     * @throws PDOException
     * @throws \Exception
     * @throws \Throwable
     */
    public function transactionXa(callable $callback, array $dbs = [])
    {
        $xid = uniqid('xa');

        if (empty($dbs)) {
            $dbs[] = $this;
        }

        foreach ($dbs as $key => $db) {
            if ($db instanceof BaseQuery) {
                $db = $db->getConnection();

                $dbs[$key] = $db;
            }
			
			// 问题就在这里,同一个数据库服务器,一个事务ID还没执行完毕
			// 又循环生成同样的事务ID,导致异常
            $db->startTransXa($xid);
        }

        try {
            $result = null;
            if (is_callable($callback)) {
                $result = $callback($this);
            }

            foreach ($dbs as $db) {
                $db->prepareXa($xid);
            }

            foreach ($dbs as $db) {
                $db->commitXa($xid);
            }

            return $result;
        } catch (\Exception | \Throwable $e) {
            foreach ($dbs as $db) {
                $db->rollbackXa($xid);
            }
            throw $e;
        }
    }

}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
thinkphp6中,异常处理是通过异常类来实现的。当系统出现异常时,会抛出一个异常对象,我们可以通过捕获这个异常对象来进行异常处理。以下是一个简单的异常处理示例: 1. 在控制器中抛出一个异常: ```php throw new \think\Exception('这是一个异常'); ``` 2. 在应用的异常处理类中进行异常处理: ```php namespace app\exception; use think\exception\Handle; use think\Response; use Throwable; class Http extends Handle { public function render($request, Throwable $e): Response { // 根据不同异常类型进行处理 if ($e instanceof \think\Exception) { // 返回错误信息 return json(['msg' => $e->getMessage()], 500); } // 其他异常交给系统处理 return parent::render($request, $e); } } ``` 注意,上面的代码中,我们继承了think\exception\Handle类,并重写了它的render方法。render方法接受两个参数:$request表示当前请求对象,$e表示抛出的异常对象。在这个方法中,我们可以根据不同的异常类型进行处理,并返回一个Response对象。如果我们不需要对异常进行特殊处理,可以直接调用父类的render方法,让系统进行默认的异常处理。 3. 在应用的配置文件中配置异常处理类: ```php return [ // 异常处理类 'exception_handle' => 'app\exception\Http', ]; ``` 在上面的配置中,我们将异常处理类设置为app\exception\Http。这样,在应用出现异常时,系统就会自动调用这个类的render方法进行异常处理。 除了上面的方法,thinkphp6还提供了其他的异常处理方式,比如使用自定义的异常类、使用异常监听器等。你可以根据自己的需求选择合适的方式进行异常处理

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值