PHP Sharding PDO

PHP、MySQL分库分表中间件,需要依赖PDO,PHP分库分表,支持协程(PHP, MySQL sharding library and sharding table middleware, need to rely on PDO, support coroutines.)

633
10
PHP

PHP-Sharding-PDO

PHP、MySQL分库分表中间件,需要依赖PDO,PHP分库分表,支持协程

目录

环境要求

  • PHP >= 7.2
  • Swoole >= 4.1.0 (协程环境)

安装

You can install the package via composer:

composer require lys/php-sharding-pdo

说明

(1)已支持协程,使用协程必须在主进程开启 \Swoole\Runtime::enableCoroutine();
(2)支持分片规则自定义,支持实现复杂的分片,分片规则是依赖输入的where条件或者insert插入的数据来的
(3)如果是insert匹配了多个库或者多张表就会返回false,请确认你insert插入匹配的规则只会有一个库和一张表
(4)由于MySQL不能使用滚动游标

规则匹配到多库多表分页会比较慢,优化的方案是使用一个where条件来过滤掉非必要的查询结果集,增加查询分页效率比如 where id >= 1000000 或者 where id <= 1000000

规则匹配到单库单表分页与原来的速度一样

(5)在事务处理中同时插入更新二个库或者多个库,是有一定机率(2PC提交)由于事务提交失败而导致数据不一致的,所以建议把某个维度的数据放在同一个数据库,或使用柔性事务,达到最终一致性

注意

(1)协程模式必须在主进程开启这个东西,否则会出现死锁
\Swoole\Runtime::enableCoroutine(); 
(2)协程中不能使用pdo长连接,在高并发的情况下,会出现如下异常
PHP Fatal error:  Uncaught Swoole\Error: Socket#30 has already been bound to another coroutine#2,
reading of the same socket in coroutine#4 at the same time is not allowed
(3)Replace into自增主键,并发量大的时候可能出现返回false和死锁的,所以不适合高并发项目的使用,高并发,请使用雪花算法等一些分布式主键方案
(4)非协程情况下,并且常驻内存,如workerman框架请使用如下代码释放上下文,上下文管理为单例,所以需要该方法释放单例实例,一般是在一个请求结束,或者一个任务结束,释放完上下文,请重新new Model实例才行,因为释放上下文,清理了上下文中的PDO实例,方法如下:
<?php
//上下文本身应该在一次请求结束,就要重置,本身里面的值就有时效性,比如PDO实例会超时断连
\PhpShardingPdo\Core\ShardingPdoContext::contextFreed();  

(5) 请使用当前最新版本

单元测试

git clone https://github.com/1107012776/PHP-Sharding-PDO.git

cd PHP-Sharding-PDO

composer install

(1)先要配置tests/Config/.env ,测试环境数据库连接

.env文件

[database]
host=localhost
username=root
password=testpassword
[shardingPdo]
#开启记录sql日志会影响性能
sqlLogOpen=false
sqlLogPath=sql.sql

(2)然后执行如下脚本

非协程

php vendor/bin/phpunit tests/IntegrationTest.php --filter testExecStart

协程

php vendor/bin/phpunit tests/IntegrationCoroutineTest.php --filter testExecStart

分表分库的知识可以参考这篇文章

https://blog.csdn.net/weixin_38642740/article/details/81448762

示例

详细请看tests目录

1.我们需要配置一下基本的分块规则配置类

<?php

namespace PhpShardingPdo\Test;
use PhpShardingPdo\Common\ConfigEnv;
use PhpShardingPdo\Core\ShardingTableRuleConfig;
use PhpShardingPdo\Core\InlineShardingStrategyConfiguration;
use PhpShardingPdo\Core\ShardingPdoContext;
use PhpShardingPdo\Core\ShardingRuleConfiguration;
use PhpShardingPdo\Inter\ShardingInitConfigInter;
use PhpShardingPdo\Test\Migrate\build\DatabaseCreate;
class ShardingInitConfig4 extends ShardingInitConfigInter
{
    /**
     * 获取分库分表map各个数据的实例
     * return
     */
    protected function getDataSourceMap()
    {
        return [
            'db0' => self::initDataResurce1(),
            'db1' => self::initDataResurce2(),
            'db2' => self::initDataResurce3(),
            'db3' => self::initDataResurce4(),
        ];
    }

    protected function getShardingRuleConfiguration()
    {
        //article
        $tableRule = new ShardingTableRuleConfig();
        $tableRule->setLogicTable('article');
        $tableRule->setDatabaseShardingStrategyConfig(
            new InlineShardingStrategyConfiguration('db', [
                'operator' => '%',
                'data' => [    //具体的字段和相对运算符右边的数
                    'user_id',  //字段名
                    4
                ]]));
        $tableRule->setTableShardingStrategyConfig(
            new InlineShardingStrategyConfiguration('article_', [   //插件自带使用 % 进行分片的规则
                'operator' => '%',
                'data' => [    //具体的字段和相对运算符右边的数
                    'cate_id',  //字段名
                    2
                ]]));
        $shardingRuleConfig = new ShardingRuleConfiguration();
        $shardingRuleConfig->add($tableRule);  //表1规则
        //account
        $tableRule = new ShardingTableRuleConfig();
        $tableRule->setLogicTable('account');
        $tableRule->setDatabaseShardingStrategyConfig(
            new InlineShardingStrategyConfiguration('db', 
            [  //插件自带使用 % 进行分片的规则 由于要使用匿名函数自定义分片规则,所以这边是设置一个空数组
         
            ], function ($condition) {  //匿名函数自定义分片规则
                if (isset($condition['username']) && !is_array($condition['username'])) {
                    return crc32($condition['username']) % 4;
                }
                return null;
            }));
        $tableRule->setTableShardingStrategyConfig(
            new InlineShardingStrategyConfiguration('account_', [], function ($condition) {
                return 0;
            }));
        $shardingRuleConfig->add($tableRule);  //表2规则
        //user
        $tableRule = new ShardingTableRuleConfig();
        $tableRule->setLogicTable('user');
        $tableRule->setDatabaseShardingStrategyConfig(
            new InlineShardingStrategyConfiguration('db', [], function ($condition) {
                if (isset($condition['id']) && !is_array($condition['id'])) {
                    return $condition['id'] % 4;
                }
                return null;
            }));
        $tableRule->setTableShardingStrategyConfig(
            new InlineShardingStrategyConfiguration('user_', [], function ($condition) {
                return 0;
            }));
        $shardingRuleConfig->add($tableRule);  //表3规则


        //auto_distributed
        $tableRule = new ShardingTableRuleConfig();
        $tableRule->setLogicTable('auto_distributed');
        $tableRule->setDatabaseShardingStrategyConfig(
            new InlineShardingStrategyConfiguration('db', [], function ($condition) {
                if (isset($condition['stub']) && !is_array($condition['stub'])) {
                    return $condition['stub'] % 4;
                }
                return null;
            }));
        $tableRule->setTableShardingStrategyConfig(
            new InlineShardingStrategyConfiguration('auto_distributed', [], function ($condition) {
                return '';
            }));
        $shardingRuleConfig->add($tableRule);  //表4规则

        //category
        $tableRule = new ShardingTableRuleConfig();
        $tableRule->setLogicTable('category');
        $tableRule->setDatabaseShardingStrategyConfig(
            new InlineShardingStrategyConfiguration('db', [], function ($condition) {
                return 0;
            }));
        $tableRule->setTableShardingStrategyConfig(
            new InlineShardingStrategyConfiguration('category', [], function ($condition) {
                return '';
            }));
        $shardingRuleConfig->add($tableRule);  //表5规则


        return $shardingRuleConfig;
    }


    protected static function initDataResurce1()
    {
        $dbms = 'mysql';
        $dbName = DatabaseCreate::$databaseNameMap[0];
        $servername = ConfigEnv::get('database.host', "localhost");
        $username = ConfigEnv::get('database.username', "root");
        $password = ConfigEnv::get('database.password', "");
        $dsn = "$dbms:host=$servername;dbname=$dbName;port=3306;charset=utf8mb4";
        try {
            return self::connect($dsn, $username, $password);
        } catch (\PDOException $e) {
            if (ShardingPdoContext::getCid() > -1) {
                \Swoole\Event::exit();
            }else{
                die();
            }
        }
    }

    protected static function initDataResurce2()
    {
        $dbms = 'mysql';
        $dbName = DatabaseCreate::$databaseNameMap[1];
        $servername = ConfigEnv::get('database.host', "localhost");
        $username = ConfigEnv::get('database.username', "root");
        $password = ConfigEnv::get('database.password', "");
        $dsn = "$dbms:host=$servername;dbname=$dbName;port=3306;charset=utf8mb4";
        try {
            return self::connect($dsn, $username, $password);
        } catch (\PDOException $e) {
            if (ShardingPdoContext::getCid() > -1) {
                \Swoole\Event::exit();
            }else{
                die();
            }
        }
    }

    protected static function initDataResurce3()
    {
        $dbms = 'mysql';
        $dbName = DatabaseCreate::$databaseNameMap[2];
        $servername = ConfigEnv::get('database.host', "localhost");
        $username = ConfigEnv::get('database.username', "root");
        $password = ConfigEnv::get('database.password', "");
        $dsn = "$dbms:host=$servername;dbname=$dbName;port=3306;charset=utf8mb4";
        try {
            return self::connect($dsn, $username, $password);
        } catch (\PDOException $e) {
            if (ShardingPdoContext::getCid() > -1) {
                \Swoole\Event::exit();
            }else{
                die();
            }
        }
    }


    protected static function initDataResurce4()
    {
        $dbms = 'mysql';
        $dbName = DatabaseCreate::$databaseNameMap[3];
        $servername = ConfigEnv::get('database.host', "localhost");
        $username = ConfigEnv::get('database.username', "root");
        $password = ConfigEnv::get('database.password', "");
        $dsn = "$dbms:host=$servername;dbname=$dbName;port=3306;charset=utf8mb4";
        try {
            return self::connect($dsn, $username, $password);
        } catch (\PDOException $e) {
            if (ShardingPdoContext::getCid() > -1) {
                \Swoole\Event::exit();
            }else{
                die();
            }           
        }
    }

    protected static function connect($dsn, $user, $pass, $option = [])
    {
        //$dbh = new PhpShardingPdo\Core\SPDO($dsn, $user, $pass); //初始化一个PDO对象
        //默认这个不是长连接,如果需要数据库长连接,需要最后加一个参数:array(PDO::ATTR_PERSISTENT => true) 变成这样:
        //$dbh = new \PhpShardingPdo\Core\SPDO($dsn, $user, $pass, array(\PDO :: ATTR_TIMEOUT => 30,\PDO::ATTR_PERSISTENT => true));
        $dbh = new \PhpShardingPdo\Core\SPDO($dsn, $user, $pass, $option);
        $dbh->query('set names utf8mb4;');
        return $dbh;
    }

    /**
     * 获取事务sql执行日志路径,当事务提交失败的时候会出现该日志
     * @return string
     */
    protected function getExecTransactionSqlLogFilePath()
    {
        return './execTransactionSqlLogFilePath.log';
    }
}

2.Model创建

<?php

namespace PhpShardingPdo\Test\Model;
use PhpShardingPdo\Components\SoftDeleteTrait;
use PhpShardingPdo\Core\Model;
use PhpShardingPdo\Test\ShardingInitConfig4;
Class ArticleModel extends Model
{
    use SoftDeleteTrait; //软删除需要配置这个
    protected $tableName = 'article';
    protected $tableNameIndexConfig = [
        'index' => '0,1', //分表索引 index ,号分割
        //'range' => [1,2]  //范围
    ];
    protected $shardingInitConfigClass = ShardingInitConfig4::class;
}

<?php

namespace PhpShardingPdo\Test\Model;
use PhpShardingPdo\Core\Model;
use PhpShardingPdo\Test\ShardingInitConfig4;
Class UserModel extends Model
{
    protected $tableName = 'user';
    protected $shardingInitConfigClass = ShardingInitConfig4::class;
    protected $tableNameIndexConfig = [
        'index' => '0', //分表索引 index ,号分割
        //'range' => [1,2]  //范围
    ];
}

3.基础用法

查询

<?php
$model = new \PhpShardingPdo\Test\Model\ArticleModel();
$res = $model->where(['user_id' => 2, 'cate_id' => 1])->find();
var_dump($res);
$res = $model->renew()->where(['user_id' => 2, 'cate_id' => 1])->find();
var_dump($res);
$res = $model->renew()->where(['id' => 3])->findAll();
var_dump($res);
//order by
$res = $model->renew()->order('user_id desc')->limit(100)->findAll();
var_dump($res);
var_dump($model->find());
//group by
$res = $model->renew()->field('sum(id) as total,create_time,user_id')->group('user_id')->limit(100)->findAll();
var_dump($res);
$newObj = clone $model->renew();
var_dump($newObj === $model);  //输出false
//count 查询
$count = $model->renew()->count();
var_dump($count);
$count = $model->renew()->where(['id' => ['gt', 100000]])->count('id');   //索引覆盖型查询
var_dump($count);
//in 查询
$list = $model->renew()->where(['id' => ['in', [1,2,3]]])->findAll();  
var_dump($list);
//not in 查询
$list = $model->renew()->where(['id' => ['notIn', [1,2,3]]])->findAll();  
var_dump($list);
//gt大于  egt大于等于  lt小于  elt小于等于
$list = $model->renew()->where(['id' => ['gt', 1]])->findAll(); 
var_dump($list);
//between 两者之间 相当于  id >= 100 and id <= 10000
$list = $model->renew()->where(['id' => ['between', [100, 10000]]])->findAll();  
var_dump($list);
//同一个字段多条件查询 相当于 cate_id >= 1 and cate_id <= 4 和上面的between一样
$count = $model->renew()->where([
    'cate_id' => ['egt', 1]
])->where(['article_title' => '文章1'])
->where(['cate_id' => ['elt', 4]])
->count();
$this->assertEquals($count == 4, true);
//not between  不在两者之间 相当于  id < 100 and id > 10000
$list = $model->renew()->where(['id' => ['notBetween', [100, 10000]]])->findAll();  
var_dump($list);
//neq 不等于  可以是数组,也可以单个
$list = $model->renew()->where(['id' => ['neq', [1,2,3]]])->findAll();  
var_dump($list);
$list = $model->renew()->where(['id' => ['neq', 1]])->findAll();  
var_dump($list);
//like 查询
$list = $model->renew()->where(['article_title' => ['like','某网络科技%'],'type' => 1])->findAll();  
var_dump($list);
//not like 查询
$list = $model->renew()->where(['article_title' => ['notLike','某网络科技%'],'type' => 1])->findAll();  
var_dump($list);
//findInSet 查询
$count = $model->renew()->where([
    'cate_id' => ['findInSet', 1]
])->where(['article_title' => '文章1'])
->count();
$this->assertEquals($count == 2, true);

插入

<?php
$model = new \PhpShardingPdo\Test\Model\ArticleModel();
$user = new \PhpShardingPdo\Test\Model\UserModel();
$model->startTrans(); 
$model->startTrans(); //事务嵌套
$res = $user->renew()->insert(['id' => 2,  'create_time' => date('Y-m-d H:i:s')]);
$this->assertEquals(!empty($res), true);
$res = $model->renew()->insert(['user_id' => $user->getLastInsertId(), 'article_title' => '某网络科技', 'create_time' => date('Y-m-d H:i:s')]);
$this->assertEquals(!empty($res), true);
$user->commit();
$user->commit();

更新

<?php
$model = new \PhpShardingPdo\Test\Model\ArticleModel();
$model->startTrans(); 
$res = $model->renew()->where(['id' => 3])->update(['update_time' => date('Y-m-d H:i:s')]);
var_dump($res);  //影响行数
//decr 自减
$res = $model->renew()->where(['id' => 3])->decr('is_choice', 1);
var_dump($res); //影响行数
//incr 自增
$res = $model->renew()->where(['id' => 3])->incr('is_choice', 1);
var_dump($res); //影响行数
$model->commit();

删除

<?php
$model = new \PhpShardingPdo\Test\Model\ArticleModel();
$model->startTrans();
$res = $model->renew()->where(['id' => 9])->delete();
var_dump($res);  //影响行数
$model->commit();
//强制物理删除(如果有设置软删除的话)
$model->startTrans();
$res = $model->renew()->where(['id' => 10])->delete(true);
var_dump($res);  //影响行数
$model->commit();

4.Join用法

Join只支持同个数据库的,不支持跨库

<?php
namespace PhpShardingPdo\Test;
ini_set("display_errors", "On");

error_reporting(E_ALL); //显示所有错误信息
ini_set('date.timezone', 'Asia/Shanghai');

use PhpShardingPdo\Common\ConfigEnv;
use PhpShardingPdo\Test\Migrate\Migrate;
use PhpShardingPdo\Test\Model\ArticleModel;
use PhpShardingPdo\Test\Model\UserModel;
use PHPUnit\Framework\TestCase;

$file_load_path = __DIR__ . '/../../../autoload.php';
if (file_exists($file_load_path)) {
    require_once $file_load_path;
} else {
    $vendor = __DIR__ . '/../vendor/autoload.php';
    require_once $vendor;
}

ConfigEnv::loadFile(dirname(__FILE__) . '/Config/.env');  //加载配置

/**
* @method assertEquals($a, $b)
*/
class IntegrationTest extends TestCase
{
    /**
     * join查询测试
     * php vendor/bin/phpunit tests/IntegrationTest.php --filter testJoin
     */
    public function testJoin()
    {
        $articleModel = new \PhpShardingPdo\Test\Model\ArticleModel();
        $articleModel->alias('ar');
        $cateModel = new \PhpShardingPdo\Test\Model\CategoryModel();
        $cateModel1 = clone $cateModel;
        //这边输入where条件是用来查询具体表名的,用于后续join
        $plan = $cateModel1->alias('cate')->where([
            'id' => 1 
            ])->createJoinTablePlan([
            'cate.id' => $articleModel->getFieldAlias('cate_id') //这边是on条件 用于关联
        ]);
        //plan计划失败,其实就是找不到后续要用到的具体join表名,而表名由分表规则及输入where条件决定
        $this->assertEquals(!empty($plan), true); 
        $articleModel1 = clone $articleModel;
        $list = $articleModel1->innerJoin($plan)
            ->where(['cate_id' => 1])->findAll();
        $this->assertEquals(count($list) == 2, true);
        $this->assertEquals(empty($articleModel1->sqlErrors()), true);
        $articleModel1 = clone $articleModel;
        $count = $articleModel1->innerJoin($plan)
            ->where(['cate_id' => 1])->count();
        $this->assertEquals($count == 2, true);
        $this->assertEquals(empty($articleModel1->sqlErrors()), true);
        //实行三表关联查询
        $userModel = new UserModel();  //用户表
        $articleModel1 = clone $articleModel; //文章表
        $cateModel1 = clone $cateModel;  //分类表
        $userModel1 = clone $userModel;  //用户表
        $user_id = 1;
        $catePlan = $cateModel1->alias('cate')->where(['id' => 1])->createJoinTablePlan([
            'cate.id' => $articleModel1->getFieldAlias('cate_id')
        ]);
        $articlePlan = $articleModel1->alias('ar')->where(['cate_id' => 1])->createJoinTablePlan([
            'user.id' => $articleModel1->getFieldAlias('user_id')
        ]);
        $this->assertEquals(!empty($catePlan), true);
        $this->assertEquals(!empty($articlePlan), true);
        $list = $userModel1->alias('user')->field(['user.id', 'ar.cate_id as a', 'cate.id as b'])
            ->innerJoin($catePlan)
            ->innerJoin($articlePlan)
            ->where([
                'id' => $user_id
            ])->findAll();
        $this->assertEquals(isset($list[0]['id']) && $list[0]['id'] == 1, true);
        $this->assertEquals(isset($list[0]['a']) && $list[0]['a'] == 1, true);
        $this->assertEquals(isset($list[0]['b']) && $list[0]['b'] == 1, true);
        $this->assertEquals(empty($userModel1->sqlErrors()), true);
    }

    public function testLeftJoin()
    {
        $articleModel = new \PhpShardingPdo\Test\Model\ArticleModel();
        $articleModel->alias('ar');
        $cateModel = new \PhpShardingPdo\Test\Model\CategoryModel();
        $cateModel->alias('cate');
        $articleModel1 = clone $articleModel;
        $cateModel1 = clone $cateModel;
        $plan = $cateModel1->where(['id' => 1])->createJoinTablePlan([
            'cate.id' => $articleModel1->getFieldAlias('cate_id')
        ]);
        $this->assertEquals(!empty($plan), true);
        $list = $articleModel1->field(['ar.*', 'cate.name as cate_name'])->leftJoin($plan)
            ->where([$cateModel1->getFieldAlias('id') => 1])->findAll();
        $this->assertEquals(count($list) == 2, true);
    }

    public function testRightJoin()
    {
        $articleModel = new \PhpShardingPdo\Test\Model\ArticleModel();
        $articleModel->alias('ar');
        $cateModel = new \PhpShardingPdo\Test\Model\CategoryModel();
        $cateModel->alias('cate');
        $articleModel1 = clone $articleModel;
        $cateModel1 = clone $cateModel;
        $plan = $cateModel1->where(['id' => 1])->createJoinTablePlan([
            'cate.id' => $articleModel1->getFieldAlias('cate_id')
        ]);
        $this->assertEquals(!empty($plan), true);
        $list = $articleModel1->field(['ar.*', 'cate.name as cate_name'])->rightJoin($plan)
            ->where([
                $articleModel1->getFieldAlias('cate_id') => 1,
                $articleModel1->getFieldAlias('user_id') => 1,
            ])->findAll();
        $this->assertEquals(count($list) == 1, true);
    }
    
    public function testGroupByJoin()
    {
        $articleModel = new \PhpShardingPdo\Test\Model\ArticleModel();
        $articleModel->alias('ar');
        $cateModel = new \PhpShardingPdo\Test\Model\CategoryModel();
        $cateModel->alias('cate');
        $userModel = new UserModel();  //用户表
        $userModel->alias('user');
        $articleModel1 = clone $articleModel;
        $cateModel1 = clone $cateModel;
        $userModel1 = clone $userModel;
        $user_id = 1;
        $catePlan = $cateModel1->alias('cate')->where(['id' => 1])->createJoinTablePlan([
            'cate.id' => $articleModel1->getFieldAlias('cate_id')
        ]);
        $articlePlan = $articleModel1->alias('ar')->where(['cate_id' => 1])->createJoinTablePlan([
            'user.id' => $articleModel1->getFieldAlias('user_id')
        ]);
        $this->assertEquals(!empty($catePlan), true);
        $this->assertEquals(!empty($articlePlan), true);
        $list = $userModel1->field(['user.id', 'ar.cate_id as a', 'cate.id as b'])
            ->innerJoin($catePlan)
            ->innerJoin($articlePlan)
            ->where([
                'id' => $user_id
            ])->order('user.id desc')->group('user.id')->findAll();
        $this->assertEquals(isset($list[0]['id']) && $list[0]['id'] == 1, true);
        $this->assertEquals(isset($list[0]['a']) && $list[0]['a'] == 1, true);
        $this->assertEquals(isset($list[0]['b']) && $list[0]['b'] == 1, true);
        $this->assertEquals(empty($userModel1->sqlErrors()), true);
        $articleModel1 = clone $articleModel;
        $cateModel1 = clone $cateModel;
        $userModel1 = clone $userModel;
        $catePlan = $cateModel1->alias('cate')->where(['id' => 1])->createJoinTablePlan([
            'cate.id' => $articleModel1->getFieldAlias('cate_id')
        ]);
        $articlePlan = $articleModel1->alias('ar')->where(['cate_id' => 1])->createJoinTablePlan([
            'user.id' => $articleModel1->getFieldAlias('user_id')
        ]);
        $this->assertEquals(!empty($catePlan), true);
        $this->assertEquals(!empty($articlePlan), true);
        $list = $userModel1->field(['user.id', 'ar.cate_id as a', 'cate.id as b'])
            ->innerJoin($catePlan)
            ->innerJoin($articlePlan)
            ->where([
                'id' => $user_id
            ])->joinWhereCondition([  //这边存在注入的可能,因为不会使用占位符,请确保你传入的值是安全的
                $userModel1->getFieldAlias('id') => ['neq', 'ar.cate_id'] //请传递比如 ['user.id' => 'ar.cate_id']
            ])->order('user.id desc')->group('user.id')->findAll();
        $this->assertEquals(empty($list), true);
        $this->assertEquals(empty($userModel1->sqlErrors()), true);
    }
}

5.XA用法

<?php

$articleModel = new \PhpShardingPdo\Test\Model\ArticleXaModel();
$data = [
    'article_descript' => 'xa测试数据article_descript',
    'article_img' => '/upload/2021110816311943244.jpg',
    'article_keyword' => 'xa测试数据article_keyword',
    'article_title' => $this->article_title2,
    'author' => '学者',
    'cate_id' => 3,
    'content' => '<p>xa测试数据</p><br/>',
    'content_md' => 'xa测试数据',
    'create_time' => date('Y-m-d H:i:s'),
    'update_time' => date('Y-m-d H:i:s'),
    'user_id' => $this->testUserId(),
];
$data['id'] = $this->testGetId(2);
$articleModel->startTrans($articleModel->createXid());
$res = $articleModel->renew()->insert($data);
$this->assertEquals(!empty($res), true);
$articleModel->endXa();
$this->assertEquals(empty($articleModel->sqlErrors()), true);
$articleModel->prepareXa();
$this->assertEquals(empty($articleModel->sqlErrors()), true);
$articleModel->commit();
$this->assertEquals(empty($articleModel->sqlErrors()), true);
$row = $articleModel->where(['id' => $articleModel->getLastInsertId()])->find();
$this->assertEquals(!empty($row), true);
$articleModel = new \PhpShardingPdo\Test\Model\ArticleXaModel();
$data['id'] = $this->testGetId(2);
$articleModel->startTrans($articleModel->createXid());
$res = $articleModel->renew()->where(['id' => $row['id']])->delete();
$this->assertEquals(!empty($res), true);
$res = $articleModel->renew()->insert($data);
$this->assertEquals(!empty($res), true);
$articleModel->endXa();
$this->assertEquals(empty($articleModel->sqlErrors()), true);
$articleModel->prepareXa();
$this->assertEquals(empty($articleModel->sqlErrors()), true);
$articleModel->rollback();
$this->assertEquals(empty($articleModel->sqlErrors()), true);
$row = $articleModel->where(['id' => $articleModel->getLastInsertId()])->find();
$this->assertEquals(empty($row), true);

/**
* xa 事务Recover测试 (具体看tests目录里面的测试用例)
*/
$xid = '213123123213';
$data = [
    'article_descript' => 'xa测试数据article_descript',
    'article_img' => '/upload/2021110816311943244.jpg',
    'article_keyword' => 'xa测试数据article_keyword',
    'article_title' => $this->article_title2,
    'author' => '学者',
    'cate_id' => 1,
    'content' => '<p>xa测试数据</p><br/>',
    'content_md' => 'xa测试数据',
    'create_time' => date('Y-m-d H:i:s'),
    'update_time' => date('Y-m-d H:i:s'),
    'user_id' => 1,
];
$data['id'] = $this->testGetId(2);
$articleModel = new \PhpShardingPdo\Test\Model\ArticleXaModel();
$articleModel->startTrans($xid);
$res = $articleModel->renew()->insert($data);
$this->assertEquals(!empty($res), true);
$articleModel->endXa();
$this->assertEquals(empty($articleModel->sqlErrors()), true);
$articleModel->prepareXa(); //预提交
$this->assertEquals(empty($articleModel->sqlErrors()), true);
 //强制释放实例,做断开当前PDO连接
 //发现只有断开原始xa session PDO连接,新session才能恢复使用xa commit xid 或者 xa rollback xid
\PhpShardingPdo\Core\ShardingPdoContext::contextFreed();
  
$xid = '213123123213';
$xid .= '_phpshardingpdo2';
$articleModel = new \PhpShardingPdo\Test\Model\ArticleXaModel();
$res = $articleModel->where(['user_id' => 1, 'cate_id' => 1])->recover();  //获取recover xa list
$this->assertEquals(!empty($res['list']), true);
$isset = false;
foreach ($res['list'] as $item) {
    if ($item['data'] == $xid) {
        $isset = true;
    }
}
$this->assertEquals($isset, true);
$articleModel->setXid($xid);
$res = $articleModel->commit();
$this->assertEquals($res, true);
$this->assertEquals(empty($articleModel->sqlErrors()), true);

案例

https://www.what.pub/

License

Apache-2.0

更多请关注本人的博客

https://www.developzhe.com

关于项目

如果对您有所帮助或者觉得还不错,请点个Star支持一波

参与贡献

1.可以直接fork修改然后提交合并请求

2.如果有更好的意见或者方法欢迎私下交流

联系我 (Contact WeChat)

微信

有需要可以加我微信

Page visitor counter

visitor counter