-
Notifications
You must be signed in to change notification settings - Fork 281
Open
Description
让AI查询资料时崩溃,重启该条对话整个丢失,记得崩溃前AI似乎在读取网页
下面是用AI读取日志后生成的ISSUE
Bug: SQLiteBlobTooBigException - 聊天消息加载失败
概述
在加载聊天消息时,出现 SQLiteBlobTooBigException 异常,导致消息无法正常加载。
错误信息
E/ChatHistoryManager: 加载聊天消息失败
android.database.sqlite.SQLiteBlobTooBigException: Row too big to fit into CursorWindow requiredPos=1, totalRows=2
at android.database.sqlite.SQLiteConnection.nativeExecuteForCursorWindow(Native Method)
at android.database.sqlite.SQLiteConnection.executeForCursorWindow(SQLiteConnection.java:1055)
at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:873)
at android.database.sqlite.SQLiteQuery.fillWindow(SQLite.java:69)
at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:153)
at android.database.sqlite.SQLiteCursor.onMove(SQLiteCursor.java:123)
at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:269)
at android.database.AbstractCursor.moveToNext(AbstractCursor.java:301)
at com.ai.assistance.operit.data.dao.MessageDao_Impl$15.call(MessageDao_Impl.java:398)
...
复现路径
- 应用启动时正常初始化(启动耗时约23ms)
- 数据库预加载显示现有聊天数:14
- 切换到某个聊天会话时触发消息加载
- 抛出异常,消息加载失败
- 界面显示消息数为 0
环境信息
- 应用版本: 1.9.1+12
- Android SDK: 36
- 设备: Android 设备
- 日期: 2026-03-10
根因分析
技术原因
- CursorWindow 容量限制: Android 的 CursorWindow 默认缓冲区大小约为 2MB
- 单行数据过大: 某条聊天消息的数据量超过了此限制
- 数据存储方式: Room DAO 执行查询时,尝试将整行数据加载到 CursorWindow 中失败
可能触发的场景
- AI 回复了超长文本(如代码分析、日志输出、长篇文档)
- 消息中包含大量 Base64 编码的图片数据
- 消息附件被序列化后存入数据库字段
- 多轮对话累积导致单条消息上下文过长
影响范围
- 用户无法查看该聊天的历史消息
- 严重情况下可能影响整个应用的聊天功能
- 数据无法恢复(除非手动修复数据库)
建议的解决方案
方案一:分块存储(推荐)
将长消息拆分成多个 chunk 存储:
@Entity(tableName = "message_chunks")
data class MessageChunk(
val messageId: String,
val chunkIndex: Int,
val content: String,
val totalChunks: Int
)方案二:外部存储
大型内容(如长文本、图片数据)存储为独立文件,数据库仅保存引用路径:
@Entity(tableName = "messages")
data class Message(
val id: String,
val contentRef: String?, // 文件路径引用,用于大内容
val contentPreview: String, // 前 N 字符预览
val contentSize: Long, // 内容大小标记
val isLarge: Boolean // 是否为大内容标记
)方案三:分页查询优化
在 DAO 层使用分页加载,避免一次性加载大数据:
@Query("SELECT * FROM messages WHERE chatId = :chatId LIMIT :limit OFFSET :offset")
suspend fun getMessagesPaged(chatId: String, limit: Int, offset: Int): List<Message>方案四:内容压缩
对超过阈值的内容进行压缩存储:
// 存储时
if (content.length > THRESHOLD) {
val compressed = GzipUtil.compress(content)
// 存储压缩后的数据,并标记 isCompressed = true
}
// 读取时
if (message.isCompressed) {
content = GzipUtil.decompress(message.content)
}临时解决方案
用户层面
- 删除问题聊天记录
- 清除应用数据重新开始
- 定期清理历史对话,避免单条消息过长
开发者层面
- 增加 try-catch 处理,避免崩溃
- 跳过问题消息,加载其他正常消息
- 提供数据导出/修复工具
相关文件
MessageDao.kt/MessageDao_Impl.javaChatHistoryManager.ktChatHistoryDelegate.kt
优先级
高 (High) - 影响核心功能,用户可能丢失聊天数据
标签
bug database sqlite room chat data-loss
补充信息
如果选择方案一(分块存储),需要进行数据迁移:
- 检查现有消息大小
- 将超过阈值的消息拆分成 chunk
- 更新查询逻辑以支持 chunk 合并
需要考虑的边界情况:
- 合并 chunk 时的顺序保证
- 部分 chunk 丢失的处理
- 搜索功能的兼容性
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels