Posts

Showing posts with the label 数据库

A New Collection of Thoughtful Learning Apps — Now Available on iOS & Android

Image
I’m excited to share a set of mobile apps I’ve recently completed and published on both the Google Play Store and the Apple App Store. These apps are designed with a simple goal in mind: to make meaningful, structured content more accessible, whether you’re studying theology or improving your English vocabulary. 📱 Now Available on Both Platforms All apps are live and available for download: Google Play Developer Page: https://play.google.com/store/apps/dev?id=5835943159853189043 Apple App Store Developer Page: https://apps.apple.com/ca/developer/q-z-l-corp/id1888794100 📖 Theology & Confession Study Apps For those interested in Reformed theology and classical Christian teachings, I’ve developed a series of apps that present foundational texts in a clean, focused reading format: The Belgic Confession Canons of Dort Heidelberg Catechism Westminster Shorter Catechism Each app is designed to provide a distraction-free experience, making it easier to read, reflect, and revisit these im...

解决数据库“value too long for column”错误:Java中基于字节的UTF-8字符串截断

Image
解决数据库“value too long for column”错误:Java中基于字节的UTF-8字符串截断 如果你在 Java 后端开发中遇到过 “value too long for column” 的数据库错误,那么问题的根本原因可能并不像表面看起来那么简单。 本文将通过一个真实的生产事故,讲清楚 UTF-8 编码 带来的坑,以及为什么 字符串长度不等于字节长度 ,并提供一个 安全按字节截断字符串 的正确实现方式。 生产问题复盘 我们在生产环境遇到如下错误: value too long for column 数据库字段限制: 50 bytes 输入字符串长度: 53 个字符 代码已做处理:截断为 50 个字符 看起来完全没问题,但插入数据库时依然失败。 根本原因:UTF-8 编码 字符数 ≠ 字节数 在 UTF-8 编码中: 英文字符(ASCII)→ 1 字节 中文字符 → 通常 3 字节 Emoji → 4 字节 因此,一个 50 个字符的字符串,很可能超过 50 字节。 错误的实现方式 常见代码如下: if (value.length() > 50) { value = value.substring(0, 50); } 这只能保证字符数,而无法保证字节数。 更严重的是,如果直接按字节截断,还可能: 截断多字节字符 生成非法 UTF-8 字符串 出现乱码或替换字符(�) 正确解决方案:按字节安全截断 正确做法必须满足: 遵守数据库的 字节限制 保证结果是 合法 UTF-8 字符串 不能截断一个字符的一部分 Java 实现(UTF-8安全截断) public static String truncateUtf8(String value, int maxBytes) { if (value == null) return null; byte[] bytes = value.getBytes(StandardCharsets.UTF_8); if (bytes.length <= maxBytes) return valu...

一次数据清理引发的教训:缺失外键索引导致 DELETE 性能问题

Image
一次数据清理引发的教训:缺失外键索引导致 DELETE 性能问题 最近在做一次数据清理(data purge)时,我遇到了一个之前没有预料到的问题。 从业务逻辑上看,这只是一个很普通的 DELETE 操作,根据条件删除历史数据而已。 但实际执行时, DELETE 语句几乎卡住了 。 没有报错,看起来也在运行,但迟迟没有完成。 问题出在哪里? 在 DBA 的帮助下,我们开始从数据库层面分析这个问题。 最终发现,真正的原因是: 部分外键(Foreign Key)字段缺少对应的索引。 在 Oracle 中,当对父表执行 DELETE 操作时,数据库需要检查子表中是否存在 引用该记录的外键数据。 如果外键列没有索引,Oracle 就只能对子表做全表扫描。 当数据量变大时,这种全表扫描会极大地拖慢 DELETE 操作, 甚至导致长时间锁表,看起来就像 SQL “卡住”了一样。 解决方案 在确认问题后,我们为所有缺失的外键字段补充了索引(涉及多个关联表)。 效果非常明显: DELETE 操作执行速度大幅提升 锁定时间明显缩短 数据库整体负载显著下降 SQL 没变,数据没变,只是补了索引,结果却完全不同。 如何定位缺失的外键索引 DBA 使用下面的 SQL 来检查哪些外键约束没有对应的索引。 以下示例中,schema 名、表名和索引名均已做匿名处理。 WITH fk_constraints AS ( SELECT c.table_name, c.constraint_name AS fk_name, LISTAGG(c.column_name, ', ') WITHIN GROUP (ORDER BY c.position) AS fk_columns FROM dba_cons_columns c JOIN dba_constraints k ON k.constraint_name = c.constraint_name WHERE k...

为什么 Vercel 部署后 PostgreSQL 仍然存在 Idle 连接

Image
为什么 Vercel 部署后 PostgreSQL 仍然存在 Idle 连接 如果你在 Vercel 上运行 Next.js , 并且直接连接 PostgreSQL ,你很可能遇到过下面的错误: remaining connection slots are reserved for roles with the SUPERUSER attribute 这个问题通常在重新部署后更容易出现,即使你的应用流量并不高。 常见现象 PostgreSQL 中存在大量 idle 状态的连接 Vercel 实例已经关闭,但连接仍未释放 新请求无法获取数据库连接 手动终止连接只能暂时缓解问题 真正的原因 这是 Serverless 架构的正常行为 。 Vercel 的工作方式大致如下: Vercel 根据请求启动多个 Serverless 实例 每个实例都会创建自己的数据库连接或连接池 实例结束后,无法保证数据库连接被优雅关闭 PostgreSQL 会继续保留这些连接直到超时 结果就是: 应用已经结束,但数据库连接仍然存在 。 为什么会导致连接数耗尽 在托管 PostgreSQL 服务中,一部分连接被保留给系统和 SUPERUSER。 当 Serverless 实例频繁创建连接时,很容易用完普通用户可用的连接数, 从而触发该错误。 这不是数据库 bug,而是架构不匹配导致的问题。 PgBouncer 如何解决 PgBouncer 位于应用和数据库之间: Vercel → PgBouncer → PostgreSQL 应用连接 PgBouncer,而不是直接连接数据库 PgBouncer 使用少量真实数据库连接服务大量请求 Serverless 实例关闭时,连接会被立即回收 PostgreSQL 不再堆积 idle 连接 对于 Serverless 场景,推荐使用 transaction pooling 模式。 推荐配置 启用 PgBouncer 应用连接 PgBouncer 的 host 和 port Node.js 连接池设置为 1–2 idleTimeo...