Laravel 中解决分表问题

发布于:2025-08-19 ⋅ 阅读:(19) ⋅ 点赞:(0)

在开发中发现 composer 的分表扩展,packages 中的并不好用,以下是我在x项目中的一个分表 实践

<?php

namespace App\Traits;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;

trait SplitTableTrait
{
    //是否分表,默认false,即不分表
    //protected $isSplitTable = false;
    //是否自动创建分表
    //protected bool $isAutoCreate = false;

    //表
    public string $endTable;

    private string $originTable;
    private string $ym;
    private string $suffix;

    public function init($suffix = null): void
    {
        //默认原表
        $this->originTable = $this->table;
        //默认最终表
        $this->endTable = $this->table;

        $this->ym = Carbon::now()->format('Ym');

        //isSplitTable参数为true时进行分表,否则不分表
        if ($this->isSplitTable) {
            //初始化后缀,未传则默认年月分表
            $this->suffix = $suffix ?: $this->ym;
        }
        //初始化分表表名并创建
        $this->setSuffix();
    }


    /**
     * 设置表后缀, 如果设置分表后缀,可在service层调用生成自定义后缀表名,
     * 但每次操作表之前都需要调用该方法以保证数据表的准确性
     * @param $suffix
     */
    public function setSuffix($suffix = null): void
    {
        //isSplitTable参数为true时进行分表,否则不分表
        if ($this->isSplitTable) {
            //初始化后缀,未传则默认年月分表
            $this->suffix = $suffix ?: $this->ym;
        }
        $this->endTable = $this->originTable . '_' . $this->suffix;
        //调用时,创建分表,格式为 table_{$suffix}
        //未传自定义后缀情况下,,默认按年月分表格式为:b_log_202101
        //无论使用时是否自定义分表名,都会创建默认的分表,除非关闭该调用
        $this->createTable();

    }

    /**
     * 提供一个静态方法设置表后缀
     * @param string $suffix
     * @return Builder
     */
    public static function suffix(string $suffix): Builder
    {
        $instance = new static;
        $instance->setSuffix($suffix);
        return $instance->newQuery();
    }

    /**
     * 创建新的"table_{$suffix}"的模型实例并返回
     * @param array $attributes
     * @param bool $exists
     */


    /**
     * 创建分表,没有则创建,有则不处理
     */
    protected function createTable(): void
    {
        //初始化分表,,按年月分表格式为:b_log_202101
        if (!Schema::hasTable($this->endTable)) {
            DB::update("create table $this->endTable like $this->table");
        }
        $this->table = $this->endTable;

    }

    /*
     * 排序字段
     */
    protected string $orderByField;

    /**
     * 排序类型,asc:正序,desc:倒序,默认倒序
     * @var string
     */
    protected string $orderBy = 'desc';

    /**
     * 执行union all对分表的最终扥分页查询
     * @param     $queries
     * @param int $limit
     * @return array
     */
    public function dealListByUnionAllQuery($queries, int $limit = 10): array
    {
        //弹出一张表作为union的开始
        $unionQuery = $queries->shift();
        //循环剩下的表添加union
        $queries->each(function ($item) use ($unionQuery) {
            $unionQuery->unionAll($item);
        });
        //设置临时表的名称,添加临时表,顺序不能反过来,否则用关联约束会找不到表
        $endQuery =
            DB::table(DB::raw("({$unionQuery->toSql()}) as union_" . $this->originTable))
                //合并查询条件
                ->mergeBindings($unionQuery);
        if ($this->orderByField) {
            $endQuery->orderBy($this->orderByField, $this->orderBy);
        }
        return $endQuery
            //分页
            ->paginate($limit)
            ->toArray();
    }

}

实际使用场景

<?php

namespace App\Models\Scrm;

use App\Models\BaseModel;
use App\Traits\SplitTableTrait;
use DateTimeInterface;

class MessageLogModel extends BaseModel
{
    use SplitTableTrait;

    protected $table = 'message_log';

    protected $casts = [
        'message'  => 'array',
    ];


    protected bool $isSplitTable = true;


    public function __construct(array $attributes = [])
    {
        parent::__construct($attributes);
        // 初始化分表处理
        $this->init();
    }

}