SQLiteBlobTooBigException: 写入数据库时行过大无法适应于CursorWindow

6

当我尝试向Android上的Sqlite数据库添加内容时,出现了上述错误。我正在使用Cloudant库。以下是堆栈跟踪:

2019-07-17 18:04:48.292 5522-5753/org.iprd.identity E/SQLiteQuery: exception: Row too big to fit into CursorWindow requiredPos=0, totalRows=1; query: SELECT docs.docid, docs.doc_id, revid, sequence, current, deleted, parent, json FROM revs, docs WHERE docs.docid=? AND revs.doc_id=docs.doc_id AND revid=? ORDER BY revs.sequence LIMIT 1
2019-07-17 18:04:48.302 5522-5522/org.iprd.identity E/DatabaseImpl: Failed to create document
    java.util.concurrent.ExecutionException: android.database.sqlite.SQLiteBlobTooBigException: Row too big to fit into CursorWindow requiredPos=0, totalRows=1
        at java.util.concurrent.FutureTask.report(FutureTask.java:123)
        at java.util.concurrent.FutureTask.get(FutureTask.java:193)
        at com.cloudant.sync.internal.documentstore.DatabaseImpl.get(DatabaseImpl.java:1084)
        at com.cloudant.sync.internal.documentstore.DatabaseImpl.create(DatabaseImpl.java:925)

我以前从未遇到过这个问题,但最近做了一个改变并开始使用第三方工具来获取一些图片。使用这个新工具,图片尺寸似乎比以前大得多。我通过对图像进行Base64编码将这些图像存储在我的数据库中作为字符串。然而,使用这个新工具,在存储图像时,我收到了上述异常。
我有一个单元测试,我试着使用从这张图片生成的字符串之一,但是它也会抛出一个错误,说字符串太大,甚至无法编译。
解决这个问题的最佳方法是什么?-
1.我想做的是将图像存储在设备本身中,只需在数据库中存储路径。 2.我们是否可以对图像进行某种压缩以减小其文件大小? 3.是否可能调整一些数据库设置以确保它可以处理更大的图像大小?
提前感谢!

将数据存储为二进制 blob 而不是 base-64 编码的字符串可以节省空间。但是如果我没记错,在 Android 版本的 sqlite 中,blob 或字符串的大小有一个相当小的上限。 - Shawn
7个回答

5
我想要做的是将图像存储在设备本身中,只在数据库中存储路径。 可能可以对图像进行压缩以减小其文件大小,但这并不一定能解决问题。即使您将图像压缩为2Mb(游标窗口的4Mb限制的一半),也只有1行会适合一个游标窗口。结果很可能是非常糟糕的性能,并且瓶颈不是SQLite而是处理游标。 不建议 另一个解决方案是将图像分成部分/块,可以在此处找到执行此操作的示例(请注意,未进行任何调整以确定256kb块大小)。请注意,响应时间被认为非常慢
  • 不建议使用,但如果性能不是问题,那么可能是一个解决方案。

不需要额外的压缩/解压开销的解决方案是将图像或超过设定大小的图像存储在文件系统(例如外部存储器)中,并存储路径或路径的一部分。

  • 后者选项,将小图像存储在数据库中,将大图像存储为文件,可以利用 比文件系统快35%)。

  • 混合路径/ blob 解决方案的示例在此处的答案中。

    • 注意,此示例可用作仅存储路径或混合解决方案的基础。
  • 建议使用

是否可以调整某些数据库设置以确保其可以处理更大的图像尺寸?

是的。

你可以考虑以下几点: - 优化你的查询。作为一个起点,参见 EXPLAIN QUERY PLANThe SQLite Query Optimizer Overview - 增加页面大小(注意限制) - 增加缓存大小 - 风险关闭同步设置 - 风险使用独占锁定 - 编译自己调整过的SQLite版本,应用编译时选项以适应需求。 - 进一步阅读上述链接,因为可能有其他适用的调整设置。

我尝试增加数据库的页面大小和缓存大小,但似乎对CursorWindow的大小没有任何影响。然而,CursorWindow的其中一个构造函数(https://developer.android.com/reference/android/database/CursorWindow#CursorWindow(java.lang.String,%20long))有一个窗口大小的参数。 也许可以利用这个来创建一个CursorFactory的实现,返回一个具有更大窗口大小的SQLiteCursor的扩展... - undefined

3
我解决了这个问题:当我从我的远程库中搜索图像时,我会检查它们的大小,如果它们大于500 kb,我会进行缩放处理。
produto.setImagem_img(imagemTratada(rs.getBytes("imagem_img")));

做神奇效果的方法:
private byte[] imagemTratada(byte[] imagem_img){

    while (imagem_img.length > 500000){
        Bitmap bitmap = BitmapFactory.decodeByteArray(imagem_img, 0, imagem_img.length);
        Bitmap resized = Bitmap.createScaledBitmap(bitmap, (int)(bitmap.getWidth()*0.8), (int)(bitmap.getHeight()*0.8), true);
        ByteArrayOutputStream stream = new ByteArrayOutputStream();
        resized.compress(Bitmap.CompressFormat.PNG, 100, stream);
        imagem_img = stream.toByteArray();
    }
    return imagem_img;

}

0

是的 - 这是一个真正的问题。但我已经为此实施了一个修复方案。我们基本上在我们的文档中使用生物识别图像。现在我所做的是:将单个生物识别实体(例如指纹)存储在单独的文档中。拥有一个包含该文档ID的主文档。

例如:我们为一个人捕获拇指和食指。我们存储2个文档以存储它们 - 让我们称它们为thumb.json和index.json。这些分别具有文档ID为thumb_100和index_100。现在,我们有一个主生物识别文档,看起来像:

"thumb": "thumb_100" "index": "index_100"

这有助于我们从主文档中获取拇指和食指印记。更重要的是,它确保单个行大小不超过限制或引起之前看到的异常。


0
我发现如何使用 length() 和 substr() 仅请求 1MB 的数据(CursorWindow 的最大值为2MB),也许会有所帮助(万一用户遇到此问题,您需要避免异常)。另外,您可以使用 context.getContentResolver().query() 并只包含相关列的选择,这样您就不会加载与其他列相关的数据(并节省一些空间)。否则,我建议您采用您提出的第一种选项,即将文件存储在内部存储器中,并在数据库中存储文件路径。

0
如果您想将图像保存为字节数组在SQLite或Room中,您应该压缩字节数组。
就像这样。
Bitmap bmp = intent.getExtras().get("data");
ByteArrayOutputStream stream = new ByteArrayOutputStream();
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
byte[] byteArray = stream.toByteArray();

这样做行不通,因为Bitmap.CompressFormat.PNG会忽略quality参数 - 链接 - Warle

0
我最近遇到了同样的问题,在进行了相当多的研究之后,我找到了解决方法:
将以下代码添加到你的MainApplication.onCreate方法中。
try {
  Field field = CursorWindow.class.getDeclaredField("sCursorWindowSize");
  field.setAccessible(true);
  field.set(null, 100 * 1024 * 1024); // 100MB is the new size
} catch (Exception e) {
  if (BuildConfig.DEBUG) {
    e.printStackTrace();
  }
}

你需要以下的导入:
import android.database.CursorWindow;
import java.lang.reflect.Field;

感谢@bashen1这个github问题中发布了这个解决方案。

-2

将图像存储在设备上的应用程序私有存储空间中,并将该图像路径存储在数据库中。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接