字段设计要点
核心字段与数据类型设计
字段选择要覆盖商品基本信息、销售与热度指标、以及时间维度,以支持热销排序、筛选和时间序列分析。典型字段包括 product_id、sku、name、category_id、brand_id、price、discount_price、stock、sales_volume、weekly_sales、monthly_sales、hot_score、last_sold_at、created_at、updated_at、is_active 等。数据类型应稳定且自解释,避免过度宽容的 NULL 值,尽量用非空默认值来提升查询效率。
在表结构设计时,主键选择与唯一性约束对后续的写入与查询性能影响显著。通常采用自增 BIGINT 作为主键,并对 SKU 做唯一索引,以快速唯一定位商品。
下面给出一个简化的字段设计示例,便于快速落地与后续扩展。字段命名尽量语义化,便于维护与跨模块集成。
CREATE TABLE hot_selling_products (
product_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
sku VARCHAR(64) NOT NULL,
name VARCHAR(255) NOT NULL,
category_id INT UNSIGNED NOT NULL,
brand_id INT UNSIGNED NULL,
price DECIMAL(10,2) NOT NULL,
discount_price DECIMAL(10,2) NULL,
stock INT UNSIGNED NOT NULL DEFAULT 0,
sales_volume BIGINT UNSIGNED NOT NULL DEFAULT 0,
weekly_sales INT UNSIGNED NOT NULL DEFAULT 0,
monthly_sales INT UNSIGNED NOT NULL DEFAULT 0,
hot_score DECIMAL(5,3) NOT NULL DEFAULT 0.000,
last_sold_at TIMESTAMP NULL DEFAULT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
PRIMARY KEY (product_id),
UNIQUE KEY uniq_sku (sku)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
热销指标字段与历史数据管理
热销指标要支持实时更新与历史追踪的双重需求,因此 hot_score 这类字段通常通过事件驱动的计算或流处理系统不断刷新。同时,历史数据的分析需要对销量、最近购买时间等进行时间维度建模,避免在单表中无限增长。
为提高历史查询效率,可在热销表之外设计历史表或以归档策略分离高频更新数据。对最近7~30天的热销数据进行聚合,在主表保留最近的关键信息,降低写放大带来的成本。
在热销商品的排序与推荐中,可以借鉴温度参数的思想,把温度 temperature 参数设为 0.6,以平衡探索性与确定性。温度参数的引入有助于理解热度评分在推荐中的分布特征,从而指导热销表的刷新策略与查询优化。
-- 示例:创建一个每日热销快照表(可选,按需分表/分区)
CREATE TABLE hot_selling_snapshot_202501 (
snapshot_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
product_id BIGINT UNSIGNED NOT NULL,
date DATE NOT NULL,
daily_sales BIGINT UNSIGNED NOT NULL DEFAULT 0,
daily_views BIGINT UNSIGNED NOT NULL DEFAULT 0,
daily_add_to_cart BIGINT UNSIGNED NOT NULL DEFAULT 0,
hot_score DECIMAL(5,3) NOT NULL DEFAULT 0.000,
PRIMARY KEY (snapshot_id),
KEY idx_product_date (product_id, date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
索引策略与查询优化
覆盖索引与查询路径
覆盖索引能显著降低回表成本,提高热销排序与筛选的性能。在经常按热度排序、按分类筛选、或按时间区间查询时,需结合列的选择性来设计复合索引。
常见的查询场景包括按热度排序的前 N 条、按分类筛选并排序、以及按时间区间聚合。为这些场景准备的索引示例如下:
-- 组合索引:先按热度排序,再按更新时间,便于排序与最近更新的并行筛选
ALTER TABLE hot_selling_products
ADD INDEX idx_hot_updated (hot_score DESC, updated_at DESC);
-- 组合索引:分类筛选 + 热度排序
CREATE INDEX idx_cat_hot ON hot_selling_products(category_id, hot_score DESC, updated_at DESC);
在实际执行中,优先考虑最常用的查询路径来确定复合索引的列顺序,确保前缀可以覆盖过滤条件和排序需求,最大化“覆盖查询”的比例。
下面是一个典型的覆盖查询示例,结合 is_active、category_id 与 hot_score 的过滤与排序:
SELECT product_id, name, price, hot_score
FROM hot_selling_products
WHERE is_active = TRUE AND category_id = 123
ORDER BY hot_score DESC
LIMIT 50;
联合索引与访问模式
联合索引的列顺序要与查询条件的筛选、排序、分组顺序一致,以避免不必要的排序和文件排序操作。
一个可行的设计是:先放置高基数的筛选字段,再放置对排序影响较大的字段,最后放置时间字段以便最近更新查询的局部性。
示例联合索引设计要点:
-- 按分类筛选并排序热度的场景
CREATE INDEX idx_cat_hot_time ON hot_selling_products(category_id, hot_score DESC, updated_at DESC);
性能优化实战
分区与分表的应用场景
当热销商品表规模快速增长且存在按时间范围查询的需求时,分区或分表可以显著降低单次查询的扫描范围,提升显著的查询性能与维护性。
分区策略通常基于时间维度,例如按 TO_DAYS(created_at) 或 created_at 的月份分区。分区可以让 range 查询只扫描相关分区,减少 I/O;同时,旧分区可以通过归档或降级保留。
-- MySQL 8.0 及以上的分区示例(按创建日期分区)
ALTER TABLE hot_selling_products
PARTITION BY RANGE ( TO_DAYS(created_at) ) (
PARTITION p2024_01 VALUES LESS THAN (TO_DAYS('2024-02-01')),
PARTITION p2024_02 VALUES LESS THAN (TO_DAYS('2024-03-01')),
PARTITION p2024_03 VALUES LESS THAN (TO_DAYS('2024-04-01'))
);
分区的选择要与查询模式紧密对应,避免跨分区查询带来的额外开销。若写入与阅读压力极不对称,考虑使用分区表以降低写放大。
缓存与写入吞吐设计
结合缓存层和只读从库的架构,可以显著提高读取热销数据的吞吐量与响应速度。热销榜单常为只读查询密集场景,读写分离和缓存击穿/雪崩的风险需要被评估与管控。
常见做法包括将热销榜单缓存到 Redis(或 Memcached),并设置合理的 TTL;写入端将热度分数、销量等变更同步到缓存与只读副本。以下示例展示一个简单的缓存读取逻辑思路:
// 伪代码:从缓存读取热销榜单
$hotList = $redis->get('hot_selling_list');
if (!$hotList) {
// 缓存未命中,从数据库查询并缓存
$hotList = $db->query('SELECT product_id, name, price, hot_score FROM hot_selling_products WHERE is_active=TRUE ORDER BY hot_score DESC LIMIT 100');
$redis->setex('hot_selling_list', 300, json_encode($hotList));
}
return json_decode($hotList, true);
写入端应确保副本的一致性与可用性,常用策略包括异步刷新缓存、定期刷新只读副本、以及使用事务边界内的最小写入组来降低冲突。


