mysql如何设计一个问卷调查系统_mysql问卷系统设计与开发

来源:这里教程网 时间:2026-02-28 20:47:23 作者:

问卷表和题目表如何用外键关联才不翻车

问卷系统最常崩在「一对多」关系设计上。别把所有题目硬塞进一张表,也别让

questionnaire_id
在题目表里允许为
NULL
——这会导致查问卷时漏题或关联失败。

正确做法是两张主表加一张中间表(如果支持题目复用);若题目只归属单个问卷,直接在题目表设

questionnaire_id
外键并加索引:

CREATE TABLE questionnaire (
  id INT PRIMARY KEY AUTO_INCREMENT,
  title VARCHAR(255) NOT NULL,
  status TINYINT DEFAULT 1  -- 1=启用, 0=停用
);
<p>CREATE TABLE question (
id INT PRIMARY KEY AUTO_INCREMENT,
questionnaire_id INT NOT NULL,
content TEXT NOT NULL,
type ENUM('single', 'multiple', 'text') NOT NULL,
sort_order TINYINT DEFAULT 0,
FOREIGN KEY (questionnaire_id) REFERENCES questionnaire(id) ON DELETE CASCADE
);</p><p>-- 给外键字段加索引(InnoDB 外键自动建索引,但显式声明更稳妥)
CREATE INDEX idx_question_qid ON question(questionnaire_id);
ON DELETE CASCADE
能避免删问卷后残留“孤儿题目”,但上线前务必确认业务是否真允许级联删除
别用
TEXT
存选项内容(如单选的 A/B/C),应拆到
option
表,否则改选项时要字符串替换,极易出错
sort_order
TINYINT
足够(最多 255 题),比用
INT
节省空间且排序更快

用户答题记录怎么存才查得快又不锁表

答题记录是写多读少、但偶尔要按用户/问卷聚合统计的场景。别用一张大宽表存所有答案,更别把 JSON 字符串塞进一个字段里——后期想统计“第3题选A的人数”就得全表扫描+JSON 解析,慢且无法走索引。

推荐三张表分层存储:

CREATE TABLE answer_sheet (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  user_id INT NOT NULL,
  questionnaire_id INT NOT NULL,
  submitted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  ip VARCHAR(45),
  INDEX idx_user_qid (user_id, questionnaire_id),
  INDEX idx_qid_time (questionnaire_id, submitted_at)
);
<p>CREATE TABLE answer (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
sheet_id BIGINT NOT NULL,
question_id INT NOT NULL,
value TEXT,  -- 单选存选项ID,多选存逗号分隔ID列表(简单场景),文本题存原文
FOREIGN KEY (sheet_id) REFERENCES answer_sheet(id) ON DELETE CASCADE
);</p><p>CREATE INDEX idx_answer_qid ON answer(question_id);
answer_sheet
记录一次提交的元信息,
answer
记录每道题的答案,一一对应——这样加新题型不用改表结构
value
字段不用
ENUM
(扩展性差),也不建议直接存选项文字(翻译/修改成本高),优先存关联 ID(比如
option_id
),复杂逻辑放应用层
高频查询“某问卷提交人数”直接查
answer_sheet
表,别 COUNT JOIN;要“各题选项分布”再 JOIN
answer
option

MySQL 8.0 的 JSON 函数能用在哪几个关键点

不是所有地方都适合上 JSON。仅在以下三个明确场景用

JSON
类型才真正提效:

问卷配置中的动态字段:比如每份问卷可自定义「提交后跳转链接」「关闭时间」,这些非通用字段用
JSON
存在
questionnaire.config
字段里,比加一堆 nullable 列干净
用户补充信息:如「填写人所在部门」「职级」等非结构化字段,存在
answer_sheet.ext_info
中,用
JSON_CONTAINS()
做简单筛选
导出原始数据时保留完整结构:后台导 Excel 需要原样输出选项文字和用户填写内容,用
JSON_OBJECT()
+
JSON_ARRAYAGG()
拼装比应用层拼接更稳

但注意:

JSON
字段无法被普通索引加速,要用生成列 + 函数索引才能高效查询。例如想按「部门」查提交记录:

ALTER TABLE answer_sheet 
ADD COLUMN dept VARCHAR(50) 
GENERATED ALWAYS AS (JSON_UNQUOTE(JSON_EXTRACT(ext_info, '$.department'))) STORED,
<p>ADD INDEX idx_dept (dept);

并发提交时怎么防止重复记录或脏计数

用户手抖连点“提交”、或者前端没禁用按钮,会导致同一份答卷插入多次。靠应用层防重(如 UUID + 唯一索引)只是兜底,核心要在数据库层做约束。

answer_sheet
上加联合唯一索引:
UNIQUE KEY uk_user_qid (user_id, questionnaire_id, DATE(submitted_at))
,限制同用户当天只能交一次(按需调整粒度)
如果允许重填,但要保留历史版本,就把
submitted_at
改成
DATETIME(3)
(毫秒级),再加
UNIQUE KEY uk_user_qid_ts (user_id, questionnaire_id, submitted_at)
统计类字段(如“已提交人数”)绝不要用
UPDATE ... SET count = count + 1
,而应在事务中先
SELECT ... FOR UPDATE
锁住问卷行,再更新——否则高并发下会少计

实际部署时,

answer_sheet
表容易成为热点,如果日提交量超 10 万,考虑按
questionnaire_id
分表,或迁移到 TiDB 这类支持自动分片的引擎。

相关推荐