分表发+分区
1. 表结构设计
一、基本概念
- 分表:将一个大表拆分为多个小表(如
user_checkin_0、user_checkin_1),按user_id取模决定数据存到哪张表。 -
分区:将一个表中的数据按时间(如每月)分成多个物理存储块,但逻辑上仍是一张表,分区是数据库层的功能。
二、表结构设计
-- 用户签到记录表(按用户ID分表)
CREATE TABLE user_checkin_%s (
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '主键',
user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID',
checkin_date DATE NOT NULL COMMENT '签到日期(格式:YYYY-MM-DD)',
created_at INT NOT NULL DEFAULT 0 COMMENT '创建时间',
PRIMARY KEY (id, checkin_date), -- 主键包含 checkin_date
UNIQUE KEY udx_user_date (user_id, checkin_date),
KEY idx_date (checkin_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
PARTITION BY RANGE (TO_DAYS(checkin_date)) (
PARTITION p202301 VALUES LESS THAN (TO_DAYS('2023-02-01')),
PARTITION p202302 VALUES LESS THAN (TO_DAYS('2023-03-01')),
...
);
2、hyperf框架设置
一、数据库配置 config/autoload/databases.php
return [
'default' => [
'driver' => Hyperf\DB\DB::class,
'sharding' => [
'enable' => true,
'rules' => [
'user_checkin' => [ // 逻辑表名
'shard_key' => 'user_id',
'algorithm' => 'mod', // 取模算法
'num' => 3, // 分3张表(示例)
'format' => 'user_checkin_%s', // 物理表名格式
],
],
],
],
];
二、 模型类 app/Model/UserCheckin.php
namespace App\Model;
use Hyperf\DbConnection\Model\Model;
class UserCheckin extends Model
{
// 逻辑表名(对应配置中的 user_checkin)
protected $table = 'user_checkin';
// 动态选择分表
public function getTable()
{
$userId = $this->user_id ?? 0;
$suffix = $userId % 3; // 分3张表,实际用1024
return "user_checkin_{$suffix}";
}
}
3、手动或定时任务创建分区
每个月一号的时候定时任务去操作 自动分区
一、代码定时任务创建分区
编辑命令 app/Command/PartitionCommand.php
namespace App\Command;
use Hyperf\Command\Command as HyperfCommand;
use Hyperf\DbConnection\Db;
use Psr\Container\ContainerInterface;
class PartitionCommand extends HyperfCommand
{
protected $name = 'partition:maintain';
public function handle()
{
// 下个月1号的日期(如 2023-04-01)
$nextMonth = date('Y-m-01', strtotime('+1 month'));
$partitionName = 'p' . date('Ym', strtotime($nextMonth));
// 为每个分表添加分区
for ($i = 0; $i < 3; $i++) { // 示例3张分表,实际用1024
$table = "user_checkin_{$i}";
$sql = "ALTER TABLE {$table} ADD PARTITION (
PARTITION {$partitionName} VALUES LESS THAN (TO_DAYS('{$nextMonth}'))
)";
Db::statement($sql);
$this->info("为表 {$table} 添加分区 {$partitionName}");
}
}
}
注册命令 config/autoload/commands.php
return [
App\Command\PartitionCommand::class,
];
设置定时任务(每月1号执行)
# 编辑 Crontab
crontab -e
# 添加任务(每月1号 00:05 执行)
5 0 1 * * cd /path/to/project && php bin/hyperf.php partition:maintain
4、查询sql
一、纯 SQL 查询语句
-- 假设用户ID为 12345,分表为 user_checkin_313(12345 % 1024 = 313)
-- 查询 2023-10 月的签到记录
SELECT *
FROM user_checkin_313
WHERE
user_id = 12345
AND checkin_date BETWEEN '2023-10-01' AND '2023-10-31';
二、Hyperf 框架动态查询实现
namespace App\Model;
use Hyperf\DbConnection\Model\Model;
class UserCheckin extends Model
{
protected $table = 'user_checkin'; // 逻辑表名
// 动态设置分表名
public function getTable()
{
$userId = $this->user_id ?? 0;
$suffix = $userId % 1024; // 分表数量 1024
return "user_checkin_{$suffix}";
}
}
查询最近一个月签到记录
// 获取当前月份的第一天和最后一天
$startDate = date('Y-m-01'); // 本月第一天(如 2023-10-01)
$endDate = date('Y-m-t'); // 本月最后一天(如 2023-10-31)
// 执行查询(假设用户ID为 12345)
$userId = 12345;
$records = UserCheckin::query()
->where('user_id', $userId)
->whereBetween('checkin_date', [$startDate, $endDate])
->get();
// 输出结果
foreach ($records as $record) {
echo $record->checkin_date . ' : ' . $record->points . PHP_EOL;
}
三、执行流程解析
-
分表路由:
Hyperf 根据user_id计算分表后缀(如user_checkin_313),自动拼接物理表名。 -
分区剪裁:
MySQL 根据checkin_date BETWEEN '2023-10-01' AND '2023-10-31'自动定位到p202310分区,跳过其他分区。 -
查询优化:
-
索引建议:在
user_id和checkin_date上创建复合索引,加速查询:ALTER TABLE user_checkin_313 ADD INDEX idx_user_date (user_id, checkin_date);
-
四、验证查询效率
1. 查看执行计划
// 在 Hyperf 中获取 SQL 执行计划
$explain = UserCheckin::query()
->where('user_id', 12345)
->whereBetween('checkin_date', [$startDate, $endDate])
->explain()
->get();
// 输出结果
dd($explain);
预期结果:
-
partitions列显示p202310(仅扫描目标分区)。 -
type列为range,表示高效的范围查询。
2. 日志验证
检查 Hyperf 的 SQL 日志,确认实际查询的表名和条件:

浙公网安备 33010602011771号