信息发布→ 登录 注册 退出

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

发布时间:2026-01-12

点击量:
t_ticket主表必须包含id、customer_id、operator_id、title、content、status、priority、created_at、updated_at、status_updated_at字段,且需合理设置类型、索引与语义分离。

工单主表 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_atupdated_at,且都设为 DATETIME(3)(毫秒级),客服SLA统计依赖精确到秒的更新时间
  • 别忘了 customer_id(关联客户表)和 operator_id(当前处理人),这两个是高频 JOIN 字段,记得加索引

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

把每次分配、转交、升级的操作都写进 t_ticketlast_operator_idhistory 字段,短期省事,长期必踩坑。

  • 历史不可追溯:谁在什么时间把工单转给了谁?主表只留最新值,审计、复盘时抓瞎
  • 触发器或应用层拼接 JSON 到 history 字段,会导致该字段越来越大,SELECT * 拖慢所有查询
  • 正确做法是建独立表 t_ticket_flow,字段至少含:ticket_idfrom_operator_idto_operator_idaction(如 '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 在任何字段更新时都会触发,而客服系统常批量更新 descriptionattachment_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_ticketattachmentsmessages 字段。

  • TEXTMEDIUMTEXT 字段膨胀后,主表 SELECT 变慢,即使你只查 idtitle,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;

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

标签:# laravel  # js  # json  # mysql  # 不含  # 仅代表  # 才发现  # 这两个  # 并在  # 设为  # 更新时间  # 变慢  # 应用层  # 客服  # 数据库  # history  # 并发  # 接口  # int  # select  # 为什么  # 状态码  # django  # cdn  # nginx  # go  
在线客服
服务热线

服务热线

4008888355

微信咨询
二维码
返回顶部
×二维码

截屏,微信识别二维码

打开微信

微信号已复制,请打开微信添加咨询详情!