用途
该技能用于处理一类数据一致性问题:某些附属行(satellite rows)仅因另一行上的某个状态而存在,例如关注创作者的提醒、订阅的发行追踪行、打开编辑器的草稿行。当该状态结束时,这些附属行已无存在理由,但数据库 schema 本身并不知道这种依赖关系。技能的核心主张是:在状态发生变更的同一处执行删除,而不是留给后续的批量清扫。
主要能力
技能给出了一种在状态转换点清理附属行的做法,并围绕它总结了若干陷阱与验证手段。
示例代码展示了在更新关注状态的方法中,当状态变为 0(取消关注)时,同步删除该创作者的提醒行与发行追踪行:
// adapted
override suspend fun updateFollowedStatus(artistId: String, followedStatus: Int) {
localDataSource.updateFollowed(followedStatus, artistId)
if (followedStatus == 0) {
localDataSource.deleteNotificationsByArtistId(artistId)
localDataSource.deleteReleaseTrackingRow(artistId)
}
}
在加入这段逻辑之前,取消关注只是翻转一个标志位,其他什么都不做。每个曾被取消关注的创作者的附属行都会永久留存,没有任何代码路径会删除它们。
使用方式与陷阱
标志位翻转不是删除,不会触发级联。 ON DELETE CASCADE 只在父行被删除时触发。此处父行仍然存在,只是状态变了,因此引擎没有可提供的机制,也没有可配置的东西。如果某个状态可以被关闭,那么针对它的清理就是必须由应用代码编写的逻辑。
基于“父行缺失”的批量清扫会漏掉仍然存在的父行。 缓存清扫不会删除每一个离开该状态的父行——它会放过那些仍然支撑其他保留数据的父行;而形如“删除父行已消失的行”的附属行规则,永远触及不到那些被放过、但仍处于未关注状态的父行的附属行。应基于状态而非行的存在性来设计清扫,并在父行清扫之后运行,使被删除和被放过的父行都被覆盖:
DELETE FROM notification
WHERE artistId NOT IN (SELECT artistId FROM artist WHERE followed = 1);
-- artistId is declared NOT NULL here; over a nullable column this needs IS NOT NULL
-- (that failure has a sibling skill: sql-not-in-nullable-trap)
在被清扫放过的父行中,未关注的现在失去其附属行,已关注的保留其附属行——这正是期望的结果,且只有在谓词询问的是关注状态而非行是否存在时才成立。
发布转换点清理并不能修复已有数据库。 在修复之前取消关注的每个用户仍然带着那些行。应永久保留批量清扫作为这些数据的迁移路径,并在注释中说明——否则一旦转换点清理存在,清扫看起来就多余了,可能被人删除。
基于新值分支,而不是基于“值发生了变化”。 if (followedStatus == 0) 只在状态真正结束时运行清理。基于“变化”的分支也会在关注方向上触发,而基于“之前是 1”的分支需要一次本来不必做的读取。
把它放在状态变更之后,而不是按钮之后。 如果清理逻辑位于某个屏幕的点击处理器中,那么该列的所有其他写入方——设置开关、同步过程、撤销、批量操作——都会静默跳过它。它应属于拥有该状态转换的单一仓库方法,且每个调用方都必须经由该方法。
不要把它扩展到用户可以撤销的删除。 只有那些在转换后确实毫无价值的行才符合条件。用户重新关注时合理期望恢复的任何内容(自己的笔记、自己设置的已读/未读标记)都是数据,不是附属行——应保留它们,否则清理会变成一种数据丢失缺陷,只在往返操作后才显现。
验证方式
统计滞留行数。 在早于修复的数据库上,把清扫的谓词作为 SELECT COUNT(*) 运行。在只运行过修复版本构建的数据库上,任何取消关注之后它都应为零;在较旧的数据库上它是正数且从不减少——这一差异就是转换点清理确实在起作用的证明。
在相信覆盖完整之前,找出状态列的每一个写入方。 搜索对该列的赋值以及对该更新方法的调用,确认每条路径都能到达清理逻辑。这是能抓住上述“点击处理器放置”问题的检查。
手工做一次往返。 关闭状态,检查附属表,再重新打开,再次检查。两半都重要:第一半证明删除确实运行了,第二半证明你没有删除用户期望保留的东西。
在长时间会话中观察表的行数,而不只是跨单个操作。 无界增长的表正是引发此问题的症状,也是修复前唯一可观察到的症状。
适用场景与限制
该技能适用于存在“父行状态决定附属行存亡”关系的应用数据层,尤其是移动端本地数据库或类似场景。资料未提供具体支持的平台、语言版本、依赖库或安装命令。技能本身以 Kotlin 代码片段和 SQL 语句示例呈现,未说明其作为可安装工具或库的形态。