滚动新闻 →
什么情况下应该使用快速剪辑 辛亥革命为什么能够推翻清朝 伊南大爆炸!1小时轰塌38桥?黄出轨细节曝光! 台湾主持人郑弘仪揭秘父母长寿秘诀 三要素助力健康长寿 黄仁勋婚外情牵出共谍疑云 嫡系公司涉中共国资调查 陕西司机驾驶挖掘机冲击政府大楼 网民称其为英雄 霍建华女儿痴迷韩星引吃醋,自称‘上古帅哥’遭吐槽 阳台空间除了晾衣服还能怎么利用 奈良古寺旁的柿叶寿司 美军组建铁门特遣队阻断伊朗陆路通道 川普称战事已获突破 杭州万达广场疑似发生持枪抢劫案 民众披露更多细节 坐在加拿大金三角温泉的露天池里,看群山静静站在远方 王健林时隔14年重返万达商管董事长职位 中国男子东京银座涉嫌干扰通讯发送钓鱼短信 燃油泵异响应该如何判断 黄渤坦言无戏可拍 面临双重现实困境 山西太原高楼大火 至少14人死亡 高雄前镇发现缠附哑铃骨骸 疑为连环杀人案受害者 车道保持辅助到底有什么作用 桃园警方拦查可疑厢型车 7名移工跳车4人被捕3人逃逸 秦皇岛遭罕见特大暴雪袭击 深厚积雪画面曝光 加拿大投资房应该如何计算真实租金收入 塞内加尔海域非法移民船难致34人遇难 搜救持续 为什么长期投资需要耐心 民主党拟对马斯克及白宫幕僚长展开调查 黄仁勋婚外情牵涉间谍调查 川普马斯克回应 川普承认移民政策过于严苛 愿与民主党协商 二手游艇内部设备应该如何检查 新竹县议会成全台第18个通过反跨境镇压提案的县市议会 伊拉克霍尔莫尔气田遭无人机袭击 沙特阿美大楼遇空袭 电影项目如何寻找导演 张艺谋警示AI冲击电影业 中共新政引发演员失业担忧 美军特遣队封锁伊朗陆路 川普证实行动 中国中东部8省市遭遇强降雪 局地降温超14℃ 英伟达启动独立调查 黄仁勋被指涉华情报疑云 河南老君山现罕见气候奇观 一山分隔冬夏景色 加拿大为什么报税后反而需要补税 司法部或对民主党恐吓行为展开调查 美国公民权利法从哪里开始 川普:对伊朗全面开战需满足美方条件

MySQL 表应该怎么设计?从一个简单新闻网站建立数据库结构

发布时间: 2026-10-19 07:24:02    最后更新: 2026-11-08 21:32:29    阅读:97  约20 分钟阅读     

MySQL 表应该怎么设计?从一个简单新闻网站建立数据库结构
图片说明:示意图   图片来源:Public Domain(公有领域)
一个新闻网站刚开始开发时,数据库往往只有几张表:用户、文章、分类和评论。几十篇文章时,这样的设计几乎不会暴露问题,但当文章数量增长到几十万甚至几百万条,访问量增加,后台查询开始涉及发布时间、分类、发布状态、作者、浏览量和搜索时,最初看起来“能用”的数据库结构,很容易变成性能瓶颈。

因此,MySQL数据库设计真正需要考虑的,并不是把所有字段简单地塞进几张表,而是提前确定数据之间的关系、查询方式、索引结构以及未来的数据增长路径。一个好的新闻数据库应该让最常见的查询能够直接命中合适的索引,同时避免为了所谓“规范化”制造大量不必要的JOIN。

最基本的新闻系统通常可以拆成users、articles、categories和comments四类核心数据。用户表负责身份和账户信息,文章表负责正文及发布状态,分类表负责内容组织,评论表负责用户与文章之间的互动。

用户表可以设计为:

CREATE TABLE users (
user_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password_hash VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
last_login_at DATETIME NULL,
is_active TINYINT UNSIGNED NOT NULL DEFAULT 1,

PRIMARY KEY (user_id),
UNIQUE KEY uk_username (username),
UNIQUE KEY uk_email (email)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里有几个细节值得注意。密码字段不应该命名为password,而应该明确表示它存储的是password_hash。数据库只保存经过安全密码哈希算法处理后的结果,绝不能保存明文密码。VARCHAR(255)通常足以容纳现代密码哈希格式。

user_id是否一定要使用INT,也没有绝对答案。小型网站使用INT UNSIGNED可以容纳约42亿个非负整数ID,对于普通网站已经绰绰有余。如果预计数据规模很大,或者多个系统需要统一ID空间,则可以考虑BIGINT UNSIGNED。需要注意的是,BIGINT并不会让查询自动变快,它只是提供更大的数值范围,同时会增加索引和存储空间,因此应该根据实际规模选择。

文章表才是新闻网站数据库的核心。一个比较实用的结构可以是:

CREATE TABLE articles (
article_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
content LONGTEXT NOT NULL,
author_id BIGINT UNSIGNED NULL,
category_id BIGINT UNSIGNED NULL,
status TINYINT UNSIGNED NOT NULL DEFAULT 0,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
ON UPDATE CURRENT_TIMESTAMP,
published_at DATETIME NULL,

PRIMARY KEY (article_id),
KEY idx_category_published (category_id, published_at, article_id),
KEY idx_status_published (status, published_at, article_id),
KEY idx_author_created (author_id, created_at, article_id),

CONSTRAINT fk_article_author
FOREIGN KEY (author_id)
REFERENCES users(user_id),

CONSTRAINT fk_article_category
FOREIGN KEY (category_id)
REFERENCES categories(category_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里没有急着使用ENUM('draft','published','archived'),而是使用数值状态。例如0代表草稿、1代表发布、2代表归档。ENUM并非不能使用,但状态值未来经常会发生变化,例如增加scheduled、deleted、pending_review等状态时,数值状态或者独立的状态表通常更容易扩展。

文章表还有一个容易被忽略的字段:published_at。

created_at表示文章记录什么时候进入数据库,而published_at表示文章什么时候真正公开。新闻系统中这两个时间经常并不相同。编辑可能今天创建文章,明天才发布;定时发布系统也可能提前几小时甚至几天写入数据库。如果首页按照created_at排序,就可能出现文章还没有发布却被错误排在列表中的情况。

因此,新闻网站常见的查询应该类似:

SELECT article_id, title, published_at
FROM articles
WHERE status = 1
ORDER BY published_at DESC, article_id DESC
LIMIT 50;

这里的article_id作为第二排序条件非常重要。当大量文章拥有相同的published_at时,仅仅按照时间排序可能导致分页结果不稳定。加入唯一递增ID之后,排序关系就更加确定。

分类表相对简单:

CREATE TABLE categories (
category_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) NOT NULL,
description TEXT NULL,

PRIMARY KEY (category_id),
UNIQUE KEY uk_category_name (name),
UNIQUE KEY uk_category_slug (slug)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

如果分类名称需要显示给用户,而slug用于URL,那么两者最好分开。比如分类名称可以是“美国新闻”,slug则可以是usa-news。以后网站修改显示名称时,不一定需要同时改变URL结构。

评论表则属于典型的一对多关系。一篇文章可以拥有大量评论,一个用户也可以发表大量评论。

CREATE TABLE comments (
comment_id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
article_id BIGINT UNSIGNED NOT NULL,
user_id BIGINT UNSIGNED NULL,
content TEXT NOT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
status TINYINT UNSIGNED NOT NULL DEFAULT 0,

PRIMARY KEY (comment_id),
KEY idx_article_created (article_id, created_at, comment_id),
KEY idx_user_created (user_id, created_at),

CONSTRAINT fk_comment_article
FOREIGN KEY (article_id)
REFERENCES articles(article_id)
ON DELETE CASCADE,

CONSTRAINT fk_comment_user
FOREIGN KEY (user_id)
REFERENCES users(user_id)
ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里的ON DELETE策略不能机械地全部设置成CASCADE。删除一篇文章时,如果评论没有独立保存价值,可以让评论随文章删除;但删除用户时,历史评论往往仍然具有内容价值,因此把user_id设置成NULL通常更加合理。这样既删除了用户关联,又不会把历史评论一起删除。

索引是新闻数据库设计中最容易被误解的部分。很多开发者认为“经常查询的字段都应该建立索引”,结果一个表建立十几个甚至几十个索引。索引并不是免费的。每增加一个索引,INSERT、UPDATE和DELETE都需要维护更多索引结构,同时占用磁盘和内存。

真正应该建立的是与实际查询模式匹配的索引。

例如新闻首页最常见的查询可能是:

WHERE status = 1
ORDER BY published_at DESC

那么:

KEY idx_status_published (status, published_at, article_id)

就比单独给status和published_at分别建立索引更有针对性。

如果分类页面经常执行:

WHERE category_id = 10
AND status = 1
ORDER BY published_at DESC

则需要根据实际查询条件设计复合索引,而不是简单地认为“category_id已经有索引了”。索引列的排列顺序必须结合WHERE、ORDER BY和选择性进行判断。

另外,所谓“一个表最多5个索引”并不是MySQL规则。真正合理的索引数量取决于表结构、写入频率、查询模式、数据分布以及索引大小。有的表三个索引已经很多,有的复杂业务表十几个索引也可能合理。最终应该通过真实SQL和EXPLAIN进行验证,而不是死记一个数字。

新闻网站还有一个经常被低估的问题:分页。

最简单的分页方式是:

SELECT article_id, title, published_at
FROM articles
WHERE status = 1
ORDER BY published_at DESC, article_id DESC
LIMIT 100000, 20;

数据量小时没有感觉,但当offset达到几十万甚至几百万时,数据库可能需要扫描并跳过大量记录。对于不断向下加载新闻列表的网站,更适合考虑基于游标的分页,也就是Keyset Pagination。

例如上一页最后一条记录是:

published_at = '2026-09-20 18:30:00'
article_id = 98231

下一页可以查询:

SELECT article_id, title, published_at
FROM articles
WHERE status = 1
AND (
published_at < '2026-09-20 18:30:00'
OR (
published_at = '2026-09-20 18:30:00'
AND article_id < 98231
)
)
ORDER BY published_at DESC, article_id DESC
LIMIT 20;

这种方式尤其适合新闻流、日志、订单等按照时间持续增长的数据。

数据类型也不能只看“能不能存”。标题通常使用VARCHAR,正文根据最大长度选择TEXT、MEDIUMTEXT或者LONGTEXT。绝大多数普通文章使用TEXT或MEDIUMTEXT就足够,只有非常长的正文才需要LONGTEXT。数据库字段允许存储多大,并不意味着业务层就应该允许用户提交那么大的内容。

字符集则建议使用utf8mb4,而不是早期的utf8。MySQL历史上的utf8实际上并不是完整的Unicode实现,而utf8mb4能够正确处理更完整的Unicode字符,包括许多Emoji和扩展字符。

图片也不一定应该直接塞进MySQL的BLOB字段。新闻网站通常更适合把图片放在文件系统、对象存储或CDN中,数据库只保存URL、对象Key、文件尺寸、MIME类型以及相关元数据。数据库擅长管理结构化数据,不代表所有二进制文件都应该放进数据库。

同样,浏览量也值得单独考虑。如果每一次页面访问都执行:

UPDATE articles
SET views = views + 1
WHERE article_id = ?;

在高访问量网站上,大量请求可能集中更新同一批热门文章,造成额外的数据库写压力。低流量网站完全可以这么做,但流量增长后,可以考虑Redis计数、异步批量回写或者独立统计表,把“文章内容数据”和“高频变化的访问统计数据”分开处理。

全文搜索也不应该简单理解成给title建立普通B-tree索引。普通索引适合精确匹配、范围查询以及排序等场景,但对于“搜索文章正文中包含某个关键词”的需求,需要考虑MySQL FULLTEXT,或者在搜索规模进一步扩大后使用专门的搜索系统。

例如:

ALTER TABLE articles
ADD FULLTEXT KEY ft_title_content (title, content);

然后根据具体数据库版本和语言分词能力设计MATCH ... AGAINST查询。不过中文搜索又涉及分词问题,新闻网站规模较大后,MySQL全文搜索是否足够,需要根据实际搜索需求测试,而不能仅仅因为有FULLTEXT就认为搜索系统已经解决。

外键同样不能简单地理解成“所有关系都必须加”。InnoDB支持外键约束,它能够防止文章指向不存在的作者、评论指向不存在的文章等数据完整性问题。但在超大规模系统、跨数据库架构、分库分表环境中,外键可能增加运维和迁移复杂度。因此,小型和中型单库新闻网站通常可以积极使用外键;当系统进入分布式架构后,则需要重新评估。

存储引擎方面,现代MySQL新闻网站通常直接使用InnoDB。过去有人建议“只读表使用MyISAM”,但对于今天的新项目,这种建议已经没有太大必要。InnoDB具备事务、崩溃恢复、行级锁和更完整的数据一致性机制,通常是新闻CMS的默认选择。

数据库优化也不能靠“SELECT *”这一条规则解决所有问题。真正有效的方法是先找到慢SQL,再通过EXPLAIN观察执行计划,检查是否使用了预期索引、扫描行数是否过大、是否发生filesort、temporary table或者不必要的回表,然后再修改索引和SQL。

例如:

EXPLAIN
SELECT article_id, title, published_at
FROM articles
WHERE status = 1
ORDER BY published_at DESC, article_id DESC
LIMIT 20;

不要看到EXPLAIN里的某一个字段就立即下结论。需要结合表的数据量、索引基数、实际执行时间以及查询频率综合判断。一个查询在一万行数据上运行0.5毫秒,并不值得为了理论上的优化增加复杂索引;同一个SQL到了五千万行数据上,情况可能完全不同。

随着新闻网站发展,数据库通常还会增加tags、article_tags、media、revisions、scheduled_posts、likes、bookmarks以及访问统计等表。这时候最重要的不是不断往articles表里添加字段,而是把具有独立生命周期的数据拆出来。

例如文章标签就是典型的多对多关系:

articles
|
| 1:N
|
article_tags
|
| N:1
|
tags

这种结构比在articles表中保存一个逗号分隔的“科技,AI,服务器,数据库”字符串更加可靠,也更容易建立索引和进行统计查询。

真正成熟的新闻数据库,还应该考虑数据归档和备份。当文章数量达到数百万甚至更高时,数据库性能问题往往不再来自某一个SQL,而是来自数据规模、索引体积、备份时间、磁盘IO以及历史数据不断增长。此时可以根据业务需求进行冷热数据分离、归档旧文章、优化索引、增加只读副本,甚至进一步考虑分库或分表。

备份也不能停留在“每天执行一次mysqldump”。真正可靠的数据库备份体系应该考虑全量备份、增量或日志恢复、异地保存、备份完整性验证以及实际恢复演练。一个从来没有测试过恢复流程的“备份”,严格来说只是一个希望它将来能用的文件。

一个简单新闻网站最容易犯的错误,就是一开始过度设计,把数据库弄成几十张表;另一个极端则是所有内容全部塞进一张articles表,分类、标签、作者、图片、统计、审核记录甚至搜索关键词全部堆在一起。前一种结构让开发变得复杂,后一种结构则会让数据库越来越难以维护。

好的MySQL设计并不是追求表越多越专业,也不是追求字段越少越漂亮。核心是让数据关系清楚,让高频查询能够利用正确的索引,让经常变化的数据与稳定内容适当分离,同时给未来的数据增长留下空间。

对于一个真正运行中的新闻网站来说,数据库设计最终会回到三个问题:数据怎样保存,数据怎样查询,以及数据怎样在规模扩大以后继续保持可维护。表结构只是起点,SQL访问模式、索引设计、事务边界、分页方式、备份恢复和数据生命周期,才共同决定了这个数据库能不能长期稳定运行。

喜欢这篇报道?

使用下面的功能,方便以后继续阅读和分享 MNewsTV

设为 Google 新闻首选来源 让 Google 新闻优先显示 MNewsTV 的最新报道 ›
★ 我的收藏 查看已经收藏的文章
关于文章收藏 收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。 删除收藏请进入「我的收藏」进行管理。
分享这篇报道

捐助(Paypal): https://www.paypal.me/observeccp
订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP