laravel分表需动态计算表名如users_01,db::table()和eloquent均需手动设表;join须分片键对齐,否则应用层拆查;分页禁用offset/limit,改用游标或归并。

分表后 DB::table() 查询直接报错:表不存在
因为 Laravel 默认不支持动态表名,DB::table('users') 写死的字符串无法自动路由到 users_01、users_02 这类物理表。硬编码改表名会破坏业务逻辑,且无法复用查询构造器。
- 必须把表名抽象成可计算的变量,比如
$tableName = 'users_' . $shardId; - 不能用
DB::table('users')->where(...)直接查,得先算出真实表名再传进去:DB::table($tableName)->where(...) - 注意:Eloquent 模型默认绑定固定表名,
User::query()会始终查users,不走分表逻辑 —— 所以分表场景下,模型层需绕过默认行为
Laravel 模型如何动态切换分表(不改 $table 属性)
直接改模型的 $table 是全局生效的,多请求并发时会串表;正确做法是每次查询前临时指定,且保证生命周期隔离。
- 在查询前调用
on('connection_name')不起作用,它只切连接,不切表 - 推荐用
newModelQuery()+setTable()组合:$user = (new User)->setTable('users_03')->where('id', 123)->first(); - 如果要用关联查询(如
posts),关联方法里也得动态算表名,不能依赖$this->posts()的默认实现 - 注意:
setTable()只影响当前实例,但要注意不要在作用域(Scope)或访问器(Accessor)里意外覆盖
分表键(shard key)选错导致跨表 JOIN 失效
水平分表后,JOIN users ON orders.user_id = users.id 在大多数分表方案里根本跑不通 —— 因为两个表按不同字段分片,数据物理上不在同一库/表,mysql 不支持跨物理表 JOIN。
- 唯一稳妥的方式是:JOIN 必须基于分表键对齐,比如都按
user_id分片,且分片算法一致(同模同余) - 如果订单按
order_id分,用户按user_id分,那就别 JOIN,改成应用层两次查询:User::find($userId)+Order::where('user_id', $userId)->get() - 分表键尽量选高频查询条件和关联外键,避免后期被迫“查全表”或“广播查询”
分页时 offset/limit 跨分片不准
比如查第 10001 条开始的 20 条,如果每片返回自己的第 10001–10020 条,合并后结果完全错乱 —— 这是分布式分页最典型的陷阱。
- 绝对不要用
skip(10000)->take(20)直接套在分表查询上 - 要么改用游标分页(
WHERE id > ? ORDER BY id LIMIT 20),靠主键连续性规避偏移问题 - 要么用归并排序:各分片查出足够多的数据(比如
limit 10020),应用层合并、去重、再截取,代价高但准确 - 注意:Laravel 的
paginate()方法底层就是offset/limit,分表场景下必须禁用,自己手写分页逻辑
分表不是加个中间件就能跑起来的事,真正卡住人的永远是 JOIN、分页、事务一致性这些隐含约束 —— 表名能换,SQL 习惯换不了,得从查询设计源头就按分片思维来。