简介:本资源是一份面向数据库设计初学者与CRM系统开发者的《CRM客户关系管理系统数据库表设计需求规格说明书》,聚焦企业级权限管理、销售流程跟踪与客户信息建模等核心场景。文档完整定义了10张关键数据表(含角色、菜单、权限、用户、销售机会、客户、联系人、交往记录、流失客户及开发计划表),涵盖主外键约束、字段类型、长度、空值规则及业务语义说明,可直接用于SQL Server或兼容数据库的建模与开发落地。资源为单文件Word文档(.doc格式),大小191KB,结构清晰、字段注释详尽,适合作为课程设计参考、毕业项目数据库设计蓝本或团队开发规范依据。目前已有296人学习下载,读者可快速掌握CRM系统中多角色权限控制、销售漏斗数据关联、客户全生命周期字段体系等实战设计要点。
1. 为什么一份像样的 CRM 数据库表设计文档,比写十行 CRUD 代码还难落地?
你手头那份标着“CRM客户关系管理系统数据库表设计需求规格说明书(1).doc”的 Word 文件,大概率不是被锁在共享盘角落吃灰,就是刚被产品经理甩进钉钉群、附言“今晚下班前给开发看下”。但现实是:90% 的 CRM 项目卡在第一关——不是接口联调失败,不是前端样式错位,而是用户信息表字段命名和 null 约束对不上业务口径,线索表和商机表的生命周期状态机压根没对齐,连“客户”到底指自然人还是企业法人都没共识。这不是文档格式问题,是业务语义到数据结构的翻译断层。这份说明书真正的价值,不在于它多厚、多规范,而在于它能否让销售、客服、IT、DBA 在同一张 ER 图上说同一种话。本文不讲 ISO/IEC/IEEE 29148 标准怎么套模板,只讲一线工程师怎么把“需求规格说明书”这六个字,变成可建表、可索引、可查、可改、可审计的 17 张真实 SQL 表——从字段粒度、状态流转、历史留痕到权限隔离,每一步都踩过坑、验过数、跑过压测。适合正在接手 CRM 改造、准备本地部署 Microsoft Dynamics CRM 替代方案、或想用免费 CRM 框架(如 Odoo、EspoCRM)做深度定制的后端/全栈工程师。
2. 从需求说明书到物理表:三步拆解核心实体与关联逻辑
CRM 系统不是一堆表的堆砌,而是业务动作在数据层的镜像。一份合格的需求规格说明书,必须能映射出三类关键实体:主体(Who)、对象(What)、动作(When & How)。我们以说明书里高频出现的“第1关:数据库表设计 - 用户信息表”为锚点,反向推导出最常被忽略的底层约束。
2.1 主体层:用户、客户、联系人三者为何不能合并在一张表?
很多团队图省事,把user_id、customer_id、contact_id全塞进t_user表,加个type字段区分。这是 CRM 数据库翻车的第一高发区。真实业务中:
- 用户(User):系统登录者,有账号、密码、角色、部门归属,受 RBAC 控制;
- 客户(Customer):销售跟进的目标单位,可能是企业(含统一社会信用代码、注册资本、行业分类)或自然人(需身份证号脱敏存储);
- 联系人(Contact):客户下的具体对接人,一个客户可有多个联系人,每个联系人有职位、手机、邮箱、微信等独立属性。
三者存在1:N:N 关系链:1 个 User 可代表多个 Customer(如销售总监管理多个客户),1 个 Customer 可有 N 个 Contact,1 个 Contact 只属于 1 个 Customer。强行合并会导致:
- 权限控制失效(给 User 分配的菜单权限无法精准作用于其负责的 Customer);
- 历史记录错乱(某 Contact 的沟通记录被误归到其所属 Customer 的其他 Contact 下);
- 扩展性崩溃(当需要为 Customer 增加“股权结构图”附件,或为 Contact 增加“微信聊天截图”时,字段爆炸式增长)。
提示:不要用
t_user表承载业务身份。标准做法是建三张独立表,并通过外键明确关联:
t_user(系统账户)t_customer(客户主数据)t_contact(联系人,含customer_id外键)
2.2 对象层:线索(Lead)、商机(Opportunity)、合同(Contract)的状态机如何设计才不漏单?
需求说明书里常写“线索可转为客户”,但没写清楚“转”这个动作背后的数据迁移规则。我们按实际销售流程定义三张核心业务表及其状态字段:
| 表名 | 核心状态字段 | 典型值 | 状态流转约束 |
|---|---|---|---|
t_lead | status | new,contacted,qualified,disqualified,converted | converted后必须生成t_customer+t_contact记录,且t_lead不可再编辑 |
t_opportunity | stage | prospecting,proposal,negotiation,closed_won,closed_lost | stage变更需记录stage_updated_at和操作人stage_updated_by;closed_won必须关联t_contract |
t_contract | status | draft,signed,executing,completed,cancelled | signed状态需校验sign_date非空,且amount> 0;completed前必须有actual_end_date |
关键细节:所有状态字段必须是 ENUM 或引用字典表(如t_dict_status),禁止用字符串硬编码。否则后续报表统计、BI 取数、前端下拉选项全部崩盘。例如t_opportunity.stage若存"proposal"而非2,当销售流程升级新增demo阶段时,旧数据无法自动兼容。
2.3 动作层:沟通记录、任务、文件附件如何实现“一次录入,多处可见”?
CRM 的价值在于行为留痕。但很多设计把t_communication(沟通记录)简单设为content TEXT,导致无法检索、无法分析、无法联动。正确做法是结构化拆解:
CREATE TABLE t_communication ( id BIGINT PRIMARY KEY AUTO_INCREMENT, type ENUM('call', 'email', 'meeting', 'wechat', 'sms') NOT NULL, -- 明确沟通类型 subject VARCHAR(255) NOT NULL, -- 主题,用于列表页快速识别 content TEXT, -- 详细内容(支持富文本时建议存 HTML 片段) duration_seconds INT DEFAULT 0, -- 通话时长/会议时长,用于销售效能分析 related_to_type ENUM('lead', 'customer', 'opportunity', 'contact') NOT NULL, related_to_id BIGINT NOT NULL, -- 关联主键,实现跨实体挂载 created_by BIGINT NOT NULL, -- 操作人 created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_related (related_to_type, related_to_id), -- 关键查询索引 INDEX idx_user_time (created_by, created_at) -- 销售个人工作流索引 );这个设计让一条沟通记录可同时挂在“线索 A”、“客户 B”、“商机 C”下,前端按需聚合展示,后台按related_to_type + related_to_id精准推送消息。同理,t_task表需包含assignee_id(指派人)、due_date(截止日)、priority(优先级)、status(待处理/进行中/已完成/已取消),并强制要求status = 'completed'时必须填写completed_at和completed_by—— 这是销售过程数字化的底线。
3. 字段级设计:那些说明书里没写、但上线必炸的 7 类细节
需求规格说明书往往只列“客户名称、联系电话、地址”,却从不提“电话要不要分固话/手机?地址要不要拆成省市区街道?名称要不要支持繁体/生僻字?”。这些看似琐碎的字段设计,直接决定系统能否过等保、能否接 BI、能否支撑千人千面营销。
3.1 字符串字段:长度、编码、校验,一个都不能少
客户名称
name:VARCHAR(200)是底线。理由:中国公司全称最长可达 180 字(如“北京中关村科技园区发展股份有限公司”),加上英文/括号/符号,200 安全。必须用utf8mb4编码,否则 emoji、生僻字(如“䶮”、“堃”)存不进去。注意:MySQL 5.7+ 默认
innodb_large_prefix=ON,但若用utf8mb4+VARCHAR(200),索引前缀长度需显式指定(如INDEX idx_name (name(191))),否则建表报错。联系电话
phone:
拆成mobile(手机号)、tel(固话)、wechat(微信号)三字段。mobile用CHAR(11)+ 正则校验(^1[3-9]\d{9}$),tel用VARCHAR(20)(含区号、分机号,如010-88881234-801),wechat用VARCHAR(64)(微信 ID 规则宽松,但上限 64 字符)。提示:绝不允许
phone单字段存多种格式!否则导出 Excel 时固话被 Excel 自动转成科学计数法(如02112345678→2.112345678E10),销售投诉率飙升。地址
address:
拆为province、city、district、street、postal_code五字段。province/city/district用字典表关联(ID + 名称),street用VARCHAR(255),postal_code用CHAR(6)。好处:地图打点、区域销售分析、物流配送路由全部可算。
3.2 数值与时间字段:精度、时区、默认值的血泪经验
金额
amount:DECIMAL(18,2)是铁律。FLOAT或DOUBLE会导致 0.1+0.2≠0.3,财务对账直接翻车。18 位总长(含小数点后 2 位)覆盖亿元级合同无压力。注意:
DECIMAL(10,2)看似够用,但某客户签了 12.34 亿合同(1234000000.00),10 位不够存,线上直接报错。日期时间
created_at/updated_at:
统一用DATETIME(非TIMESTAMP),时区设为Asia/Shanghai。TIMESTAMP会随 MySQL 服务器时区变更自动转换,导致日志时间错乱。DATETIME存的是绝对时间,稳定可靠。提示:所有业务时间字段(如
next_follow_up_time,contract_sign_date)必须带_time或_date后缀,避免与id、status等字段混淆。状态标识
is_deleted/is_active:
用TINYINT(1)(0/1),绝不用BOOLEAN(MySQL 实际是TINYINT别名,但 ORM 映射易出错)。is_deleted默认0,软删除时置1并加deleted_at DATETIME字段,便于审计。避坑:
is_active和status字段不能共存!status已含active/inactive/pending等值,再加is_active属逻辑冗余,维护成本翻倍。
3.3 外键与索引:不是所有关联都要加外键,但所有查询都要有索引
外键(FOREIGN KEY):
仅在强一致性场景使用:t_contact.customer_id → t_customer.id、t_opportunity.customer_id → t_customer.id。
禁止在外键上设ON DELETE CASCADE!CRM 中删除客户必须走审批流,而非数据库级级联删掉所有商机、合同、沟通记录。提示:
t_user.created_by不加外键。因为t_user表可能被定时归档,而创建人记录需永久保留,用逻辑外键(应用层校验)更安全。索引(INDEX):
每张表至少有 3 类索引:- 主键索引(InnoDB 自带);
- 关联查询索引:如
t_communication的(related_to_type, related_to_id); - 高频查询索引:如
t_customer的(status, updated_at)(查最近更新的活跃客户)。
注意:
LIKE '%关键词%'查询永远走不了索引!搜索客户名必须用全文索引(FULLTEXT)或接入 Elasticsearch,别指望name LIKE '%华为%'能快。
4. 避坑指南:CRM 数据库上线前必须验证的 5 个致命陷阱
再完美的设计文档,落地时也会因环境差异、认知偏差、历史包袱暴雷。以下是我在 7 个 CRM 项目中踩过的真坑,按现象→原因→解决三步还原。
4.1 现象:销售反馈“找不到昨天新建的线索”,但数据库里明明有记录
原因:t_lead.created_at字段用了CURRENT_TIMESTAMP默认值,但应用服务器时区为UTC,数据库时区为Asia/Shanghai,导致时间差 8 小时,前端按“今日”筛选时漏掉。
解决:所有created_at/updated_at字段默认值统一写为DEFAULT CURRENT_TIMESTAMP,且应用层插入时显式传入NOW()时间戳(由应用服务器生成),杜绝时区依赖。
4.2 现象:导出客户列表 Excel 时,部分姓名显示为??或乱码
原因:数据库字符集为utf8(仅支持 3 字节 UTF-8),但客户姓名含 4 字节 emoji(如 🌟)或生僻字(如 “龘”),存入后截断。
解决:建库时指定CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci,所有表、字段、连接字符串(JDBC URL 加?characterEncoding=utf8mb4)同步升级。
4.3 现象:商机阶段变更后,销售经理看不到下属的最新进展
原因:t_opportunity.updated_at字段未设ON UPDATE CURRENT_TIMESTAMP,且应用层未主动更新该字段,导致按“最后更新时间”排序失效。
解决:建表时强制添加updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,ORM 更新时忽略此字段(由 DB 自动维护)。
4.4 现象:搜索“上海”客户时,返回北京、深圳的记录
原因:t_customer.city字段存的是“上海市”,但前端搜索框输入“上海”,WHERE city LIKE '%上海%'匹配到“北京市”(含“上海”二字)。
解决:城市字段用字典表 ID 存储(city_id INT),搜索时用JOIN t_dict_city ON t_customer.city_id = t_dict_city.id WHERE t_dict_city.name = '上海',精确匹配。
4.5 现象:批量导入 10 万条联系人后,t_contact表查询变慢 10 倍
原因:导入脚本未关闭唯一索引(如mobile字段的UNIQUE约束),每插一条都触发全表扫描去重,O(n²) 复杂度。
解决:大批量导入前,ALTER TABLE t_contact DROP INDEX uk_mobile,导入完成后再ADD UNIQUE INDEX uk_mobile (mobile);或改用INSERT IGNORE+ 事后去重。
5. 权限与审计:让 CRM 数据库从“能用”走向“可信”
CRM 存的是企业命脉——客户资源、销售过程、合同金额。一份合格的需求规格说明书,必须包含数据权限与操作审计的设计条款。这不是锦上添花,而是合规底线(尤其金融、医疗类客户)。
5.1 行级权限(RLS):如何让销售只能看到自己名下的客户?
传统 RBAC(角色权限)只能控制“能看客户列表”,无法控制“能看到哪些客户”。必须引入行级权限。MySQL 8.0+ 原生支持 RLS,但多数项目用应用层模拟:
-- 在查询客户时,动态拼接 WHERE 条件 SELECT * FROM t_customer WHERE status = 'active' AND (owner_id = ? OR ? IN (SELECT user_id FROM t_user_role WHERE role_id IN (SELECT role_id FROM t_role_permission WHERE permission = 'customer_view_all')));更优雅的做法是建视图v_customer_accessible,将权限逻辑封装:
CREATE VIEW v_customer_accessible AS SELECT c.* FROM t_customer c JOIN t_user u ON c.owner_id = u.id OR u.department_id = c.department_id WHERE u.id = @current_user_id;应用层查询统一走SELECT * FROM v_customer_accessible,DBA 只需维护视图逻辑,开发无需感知权限细节。
5.2 操作审计:谁在什么时候改了客户的手机号?
CRM 最怕“静默修改”——销售私下篡改客户联系方式,导致市场部群发短信发错人。必须记录所有敏感字段变更:
CREATE TABLE t_customer_audit ( id BIGINT PRIMARY KEY AUTO_INCREMENT, customer_id BIGINT NOT NULL, field_name VARCHAR(50) NOT NULL, -- 'mobile', 'wechat', 'email' old_value TEXT, new_value TEXT, operator_id BIGINT NOT NULL, operated_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_customer_time (customer_id, operated_at) ); -- 应用层在更新 t_customer.mobile 前,先 INSERT 一条审计记录 INSERT INTO t_customer_audit (customer_id, field_name, old_value, new_value, operator_id) VALUES (?, 'mobile', ?, ?, ?);提示:审计表不加外键(避免锁表),用异步写入(如 Kafka + Flink 日志管道),保证主业务不被拖慢。
5.3 敏感字段加密:身份证号、银行卡号必须密文存储
需求说明书里常写“存储客户身份证号”,但没写“怎么存”。明文存储违反《个人信息保护法》,必须加密:
- 方案选择:AES-256-GCM(加密+认证),密钥由 KMS(密钥管理服务)托管,应用层调用 KMS API 加解密;
- 字段设计:
id_card_encrypted VARBINARY(512),id_card_iv VARBINARY(16)(初始向量); - 使用约束:解密操作仅限特定接口(如客户实名认证),前端永远看不到明文。
# Python 示例:调用阿里云 KMS 解密 from aliyunsdkkms.request.v20160120 import DecryptRequest from aliyunsdkcore.client import AcsClient def decrypt_id_card(encrypted_data: bytes) -> str: request = DecryptRequest.DecryptRequest() request.set_CiphertextBlob(encrypted_data.hex()) response = client.do_action_with_exception(request) return json.loads(response)["Plaintext"]绝不允许在数据库里存 Base64 编码的“伪加密”字符串——Base64 不是加密,只是编码,毫无安全意义。
6. 验证与演进:用 3 个真实 SQL 脚本检验你的设计是否经得起实战
设计再完美,不跑 SQL 就是纸上谈兵。我习惯用以下 3 个脚本,在本地 MySQL 实例上一键验证核心路径是否通畅。它们不是测试用例,而是业务生命力的探测器。
6.1 脚本 1:模拟销售全流程,验证状态机与关联完整性
-- 1. 创建线索 INSERT INTO t_lead (name, mobile, status, created_by) VALUES ('张三科技', '13800138000', 'new', 1001); -- 2. 转为客户(触发业务逻辑) INSERT INTO t_customer (name, industry, status, owner_id) VALUES ('张三科技', 'IT服务', 'active', 1001); INSERT INTO t_contact (customer_id, name, position, mobile, email) VALUES (LAST_INSERT_ID(), '张三', 'CEO', '13800138000', 'zhang@zhan.com'); -- 3. 创建商机 INSERT INTO t_opportunity (customer_id, name, amount, stage, owner_id) VALUES (LAST_INSERT_ID(), 'ERP系统采购', 1200000.00, 'prospecting', 1001); -- 4. 记录首次沟通 INSERT INTO t_communication (type, subject, content, related_to_type, related_to_id, created_by) VALUES ('call', '初次电话沟通', '介绍产品功能,预约演示', 'opportunity', LAST_INSERT_ID(), 1001); -- ✅ 验证:查商机详情,应自动关联客户、联系人、沟通记录 SELECT o.name AS opportunity_name, c.name AS customer_name, ct.name AS contact_name, com.subject AS last_communication FROM t_opportunity o JOIN t_customer c ON o.customer_id = c.id JOIN t_contact ct ON c.id = ct.customer_id LEFT JOIN t_communication com ON com.related_to_type = 'opportunity' AND com.related_to_id = o.id WHERE o.id = LAST_INSERT_ID();预期结果:一行记录,字段完整,无 NULL。若customer_name或contact_name为 NULL,说明外键关联或插入顺序有误。
6.2 脚本 2:压力测试索引有效性,验证千万级数据查询性能
-- 模拟 100 万条沟通记录(生产环境常见量级) INSERT INTO t_communication (type, subject, content, related_to_type, related_to_id, created_by, created_at) SELECT ELT(FLOOR(1 + RAND() * 5), 'call', 'email', 'meeting', 'wechat', 'sms'), CONCAT('沟通主题-', seq), CONCAT('沟通内容详情,编号:', seq), ELT(FLOOR(1 + RAND() * 3), 'lead', 'customer', 'opportunity'), FLOOR(1 + RAND() * 100000), FLOOR(1001 + RAND() * 100), DATE_SUB(NOW(), INTERVAL FLOOR(RAND() * 365) DAY) FROM ( SELECT @row := @row + 1 as seq FROM (SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t1, (SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9) t2, (SELECT @row := 0) t3 LIMIT 1000000 ) seqs; -- ✅ 验证:查某销售最近 100 条沟通,应在 100ms 内返回 SELECT * FROM t_communication WHERE created_by = 1001 ORDER BY created_at DESC LIMIT 100;预期结果:执行时间< 100ms。若超时,检查idx_user_time (created_by, created_at)索引是否存在,或created_by是否为BIGINT(若误设为INT,索引失效)。
6.3 脚本 3:审计合规检查,验证敏感操作可追溯
-- 模拟修改客户手机号(高危操作) UPDATE t_customer SET mobile = '13900139000', updated_at = NOW() WHERE id = 1; -- ✅ 验证:审计表必须有一条对应记录 SELECT ca.field_name, ca.old_value, ca.new_value, u.username AS operator_name FROM t_customer_audit ca JOIN t_user u ON ca.operator_id = u.id WHERE ca.customer_id = 1 AND ca.field_name = 'mobile' ORDER BY ca.operated_at DESC LIMIT 1;预期结果:返回 1 行,old_value为原手机号,new_value为新手机号,operator_name为操作人姓名。若无记录,说明应用层未调用审计写入逻辑。
做完这三步验证,你的 CRM 数据库表设计就不再是 Word 文档里的静态文字,而是一套能呼吸、可生长、抗压、合规的活系统。我坚持一个习惯:每次需求评审会前,先把这三段 SQL 贴到会议纪要里,让产品经理、销售总监、CTO 一起看结果——数据不会说谎,它比 PPT 上的“高可用”“高性能”更有说服力。希望帮到你。
本文还有配套的精品资源,点击获取