laravel分库分表同一模型能否实现联查?

1. 运行环境

1). 当前使用的 Laravel 版本?

5.7.29

2). 当前使用的 php/php-fpm 版本?

PHP 版本:7.3.4

2. 问题描述?

目前model是动态链接指定分库查询的,现在想请教一下各位大牛,能否在模型中设置所有分库做联查并分页显示

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class UserCoinWalletFlowExt extends Model
{
  protected $connection = 'mysqlRecord'; //库名
  public function __construct($user_id = 0)
 {
     //根据用户id 分配
      parent::__construct();
      $this->table ='user_coin_wallet_flow'; //连接表
      $this->connection =$this->connection.$user_id % 10; //连接库
  }
}

尝试过使用这样组合sql语句的方法,但是因为模型是动态链接分库的,获取到的值并不符合预期结果

// 查询集合
$queries = collect();
for ($i = 0; $i < 10; $i++) {
    $connection = 'mysqlRecord'.$i;
    $table = 'user_coin_wallet_flow';
    $queries->push(
    DB::connection($connection)->table($table)
    ->select('id', 'flow_id', 'user_id', 'currency_id','in_out','biz_type','biz_sub_type','biz_detail','remark','create_date')
    ->where(['biz_type' => 1, 'biz_sub_type' => 1009]));
}

// 出列一张表作为union的开始
$unionQuery = $queries->shift();
// 循环剩下的表添加union
$queries->each(function ($item) use ($unionQuery) {
    $unionQuery->unionAll($item);
});

$lists = with(new UserCoinWalletFlowExt(0))->setTable('test')
->from(DB::raw("({$unionQuery->toSql()}) as test"))
->mergeBindings($unionQuery)
->orderBy('create_date', 'desc')
->get();

业务逻辑:联查所有流水表将符合条件的数据分页展示

请教各位大牛有更好的解决方案吗或者以上方法有什么优化的吗?

《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
最佳答案

目前改掉了循环查询的代码了,还是乖乖用原生sql查询,循环拼凑sql语句用union拼接起来,然后直接排序和分页,个人感觉一条查询总比10条查询好些吧。一开始使用laravel的union连接sql语句无效是因为打印出来看到分段指定的sql语句并没有连接到指定的分库,所以怎么union都只在一个库查。几位大佬说的架构建议确实更佳,无奈项目就这样了

2年前 评论
讨论数量: 20

坐等大佬的解决方案

2年前 评论

目前解决方案,略显粗糙且比较粗暴,坐等大佬们的意见

$data = [];
for($i = 0; $i < 10; $i++){
   $data[] = (new UserCoinWalletFlowExt($i))->select($select)->where($where)->get();
}
2年前 评论
candivan 2年前
chowjiawei

不同库的结果先查出来 使用php 交集 并集 去重等方法去取数据 这样子快速 锻炼你的逻辑能力 mysql连表查询 改成 查2次简单的表 用php去解决 在慢查询领域有奇效

比如你联表查询 要1分钟 分别查2次 各10秒 只要20秒 php的处理速度 在来个1秒足够 就是21秒 省下39秒

2年前 评论
温柔的閪佬 (楼主) 2年前
chowjiawei (作者) 2年前
ab0029 2年前
chowjiawei (作者) 2年前
ab0029 2年前
chowjiawei (作者) 2年前
ab0029 2年前

可以使用这个试试呗

file

2年前 评论
温柔的閪佬 (楼主) 2年前
leihuo_emoji (作者) 2年前

我们用的dbproxy 做的分表分库,但是都分库分表了关联查询就有点反逻辑了,直接用tidb吧

2年前 评论

建议换数据库吧,分库分表已经是过去时了,分库分表就是反人类的。采用云原生分布式数据库,无需关心数据如何存储,单表几亿几百亿都没问题,分布式数据库有很多,例如:阿里云OceanBase ,华为云GaussDB,TIDB 等

2年前 评论

目前改掉了循环查询的代码了,还是乖乖用原生sql查询,循环拼凑sql语句用union拼接起来,然后直接排序和分页,个人感觉一条查询总比10条查询好些吧。一开始使用laravel的union连接sql语句无效是因为打印出来看到分段指定的sql语句并没有连接到指定的分库,所以怎么union都只在一个库查。几位大佬说的架构建议确实更佳,无奈项目就这样了

2年前 评论

这是满足我们自己查询需求的一个按照年月分表的 scope, 你可以参考一下 思路一般来说就是按照规则生成好 sql 就可以了

<?php

namespace App\Models\Scopes;

use Carbon\Carbon;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Scope;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;

// 按照日期分表的作用域
class DateScope implements Scope{

    public function apply(Builder $builder, Model $model){

        $splitType = isset($model->splitType) ? $model->splitType : ''; // 获取设置在 model 中的日期字段
        $modelTable = $model->getTable();
        $wheres = $builder->getQuery()->wheres;

        // 从 where 中获取 日期 条件
        foreach($wheres as $where){
            if(isset($where['column']) && $where['column'] === $splitType){
                $splitValue = $where['values'];
                break;
            }
        }

        $startDate = Carbon::parse($splitValue[0]);
        $endDate = Carbon::parse($splitValue[1]);

        $queries = collect();
        $connection = $model->getConnectionName();

        for($i = $startDate; $i->format('Ym') <= $endDate->format('Ym'); $i->addMonth() ){
            $table = $modelTable . '_' . $i->format('Ym');
            $db = DB::connection($connection)->table($table);

            // 拼接 where 条件, 目前处理了 between, in, = 三种条件
            foreach($wheres as $where){

                if($where['type'] == 'between'){
                    $db->whereBetween($where['column'], $where['values'], $where['boolean'], $where['not']);
                }else if($where['type'] == 'In'){
                    $db->whereIn($where['column'], $where['values'], $where['boolean']);
                }else if($where['type'] == 'NotIn'){
                    $db->whereNotIn($where['column'], $where['values'], $where['boolean']);
                }else if($where['type'] == 'Basic'){
                    $db->where($where['column'],$where['operator'], $where['value'], $where['boolean']);
                }else if($where['type'] == 'Nested'){
                    $db->addNestedWhereQuery($where['query'], $where['boolean']);
                }else if($where['type'] == 'Exists'){
                    $exists = $where['query'];
                    $db->whereExists(function($query) use($exists, $modelTable, $table){
                        $wheres = $exists->wheres;
                        $column = $wheres[0];
                        // 替换表为对应带日期后缀的表
                        $column['first'] = str_replace($modelTable, $table, $column['first']);
                        $query->select(DB::raw(1))
                            ->from($exists->from)
                            ->whereRaw("{$column['first']} {$column['operator']} {$column['second']}");
                        // 添加额外的补充条件, 目前只加一条
                        if(isset($wheres[1])){
                            $query->where($wheres[1]['column'], $wheres[1]['operator'], $wheres[1]['value'], $wheres[1]['boolean']);
                        }
                    });
                }
            }
            $queries[] = $db;
        }

        $unionQuery = $queries->shift();
        $queries->each(function($item, $key) use ($unionQuery){
            $unionQuery->unionAll($item);
        });
        $sql = $builder->from(DB::raw("({$unionQuery->toSql()}) as {$modelTable}"))
        ->mergeBindings($unionQuery);
        //dd($sql);
        return $sql;
    }

}
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use App\Models\Scopes\DateScope;

class YourModel extends Model
{

    public $splitType = 'DT_DATE';
    /**
     * 模型的「引导」方法。
     *
     * @return void
     */
    protected static function booted()
    {
        static::addGlobalScope(new DateScope);
    }
}
2年前 评论

借楼做下笔记

   $user_id = auth()->user()->id;

    $articles = null;

    // 循环查询符合规则的表并进行联合查询
    for ($i = 0; $i < 10; $i++) {
        $table_name = 'articles_' . $i;

        $query = DB::table($table_name)
            ->where('user_id', $user_id) // 根据用户ID过滤文章
            ->orderBy('created_at', 'desc'); // 按创建时间倒序排列

        if ($articles === null) {
            $articles = $query;
        } else {
            $articles = $articles->union($query);
        }
    }

    // 对合并后的结果进行分页
    $articles = $articles->paginate(10);


1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!