mysql 仅是存储引擎,无法单独搭建完整 cms;真正可用的简单 cms 必须搭配 php/python/node.js 等后端语言,mysql 仅负责存储文章、用户、分类等结构化数据。

用 MySQL 单独搭建完整 CMS 不现实——它只是存储引擎,不处理路由、模板、用户登录或文件上传。真正能跑起来的“简单 CMS”,必须搭配 PHP/Python/Node.js 等后端语言,MySQL 仅负责存取文章、分类、用户等结构化数据。
建表前先理清核心数据关系
一个最小可用 CMS 至少要支撑「用户登录」「发布文章」「打标签」「分页列表」。别一上来就设计 10 张表,先聚焦 4 张基础表:
-
users:存id、username、password_hash(别存明文!)、role(如 'admin' 或 'editor') -
posts:含id、title、content(TEXT 类型)、author_id(关联 users.id)、created_at、status('draft'/'published') -
categories:id、name、slug -
post_categories:纯关联表,字段只有post_id和category_id(支持一篇文章多个分类)
外键约束可加可不加,开发阶段建议先关掉 FOREIGN_KEY_CHECKS=0,避免 INSERT 顺序出错;上线前补上约束并验证数据一致性。
PHP + MySQL 实现文章列表的关键查询
前端要显示「已发布的文章标题+摘要+分类名+发布时间」,一条 JOIN 查询就能搞定,但要注意字段别名和 NULL 处理:
SELECT p.id, p.title, SUBSTRING(p.content, 1, 120) AS excerpt, c.name AS category_name, p.created_at FROM posts p LEFT JOIN post_categories pc ON p.id = pc.post_id LEFT JOIN categories c ON pc.category_id = c.id WHERE p.status = 'published' ORDER BY p.created_at DESC LIMIT 10;
常见坑:
-
SUBSTRING在 MySQL 8.0+ 支持,老版本用SUBSTR;若 content 是 JSON 字段,不能直接截取,得先用JSON_UNQUOTE(JSON_EXTRACT(...)) - LEFT JOIN 导致同一篇文章出现多行(多个分类),需在 PHP 层合并,或改用
GROUP_CONCAT(c.name)拼接分类名 - 没加
WHERE p.status = 'published'?测试数据会混进草稿,线上直接暴露未审核内容
密码存储和用户登录的硬性要求
MySQL 本身不校验密码强度或加密逻辑,这些必须由应用层完成:
- 注册时,PHP 必须用
password_hash($password, PASSWORD_ARGON2ID)(优先)或PASSWORD_DEFAULT生成哈希,存进users.password_hash - 登录验证时,用
password_verify($input, $hash_from_db),绝不用=或MD5() - MySQL 用户账号(如 root@localhost)和 CMS 应用用户(如 admin@example.com)完全无关,别把应用密码配到 MySQL 连接配置里传明文
如果跳过这步,哪怕界面再漂亮,系统也算不上“可用”——它只是个带 SQL 注入漏洞的玩具。
部署时最容易被忽略的 MySQL 配置项
本地能跑,放到服务器就报错?大概率是这几个参数没调:
-
sql_mode:默认可能含STRICT_TRANS_TABLES,插入空字符串到 NOT NULL 字段会失败;CMS 表单常有可选字段,建议设为""或至少去掉STRICT相关项 -
max_allowed_packet:上传富文本(含图片 base64)时,content字段超 4MB 就被截断,需调大到 32M+ - 字符集:全程统一用
utf8mb4(不是 utf8!),否则 emoji 和生僻字存不进去,建库时就指定:CREATE DATABASE cms DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
这些不是“高级技巧”,而是让数据不丢、不乱、不报错的底线配置。










