mysql在客服系统中的工单管理数据库设计

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

工单主表
t_ticket
必须包含哪些字段

客服系统里工单是核心实体,

t_ticket
不能只存标题和内容。漏掉关键字段会导致后续无法过滤、统计或对接客服SaaS接口。

id
:用
BIGINT UNSIGNED AUTO_INCREMENT
,别用
INT
—— 高并发客服场景下月增10万单,
INT
2年就溢出
status
:用
TINYINT
存状态码(如 1=新建, 2=处理中, 3=已解决, 4=已关闭),别用
VARCHAR
存“已解决”——排序、索引、JOIN 都会变慢
priority
:同理用
TINYINT
(1=低, 2=中, 3=高, 4=紧急),避免模糊查询 like '%紧急%'
必须有
created_at
updated_at
,且都设为
DATETIME(3)
(毫秒级),客服SLA统计依赖精确到秒的更新时间
别忘了
customer_id
(关联客户表)和
operator_id
(当前处理人),这两个是高频 JOIN 字段,记得加索引

工单流转记录为什么不能合并在主表里

把每次分配、转交、升级的操作都写进

t_ticket
last_operator_id
history
字段,短期省事,长期必踩坑。

历史不可追溯:谁在什么时间把工单转给了谁?主表只留最新值,审计、复盘时抓瞎 触发器或应用层拼接 JSON 到
history
字段,会导致该字段越来越大,
SELECT *
拖慢所有查询
正确做法是建独立表
t_ticket_flow
,字段至少含:
ticket_id
from_operator_id
to_operator_id
action
(如 'assign'/'transfer'/'escalate')、
created_at
t_ticket_flow
上建联合索引
(ticket_id, created_at)
,查某工单全流程只要
WHERE ticket_id = ? ORDER BY created_at

status
字段变更时要不要自动更新
updated_at

要,但不能靠 MySQL 的

ON UPDATE CURRENT_TIMESTAMP
自动机制。

ON UPDATE CURRENT_TIMESTAMP
在任何字段更新时都会触发,而客服系统常批量更新
description
attachment_url
,不该因此污染
updated_at
—— 它应仅代表「业务状态变化」的时间点
应用层在调用
UPDATE t_ticket SET status = ?, updated_at = NOW(3) WHERE id = ?
时显式赋值,更可控
如果用 ORM(如 Laravel Eloquent 或 Django ORM),确保状态变更逻辑走专用方法,而不是通用
save()
额外建议:加一个
status_updated_at
字段,专用于记录状态变更时间,和通用
updated_at
分离,避免语义混淆

附件、消息、评论该不该和工单主表放一起

绝对不要。常见错误是把图片 base64、聊天记录 JSON 全塞进

t_ticket
attachments
messages
字段。

TEXT
MEDIUMTEXT
字段膨胀后,主表
SELECT
变慢,即使你只查
id
title
,MySQL 仍需读取整行(除非用
ROW_FORMAT=COMPRESSED
+
BLOB
外存,但太重)
正确拆分:建
t_ticket_attachment
(字段:ticket_id, file_name, file_size, url, uploaded_at)、
t_ticket_message
(ticket_id, sender_type, sender_id, content, sent_at)
附件 URL 建议存相对路径(如
/uploads/ticket_12345/abc.jpg
),由 Nginx 或 CDN 直接服务,别让 MySQL 承担文件传输
消息表必须加索引
(ticket_id, sent_at)
,客服坐席翻页加载历史消息时,
LIMIT 20 OFFSET 100
才不会全表扫
-- 示例:工单主表精简结构(不含冗余字段)
CREATE TABLE `t_ticket` (
  `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  `customer_id` BIGINT UNSIGNED NOT NULL,
  `operator_id` BIGINT UNSIGNED DEFAULT NULL,
  `title` VARCHAR(200) NOT NULL,
  `content` TEXT NOT NULL,
  `status` TINYINT NOT NULL DEFAULT 1,
  `priority` TINYINT NOT NULL DEFAULT 2,
  `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
  `updated_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3),
  `status_updated_at` DATETIME(3) NULL,
  PRIMARY KEY (`id`),
  KEY `idx_customer_status` (`customer_id`, `status`),
  KEY `idx_status_priority` (`status`, `priority`),
  KEY `idx_operator_status` (`operator_id`, `status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

实际跑起来才发现,最麻烦的不是字段怎么设,而是「状态机」怎么落地——比如“已解决”后是否允许回退到“处理中”,这种业务规则得在应用层硬校验,数据库只管存,不替你做决策。

相关推荐