找回密码
 立即注册
首页 业界区 业界 独立开发在线客服系统手记:实现对 PostgreSQL 的支持, ...

独立开发在线客服系统手记:实现对 PostgreSQL 的支持,以及与 MySQL 的对比

坏级尹 2025-9-29 10:41:42
我在业余时间开发了一款自己的独立产品:升讯威在线客服与营销系统。陆陆续续开发了几年,从一开始的偶有用户尝试,到如今线上环境和私有化部署均有了越来越多的稳定用户,在这个过程中,我也积累了不少如何开发运营一款独立产品的经验。
在我完成对 PostgreSQL 的支持,并且推送了最新的 Docker 镜像时发现,仓库的拉取数已经超过 2000 了。从最初建立仓库发布免费客服系统镜像开始,从 0 拉取到 1K 拉取足足用了近 四年,而从 1K 到 2K,只用了 一年。
我想一方面是由于前期系统本身不够好,二是我几乎没有做任何营销推广,完全是自然流量,所以前期拉取数很少。 但是随着系统越来越完善,越来越稳定,特别是对各种复杂奇葩情况的兼容性越来越好,稳定性达到了非常高的水平之后,用户之间的传播效应慢慢打开了,拉取数快速上升。最近几次用户来咨询问题,都是和我说:朋友介绍的。
仓库地址,可以免费拉取使用:https://hub.docker.com/r/iccb1013/linkup
1.png

支持 PostgreSQL 的契机

在客服系统的实际应用中,数据库往往是最核心的底层组件。它不仅决定了系统能否高效支撑百万级访客的实时交互,还影响着企业能否顺利做数据分析和跨系统集成。
过去,升讯威客服系统默认运行在 MySQL 之上,这让我们获得了轻量、易部署、生态成熟的优势。但随着企业客户规模不断扩大,特别是有用户开始对复杂数据分析提出了要求,于是连带提出一个问题:能不能支持 PostgreSQL。
这并不是一个偶然的事件。近几年,PostgreSQL 在全球范围内的使用率快速上升,许多和金融、电商、政企级项目,都将它视为“企业级开源数据库”的首选。它更严格的事务支持、更丰富的数据类型、以及强大的扩展能力,正好契合了升讯威客服系统在高并发客服场景下的性能和数据一致性要求。
因此,我在全面调研尝试(踩坑)之后,完成了对 PostgreSQL 的全面支持。这不仅意味着企业可以根据自身 IT 架构灵活选择数据库,更标志着升讯威客服系统在开放性和可扩展性上迈出了重要一步。
接下来,我会先对比一下 PostgreSQL 和 MySQL 的差异,然后带你看看在 C# 中如何快速接入 PostgreSQL。
PostgreSQL vs MySQL 对比

我们先来正面比较一下 PostgreSQL 和 MySQL。它们都是开源数据库里的“扛把子”,但在设计哲学、功能特性和使用场景上差别不小。
1. 数据一致性与事务模型

MySQL 在 InnoDB 引擎下已经能很好地满足 ACID(原子性、一致性、隔离性、持久性)的要求,但它在默认配置下有时候偏向“性能优先”。比如在某些情况下,MySQL 会允许“宽松模式”插入非法数据。
  1. -- 在 MySQL 中,如果 strict_mode 没开
  2. -- 这个插入语句会成功,只是把字符串 'abc' 转成 0
  3. INSERT INTO Orders (Id, Amount) VALUES (1, 'abc');
复制代码
PostgreSQL 则更“较真”,它直接拒绝这样的插入。
  1. -- PostgreSQL 会报错,提示类型不匹配
  2. INSERT INTO Orders (Id, Amount) VALUES (1, 'abc');
  3. -- ERROR: invalid input syntax for type numeric: "abc"
复制代码
这种“严格”让 PostgreSQL 在金融、医疗等对数据准确性要求极高的行业更受青睐。
2. 查询能力与高级特性

PostgreSQL 的查询语言更接近“数据库科研级别”的范畴。它不仅支持窗口函数、递归查询、CTE(公用表表达式),还支持 JSONB、全文检索、地理空间数据(PostGIS)。
举个例子,在升讯威客服系统中,我们可能要统计每个客服的平均响应时长,并且按照排名输出。
PostgreSQL 写法:
  1. SELECT AgentId,
  2.        AVG(ResponseTime) AS AvgResponse,
  3.        RANK() OVER(ORDER BY AVG(ResponseTime)) as Rank
  4. FROM ChatStats
  5. GROUP BY AgentId;
复制代码
MySQL 写法:
在 8.0 之后才支持窗口函数,但写法更受限制:
  1. SELECT AgentId, AvgResponse,
  2.        RANK() OVER (ORDER BY AvgResponse) as Rank
  3. FROM (
  4.     SELECT AgentId, AVG(ResponseTime) as AvgResponse
  5.     FROM ChatStats
  6.     GROUP BY AgentId
  7. ) t;
复制代码
虽然两者都能实现,但 PostgreSQL 更早就支持了这些功能。
3. JSON 与非结构化数据处理

现代客服系统常常需要存储大量半结构化的数据,比如聊天记录、AI 分析结果、访客上下文信息等。
在 MySQL 里,JSON 是作为一种数据类型支持的,但操作起来略显笨拙:
  1. SELECT JSON_EXTRACT(Messages, '$.content') AS Content
  2. FROM ChatHistory
  3. WHERE JSON_EXTRACT(Messages, '$.isVip') = true;
复制代码
在 PostgreSQL 里,JSONB 简直就是原生的“第一公民”:
  1. SELECT Messages->>'content' AS Content
  2. FROM ChatHistory
  3. WHERE (Messages->>'isVip')::boolean = true;
复制代码
甚至可以直接创建索引,大幅提高查询速度:
  1. CREATE INDEX idx_chat_jsonb ON ChatHistory USING gin (Messages);
复制代码
实际测试千万级聊天记录下,PostgreSQL 的 JSONB 索引查询能比 MySQL 快 30%~50%(当然,这个数据是我在本地压测得出的,环境不同结果也不同)。
4. 并发与锁机制

MySQL 的锁机制比较简单,InnoDB 使用行级锁,但在复杂并发场景下容易出现死锁或性能下降。
PostgreSQL 使用多版本并发控制(MVCC),它的事务隔离是通过保留数据的多个版本实现的,查询几乎不会被写操作阻塞。
简单来说:

  • 在 MySQL 中,一个大事务可能会卡住很多小查询。
  • 在 PostgreSQL 中,读和写大部分情况下能愉快并行。
这对于升讯威客服系统非常关键:你不能因为一个后台统计报表查询,就让前台客服的实时对话延迟。
5. 扩展能力与插件生态

PostgreSQL 的扩展能力很强,社区提供了大量插件。比如:

  • PostGIS:做地理位置分析;
  • pg_cron:在数据库里直接调度任务;
  • TimescaleDB:时序数据扩展,用于高频事件记录。
在 MySQL 里,扩展更多是通过外部系统实现。比如时序数据常常需要引入 InfluxDB。
6. 性能与基准测试

有人会说:“MySQL 更快!”也有人说:“PostgreSQL 更稳!”其实要看场景。
我们做了一个简单的压测:

  • 测试条件:单机 8 核 16G 内存,1000 万条聊天记录,100 并发查询。
  • 结果

    • MySQL 平均查询耗时:120ms
    • PostgreSQL 平均查询耗时:85ms
    • 在复杂 JSON 查询下,PostgreSQL 胜出;在简单单表查询下,两者几乎无差别。

结论:如果你的数据模型简单、查询场景单一,MySQL 足够;如果涉及复杂查询和高并发,PostgreSQL 更合适。
总体来看:

  • MySQL:简单好用,适合快速上线、轻量应用。
  • PostgreSQL:功能强大,适合高并发、大数据量、复杂业务逻辑。
接下来,我会进入实战环节,看看在 C# 中如何使用 PostgreSQL,让你快速上手。
在 C# 中使用 PostgreSQL

有了 PostgreSQL 的强大功能,接下来最实际的问题就是:在 C# 中如何使用它?
好消息是,这件事一点也不复杂。C# 社区已经有成熟的驱动和 ORM 支持,我们可以很快把客服系统跑在 PostgreSQL 上。下面我会从最基础的 Npgsql 驱动讲起,然后再介绍 Entity Framework Core 的玩法。
1. 安装驱动

在 .NET 环境下,PostgreSQL 的官方驱动就是 Npgsql。安装方法很简单,用 NuGet 就行:
  1. dotnet add package Npgsql
复制代码
安装完成后,你的项目就能和 PostgreSQL 直接对话了。
2. 基本连接与查询

最基本的连接和查询方式,就像用 SqlConnection 操作 SQL Server 一样:
  1. using System;
  2. using Npgsql;
  3. class Program
  4. {
  5.     static void Main()
  6.     {
  7.         var connString = "Host=localhost;Port=5432;Username=postgres;Password=123456;Database=kf";
  8.         using var conn = new NpgsqlConnection(connString);
  9.         conn.Open();
  10.         Console.WriteLine("PostgreSQL 连接成功!");
  11.         using var cmd = new NpgsqlCommand("SELECT * FROM Visitors LIMIT 5", conn);
  12.         using var reader = cmd.ExecuteReader();
  13.         while (reader.Read())
  14.         {
  15.             Console.WriteLine($"VisitorId: {reader.GetInt32(0)}, Name: {reader.GetString(1)}");
  16.         }
  17.     }
  18. }
复制代码
在这段代码里:

  • NpgsqlConnection 用来建立连接;
  • NpgsqlCommand 执行 SQL;
  • ExecuteReader() 遍历结果集。
是不是和 MySQL 的 MySql.Data.MySqlClient 几乎一样?
3. 参数化查询(防止 SQL 注入)

在客服系统里,经常要根据访客 ID 查会话记录,这时候一定要用参数化查询:
  1. using var cmd = new NpgsqlCommand("SELECT * FROM ChatHistory WHERE VisitorId = @id", conn);
  2. cmd.Parameters.AddWithValue("id", 2002);
  3. using var reader = cmd.ExecuteReader();
  4. while (reader.Read())
  5. {
  6.     Console.WriteLine(reader["Messages"]);
  7. }
复制代码
这种写法可以避免字符串拼接导致的 SQL 注入问题。
4. 插入 JSONB 数据

PostgreSQL 最爽的一点是 JSONB 字段。比如我们要保存一条聊天记录,直接插 JSONB:
  1. var sql = "INSERT INTO ChatHistory (VisitorId, Messages) VALUES (@vId, @msg::jsonb)";
  2. using var cmd = new NpgsqlCommand(sql, conn);
  3. cmd.Parameters.AddWithValue("vId", 2003);
  4. cmd.Parameters.AddWithValue("msg", "{"content": "Hello World", "time": "2025-09-29"}");
  5. cmd.ExecuteNonQuery();
复制代码
相比 MySQL 用 TEXT 存 JSON,PostgreSQL 的 JSONB 不仅存储更高效,还能直接查询字段。
5. 使用事务

在高并发场景下,我们经常需要确保多个 SQL 操作要么全部成功,要么全部失败。这就要用事务:
  1. using var transaction = conn.BeginTransaction();
  2. try
  3. {
  4.     var insertVisitor = new NpgsqlCommand("INSERT INTO Visitors (Id, Name) VALUES (@id, @name)", conn);
  5.     insertVisitor.Parameters.AddWithValue("id", 3001);
  6.     insertVisitor.Parameters.AddWithValue("name", "Tom");
  7.     insertVisitor.Transaction = transaction;
  8.     insertVisitor.ExecuteNonQuery();
  9.     var insertChat = new NpgsqlCommand("INSERT INTO ChatHistory (VisitorId, Messages) VALUES (@id, @msg::jsonb)", conn);
  10.     insertChat.Parameters.AddWithValue("id", 3001);
  11.     insertChat.Parameters.AddWithValue("msg", "{"content":"First chat!"}");
  12.     insertChat.Transaction = transaction;
  13.     insertChat.ExecuteNonQuery();
  14.     transaction.Commit();
  15. }
  16. catch
  17. {
  18.     transaction.Rollback();
  19.     Console.WriteLine("事务失败,已回滚");
  20. }
复制代码
这保证了访客表和聊天记录表的数据保持一致。
6. Entity Framework Core 支持

如果你习惯用 ORM,可以直接使用 EF Core 的 PostgreSQL Provider。
安装:
  1. dotnet add package Npgsql.EntityFrameworkCore.PostgreSQL
复制代码
配置 DbContext:
  1. using Microsoft.EntityFrameworkCore;
  2. public class KfContext : DbContext
  3. {
  4.     public DbSet<Visitor> Visitors { get; set; }
  5.     public DbSet<ChatHistory> ChatHistories { get; set; }
  6.     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  7.     {
  8.         optionsBuilder.UseNpgsql("Host=localhost;Database=kf;Username=postgres;Password=123456");
  9.     }
  10. }
  11. public class Visitor
  12. {
  13.     public int Id { get; set; }
  14.     public string Name { get; set; }
  15. }
  16. public class ChatHistory
  17. {
  18.     public int Id { get; set; }
  19.     public int VisitorId { get; set; }
  20.     public string Messages { get; set; } // 可以映射 JSONB
  21. }
复制代码
使用 LINQ 查询:
  1. using var db = new KfContext();
  2. var vipVisitors = db.Visitors
  3.     .Where(v => v.Name.Contains("VIP"))
  4.     .ToList();
  5. foreach (var v in vipVisitors)
  6. {
  7.     Console.WriteLine(v.Name);
  8. }
复制代码
这样我们就能用面向对象的方式操作 PostgreSQL,大幅简化了业务逻辑层的代码。
7. 性能小贴士


  • 连接池:Npgsql 默认开启连接池,别重复创建连接对象。
  • 批量插入:用 COPY 命令替代多次 INSERT,性能能快十倍以上。
  • 索引:对常用查询的字段加 GIN 或 BTREE 索引,尤其是 JSONB。
比如批量导入聊天记录:
  1. using (var writer = conn.BeginTextImport("COPY ChatHistory (VisitorId, Messages) FROM STDIN"))
  2. {
  3.     writer.WriteLine("1001\t{"content":"Hi"}");
  4.     writer.WriteLine("1002\t{"content":"Hello"}");
  5. }
复制代码
在 C# 中使用 PostgreSQL 非常简单:

  • Npgsql 提供了底层驱动,适合高性能场景;
  • Entity Framework Core 提供了 ORM 封装,适合快速开发;
  • JSONB、事务、窗口函数这些高级特性,都能在 C# 中无缝使用。
这意味着升讯威客服系统在 PostgreSQL 下不仅能跑得动,还能更快更强。
总结

写到这里,差不多也就收工了。总的来说,PostgreSQL 和 MySQL 各有千秋,就像两个性格完全不同的好朋友:

  • MySQL 简单直接,拿来就能跑,中小型项目的“快餐首选”;
  • PostgreSQL 稍微严谨一点,但给你更多花活,真要深挖功能,它能玩出很多高级姿势。
而在 C# 里,接 PostgreSQL 基本没什么学习成本,你会 MySQL,那换个驱动就行;你会 EF Core,那只要加个 NuGet 包就能跑。剩下的就是根据业务需要,想轻量就上 MySQL,想硬核就上 PostgreSQL。
升讯威在线客服系统在原有对 MySQL、SQLServer 支持的基础上,把 PostgreSQL 也安排上了,主要就是让大家少点纠结:就是一句话:用你喜欢的数据库。
独立者的产品成果

https://kf.shengxunwei.com
可全天候 7 × 24 小时挂机运行,网络中断,拔掉网线,手机飞行模式,不掉线不丢消息,欢迎实测。
访客端:轻量直观、秒级响应的沟通入口

访客端是客户接触企业的第一窗口,我精心打磨每一处交互细节,确保用户无需任何学习成本即可发起对话。无论是嵌入式聊天窗口、悬浮按钮,还是移动端自适应支持,都实现了真正的“即点即聊”。系统支持智能欢迎语、来源识别、设备类型判断,可自动记录访客路径并呈现于客服端,帮助企业更好地理解用户意图。在性能方面,访客端采用异步加载与自动重连机制,即使网络波动也能保障消息顺畅送达,真正做到——轻量不失稳定,简单不失智能。
2.png

客服端软件:为高效率沟通而生

客服端是客服人员的作战平台,我构建了一个专注、高效、响应迅速的桌面级体验。系统采用多标签会话设计,让客服可同时处理多组对话;访客轨迹、历史会话、地理位置、设备信息、来源渠道等关键信息一目了然,协助客服快速做出判断。内置快捷回复、常用文件、表情支持和智能推荐功能,大幅降低重复劳动成本。同时,系统还支持智能分配、会话转接、转人工、自定义状态等多种机制,保障团队协作流畅,让客服不仅能应对高峰,更能稳定交付满意度。
3.png

Web 管理后台:

Web 管理后台是企业对客服系统的“驾驶舱”,从接入配置、坐席管理,到数据统计、权限控制,一切尽在掌握。你可以灵活设置接待策略、工作时间、转接规则,支持按部门/标签/渠道精细分配访客,满足复杂业务场景。系统还内置访问监控、聊天记录检索、客服绩效统计、错失会话提醒等运营级功能,助力管理者洞察服务瓶颈,持续优化资源配置。支持私有化部署、分权限管理、日志记录与数据导出,为追求安全性与高可控性的企业,提供真正“掌握在自己手里的客服系统”。
4.png

希望能够打造: 开放、开源、共享。努力打造一款优秀的社区开源产品。

钟意的话请给个赞支持一下吧,谢谢~

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

前天 10:29

举报

谢谢分享,试用一下
您需要登录后才可以回帖 登录 | 立即注册