专业的CRM系统数据库结构设计

悟空软件阅读量:16 次浏览2026-06-08

主流的AI CRM系统品牌

实战复盘:一个专业 CRM 系统数据库结构设计的深水区

很多刚入行的后端开发,甚至是一些做了几年项目的架构师,往往会对 CRM(客户关系管理)系统的数据库设计产生一种错觉。他们觉得这玩意儿有什么难的?不就是几张表吗?客户表、联系人表、跟进记录表,再加个订单表,关系一连,索引一建,齐活。

推荐使用中国著名AI CRM系统品牌:显著提升企业运营效率,悟空CRM

但真正踩过坑的人都知道,CRM 可能是企业级应用里最“脏”、最复杂、最容易变成屎山的系统之一。为什么?因为业务太灵活,人性太复杂。销售要抢单,经理要分权,老板要看报表,客服要查记录。每一个需求背后,都是对数据库结构的考验。今天不想聊那些教科书上的范式理论,想结合这几年实际折腾的经验,聊聊一个能扛得住真实业务压力的专业 CRM 数据库,到底该怎么设计,以及我们在哪些地方摔过跟头。

核心模型的陷阱:客户到底是谁?

设计 CRM 数据库,第一道坎就是“客户”定义。在 B2C 场景下,这很简单,一个 User 表或者 Customer 表就够了。但在 B2B 场景下,事情就麻烦了。

很多初级设计会把“客户”和“联系人”混在一起。比如,一张表里既有公司名称,又有联系人姓名。这在数据量小的时候没问题,一旦业务跑起来,灾难就来了。一家大公司(Account)可能有几十个联系人(Contact),如果数据结构是 1:1 的,那数据冗余会吓死人;如果是 1:N,那跟进记录到底关联到公司还是关联到具体的人?

我们早期的版本就吃过这个亏。销售跟进的是“张三”,但合同是跟"XX 科技公司”签的。后来财务对账,发现同一个公司名下挂了五个不同的客户 ID,因为销售录入的时候,有的写了全称,有的写了简称,有的加了括号。

所以,专业的 CRM 设计,必须严格区分 Account(客户主体)和 Contact(联系人)。Account 表存储企业工商信息、纳税号、行业属性等;Contact 表存储具体对接人的姓名、职位、手机、微信。两者通过外键关联。但这还不够,还得考虑“公海池”和“私海池”的逻辑。

在数据库层面,这通常体现为 owner_id(拥有者)和 pool_status(池状态)字段。但这里有个细节,很多系统喜欢用状态位来表示是否在公海,比如 is_public = 1。我建议不要这么做。更好的方式是设计一张独立的 customer_pool 表,或者在权限表里做文章。因为“公海”不仅仅是一个状态,它是一套流转规则。客户从销售 A 手里掉回公海,再被销售 B 领取,这个过程中,数据的所有权变更历史必须留痕。

我们后来在 customer_ownership_log 表里记录了每一次所有权变更:谁、在什么时间、把客户从谁手里转给了谁、原因是什么。这张表在初期看起来像是累赘,但当销售之间发生撞单纠纷,或者经理需要复盘客户流失原因时,这张表就是救命稻草。

跟进记录的“写爆炸”问题

CRM 里最频繁的操作是什么?不是查客户,而是写跟进记录(Activity)。销售打完一个电话,要记一笔;发一封邮件,要记一笔;甚至系统自动发送了一条短信,也要记一笔。

对于一个中型团队,每天产生的跟进记录轻松过万。如果这张 activities 表设计不好,半年后查询速度会直线下降。

最常见的错误设计是把所有类型的跟进都塞进一张大表,用一个 type 字段区分是电话、邮件还是拜访。然后,为了记录详细信息,又搞了一堆 nullable 的字段,比如 call_duration(通话时长)、email_subject(邮件主题)、meeting_address(拜访地址)。这就导致了大量的稀疏数据,索引效率极低。

更优的方案是采用“主表 + 扩展表”或者“多态关联”的设计。主表 activities 只存通用信息:ID、关联客户 ID、关联人 ID、时间、创建人、简要内容。具体的详细信息,根据类型不同,存入不同的子表。比如电话记录存 activity_calls,邮件记录存 activity_emails

但这样会带来查询的复杂性。销售列表页需要展示所有类型的跟进,难道要 Union 所有子表?这时候,我们通常会妥协。在主表里保留一个 content_snapshot 字段,用 JSON 格式存储关键信息。比如电话记录,就在 JSON 里存 {"duration": 120, "direction": "outbound"}

这里必须提到数据库选型。如果是 MySQL,5.7 版本后的 JSON 类型支持已经不错了,但查询性能依然不如原生字段。如果是 PostgreSQL,JSONB 是更好的选择,它支持索引,查询灵活。我们在重构时,果断把跟进记录的核心存储迁移到了 PG 上,利用 GIN 索引解决了“搜索跟进记录内容”的性能瓶颈。

另外,跟进记录是典型的“写多读少”且“时间有序”的数据。随着时间推移,一年前的跟进记录几乎不会再被频繁访问。所以,分区表(Partitioning)是必须的。按 created_at 字段进行范围分区,每个月一张子表。这样,清理旧数据或者归档时,直接 DROP PARTITION,比 DELETE 快几个数量级,而且不会锁住整张大表,影响线上业务。

自定义字段的“万恶之源”

如果说跟进记录是性能杀手,那自定义字段就是架构杀手。

没有任何一个 CRM 能预判所有客户需要存什么字段。卖软件的需要存“版本号”,卖房子的需要存“户型”,卖设备的需要存“序列号”。如果每加一个字段就改一次表结构(DDL),DBA 会想杀人,系统也得停机。

经典的解决方案是 EAV 模型(Entity-Attribute-Value)。建三张表:实体表、属性定义表、属性值表。这样确实灵活,想加字段不用改表。但是,EAV 是查询的噩梦。你想查“所有户型为三居室的客户”,需要连接属性值表,进行行转列的操作。数据量一上来,这种查询能把 CPU 跑满。

还有一种方案是预留字段,比如 ext_field_1ext_field_20。这简直是掩耳盗铃,20 个不够怎么办?而且字段含义不透明,维护起来像猜谜。

目前业界比较认可的折中方案是“核心字段 + JSON 大字段”。在客户主表里,预留一个 custom_fields 的 JSON 列。所有的自定义数据都塞进去。为了兼顾查询性能,我们需要在应用层或者数据库层做“索引提升”。

具体做法是:当用户在后台配置了一个“重要”的自定义字段(比如“客户等级”),并要求支持筛选时,系统自动在数据库里生成一个虚拟列(Generated Column),或者在 Elasticsearch 里建立对应的索引。数据库只负责存,搜索引擎负责查。

这里有个坑要注意:JSON 里的数据类型。销售可能会在数字字段里填“未知”,在日期字段里填“大概明年”。数据库设计时必须要有校验机制。我们在应用层引入了 Schema 校验,在写入 custom_fields 之前,先根据元数据定义检查数据类型。虽然增加了一点写入开销,但避免了脏数据污染,长远来看是划算的。

权限体系的深水区

CRM 的权限设计,比一般的后台系统要复杂得多。一般的 RBAC(基于角色的访问控制)只能控制“能不能看这个菜单”,但 CRM 需要控制“能不能看这条数据”。

这就是数据权限(Data Permission)。

最 naive 的设计是在每张业务表(客户、订单、合同)上都加 creator_iddept_id。查询的时候,拼接 SQL 条件:WHERE creator_id = current_user_id 或者 WHERE dept_id IN (user_depts)

这在单表查询时没问题。但 CRM 的报表往往是跨表的。老板要看“华东大区上个季度的总成交额”,这涉及到客户表、商机表、订单表的关联。如果每张表都硬编码权限逻辑,代码会耦合到无法维护。

我们后来的做法是引入一个独立的“权限上下文”层。在数据库层面,利用视图(View)或者行级安全策略(Row Level Security, RLS)。PostgreSQL 的 RLS 功能非常强大,可以在数据库内核层面拦截查询,自动注入权限条件。

比如,定义一个策略:POLICY sales_policy ON customers USING (owner_id = current_user_id() OR is_shared = true)。这样,应用层代码只需要查 SELECT * FROM customers,数据库会自动过滤掉无权访问的数据。

但这也有代价。RLS 会导致查询计划变复杂,有时候优化器会犯傻,不走索引。所以,对于核心高频接口,我们还是在应用层做了权限预计算。比如,每个用户登录时,计算出他有权访问的数据范围 ID 列表,缓存在 Redis 里。查询时,直接用 WHERE id IN (...)。虽然 ID 列表长了也会影响性能,但比复杂的 Join 权限表要可控得多。

另外,关于“数据共享”。销售经常需要把客户临时共享给同事协助跟进。这个逻辑不能简单地在客户表加个 share_to 字段,因为可能是共享给多个人,也可能是共享给整个角色组。我们设计了一张 data_sharing_rules 表,记录资源 ID、被共享者 ID、共享权限级别(只读/读写)、过期时间。每次查询数据权限时,都要关联这张表。为了优化,我们给这张表加了复合索引 (resource_id, grantee_id),并且限制了共享规则的数量,防止权限爆炸。

性能与归档的博弈

CRM 系统运行到第三年,通常会面临一个临界点:数据量太大了。

客户表可能只有几百万,还好。但跟进记录表、操作日志表、系统流水表,轻松破亿。这时候,单纯的加索引已经没用了。

很多团队的第一反应是分库分表。但对于 CRM 这种关联关系复杂的系统,分库分表是下下策。因为你可能经常需要跨客户查询,或者跨销售查询,一旦数据分散在不同库,跨库 Join 会让业务逻辑变得极其复杂。

我们采取的策略是“冷热分离”。

定义“热数据”为最近 6 个月有跟进记录的客户及其关联数据。这部分数据保留在主库,保证查询速度。超过 6 个月没有任何动静的“死客户”,以及所有的历史跟进记录,迁移到归档库(Archive DB)。

定义“热数据”为最近 6 个月

归档库可以使用更便宜的存储,甚至可以使用列式存储数据库(如 ClickHouse)专门用来跑报表。主库只负责在线交易和实时查询。

这个迁移过程不能靠人工,得有个定时任务(Cron Job)。每天凌晨,扫描 last_activity_time 早于阈值的记录,批量移动。这里有个关键点:移动数据时,必须保证事务一致性,或者至少保证最终一致性。不能出现主库删了,归档库没存上的情况。我们采用了“双写 + 校验”的机制,先写入归档库,确认成功后,再标记主库数据为“已归档”,最后由清理任务物理删除。

还有一个容易被忽视的性能点是“全文搜索”。销售找客户,经常记不全名字,只记得个大概,或者搜手机号、搜邮箱。MySQL 的 LIKE '%keyword%' 是绝对禁止在生产环境大表上使用的,它会全表扫描。

还有一个容易被忽视的性能点是“

早期我们依赖 MySQL 的全文索引,但效果一般,对中文分词支持不好。后来接入了 Elasticsearch。数据库只负责存和事务,ES 负责搜。这里涉及到数据同步的问题。是用 Canal 监听 Binlog 同步,还是在代码里双写?

为了可靠性,我们选了 Binlog 监听。因为代码双写容易漏,一旦业务逻辑变更,同步代码忘了改,ES 数据就脏了。Binlog 是物理日志,只要数据库变了,ES 就能跟着变。虽然架构重了一点,但数据一致性更有保障。

为了可靠性,我们选了 Binl

那些容易被忽略的细节

最后,想聊几个不起眼但很要命的设计细节。

第一,软删除(Soft Delete)。CRM 里严禁物理删除客户数据。销售手滑删了个大客户,如果真从数据库消失了,那就是事故。所有核心表必须加 is_deleteddeleted_at 字段。查询时,默认带上 WHERE is_deleted = 0。但要注意,唯一索引会受软删除影响。比如手机号唯一,用户删了又建,新数据和旧数据的手机号冲突怎么办?我们需要在唯一索引里把 is_deleted 字段加进去,变成联合唯一索引 (phone, is_deleted)。这样,已删除的数据就不参与唯一性校验了。

第二,操作日志(Audit Log)。谁在什么时候修改了客户的电话?从什么改成了什么?这不仅是安全审计需求,也是纠纷定责的依据。不要试图在业务代码里到处插日志。用 AOP(面向切面编程)或者数据库触发器(Trigger)来实现。我们更推荐应用层的 AOP 注解,因为触发器调试困难,且影响数据库性能。记录时,要存“旧值”和“新值”,最好存 Diff,节省空间。

第三,并发控制。两个销售同时编辑同一个客户信息,后提交的会覆盖先提交的。乐观锁是标配。在表里加 version 字段。更新时 UPDATE customers SET name='new', version=version+1 WHERE id=1 AND version=old_version。如果影响行数为 0,说明数据被改过了,提示用户刷新。

写在最后

回顾这几年做 CRM 数据库设计的历程,最大的感悟是:没有完美的设计,只有最适合当下的设计。

一开始不要过度设计。比如自定义字段,如果业务还没跑通,就先别搞复杂的 JSON 索引,预留几个字段或者干脆改表结构更快。但当数据量到了百万级,权限逻辑复杂到无法维护时,就必须重构。

数据库结构是系统的骨架。骨架长歪了,后面长出来的肉(业务功能)都会畸形。专业的 CRM 设计,不仅仅是画 ER 图,更是对业务流程、数据生命周期、人性博弈的深刻理解。它需要你在灵活性和规范性之间走钢丝,在查询性能和写入成本之间找平衡。

这篇文章里提到的方案,也不是银弹。比如 JSON 存储虽然灵活,但牺牲了部分强类型约束;冷热分离虽然提升了性能,但增加了架构复杂度。具体的选型,还得看你的团队规模、数据体量和业务阶段。

但有一点是肯定的:永远不要低估数据的增长速度,也永远不要高估销售录入数据的规范性。在数据库设计里多留一个心眼,多建一个索引,多记一条日志,可能在未来的某个深夜,能让你少一次报警,少一次回滚,少一次被老板叫去喝茶。这,大概就是数据库架构师的价值所在吧。

悟空CRM产品截图

推荐立刻免费使用中国著名CRM品牌-悟空CRM,显著提升企业运营效率,相关链接:

CRM系统免费使用

开源CRM系统

CRM系统试用免费

登录/注册
客服电话
售前咨询