本文目录导读:

在PHP项目中实现置顶功能,通常有数据库标记法和排序值法两种主流方案,下面我会给出最实用、可扩展性强的实现方式。
使用 is_top 字段 + 双排序(推荐)
这是最常用且性能较好的方式,适合文章、帖子、产品等列表的置顶。
数据库设计
在原有数据表(如 articles、posts)中添加一个字段:
ALTER TABLE `articles` ADD COLUMN `is_top` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否置顶:0=否,1=是', ADD COLUMN `top_sort` INT NOT NULL DEFAULT 0 COMMENT '置顶排序值(越大越靠前)', ADD INDEX `idx_top_list` (`is_top`, `top_sort`);
is_top:标记是否置顶。top_sort:当有多个置顶项时,控制它们之间的顺序(如 100、200 等,方便插入新置顶)。
PHP 查询代码
// 获取列表时,置顶的始终排在前面,按 top_sort 降序,再按正常排序
$sql = "SELECT * FROM articles
ORDER BY is_top DESC, top_sort DESC, created_at DESC
LIMIT 20";
$result = $db->query($sql);
原理:is_top DESC 保证置顶的(1)排在非置顶(0)前面。top_sort DESC 让置顶内部可排序,created_at DESC 作为普通排序依据。
置顶/取消置顶操作
// 置顶某篇文章
function setTop($articleId, $sortValue = 100) {
$sql = "UPDATE articles SET is_top = 1, top_sort = ? WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$sortValue, $articleId]);
}
// 取消置顶
function unsetTop($articleId) {
$sql = "UPDATE articles SET is_top = 0, top_sort = 0 WHERE id = ?";
$stmt = $pdo->prepare($sql);
$stmt->execute([$articleId]);
}
单独维护 top_list 表(适合复杂场景)
如果置顶逻辑复杂(如不同分类有不同置顶、置顶有时间限制),推荐用独立表。
新建置顶表
CREATE TABLE `top_list` (
`id` INT AUTO_INCREMENT PRIMARY KEY,
`item_type` VARCHAR(50) NOT NULL COMMENT '类型:article, product, topic',
`item_id` INT NOT NULL COMMENT '对应业务表的ID',
`group_key` VARCHAR(100) DEFAULT NULL COMMENT '分组键(如分类ID)',
`sort_val` INT NOT NULL DEFAULT 0 COMMENT '排序值',
`created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
`expire_at` DATETIME DEFAULT NULL COMMENT '置顶过期时间',
UNIQUE KEY `uk_item` (`item_type`, `item_id`)
);
查询逻辑(使用 UNION)
// 先查询置顶项
$topIds = getTopItemIds('article', $categoryId); // 返回 [101, 205, 33]
// 获取置顶记录
$topItems = [];
if (!empty($topIds)) {
$placeholders = implode(',', array_fill(0, count($topIds), '?'));
$sql = "SELECT * FROM articles WHERE id IN ($placeholders)";
$stmt = $pdo->prepare($sql);
$stmt->execute($topIds);
$topItems = $stmt->fetchAll();
// 按 top_list 的 sort_val 顺序重新排序
$order = array_flip($topIds);
usort($topItems, fn($a, $b) => $order[$a['id']] <=> $order[$b['id']]);
}
// 获取非置顶记录(分页时注意排除)
$excludeIds = $topIds ?: [0];
$placeholders = implode(',', array_fill(0, count($excludeIds), '?'));
$sql = "SELECT * FROM articles
WHERE id NOT IN ($placeholders)
ORDER BY created_at DESC
LIMIT 20";
$stmt = $pdo->prepare($sql);
$stmt->execute($excludeIds);
$normalItems = $stmt->fetchAll();
// 合并结果
$list = array_merge($topItems, $normalItems);
前端伪置顶 + 服务端配合
适合实时性要求不高且数据量小的场景(如个人博客、小型CMS)。
后端返回所有数据时标记置顶
// 查询全部数据(不考虑分页),或者分页查询后手动合并
$sql = "SELECT *, is_top FROM articles ORDER BY is_top DESC, created_at DESC";
$result = $query->fetchAll();
// 返回给前端时,前端根据 is_top 字段决定展示样式
return json_encode([
'list' => $result,
'top_ids' => array_column(array_filter($result, fn($v) => $v['is_top'] == 1), 'id')
]);
前端代码(Vue 示例)
<template>
<div v-for="item in sortedList" :key="item.id" :class="{ 'top-item': item.is_top }">
{{ item.title }}
</div>
</template>
<script>
export default {
computed: {
sortedList() {
return this.list.sort((a, b) => b.is_top - a.is_top || b.created_at - a.created_at);
}
},
data() {
return { list: [] };
}
}
</script>
四种实现方案的对比
| 方案 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|
| 字段标记 + 双排序 | 简单高效,SQL 内置 | 管理多分类置顶时需扩展 | 单表业务,置顶数量少 |
| 独立置顶表 | 灵活,支持多类型+过期 | 查询需 UNION 或多次查询 | 多业务表、分组置顶、有时效性 |
| 前端伪置顶 | 后端改动小,适合轻量项目 | 分页困难,大数据量性能差 | 个人博客、后台管理 |
| Redis 缓存置顶列表 | 性能极高,适合高并发 | 需额外维护缓存一致性 | 新闻门户、BBS 首页 |
最佳实践建议
- 简单项目:直接用
is_top + top_sort字段,SQL 中ORDER BY is_top DESC, top_sort DESC, created_at DESC。 - 多分类项目:使用独立
top_list表,通过group_key区分不同分类的置顶。 - 高并发场景:将置顶 ID 列表缓存到 Redis(Set 或 ZSet),查询时先取缓存再查 DB。
- 注意分页:如果使用字段标记法,置顶项会始终出现在第一页,需要统计总页数时注意:总记录数 = 置顶数 + 普通条目总数。
如果需要针对具体业务(如文章置顶、商品置顶、多分类置顶)给出更详细的代码示例,可以告诉我你的具体场景。