SQLiteException
转载请注明出处: https://blog.csdn.net/hx7013/article/details/143198862
在使用 Room 或其他基于 SQLite 的 ORM 框架时,批量操作如 IN
或 NOT IN
查询可能会触发 android.database.sqlite.SQLiteException: too many SQL variables
异常。该问题源于 SQLite 的 IN
和 NOT IN
子句会将数据转换为 ... IN (?, ?, ? ...)
的形式,而 SQLite 对可绑定的参数数量是有限制的。在 Android 系统中,这一限制是由系统内置的 SQLite 版本所固化,无法直接修改,除非你自行编译并替换 SQLite 库。因此,当查询的条件过多时,超过了这个限制,就会抛出该异常。
一、解决办法
由于 SQLite 内部对于参数量的限制本身是相对较高的(999或32766),大部分能引发此问题的场景通常是在执行 UPDATE
或 DELETE
操作时。
1. 修改数据库语句
可以通过优化查询语句来减少参数数量,特别是在使用 IN
或 NOT IN
的查询中。例如,提炼参数或改为单调执行循环调用,避免 IN
的使用等。由于每个项目的查询需求不同,具体的修改方式需根据实际情况进行,此处不做深入讨论。
2. 分批执行
当参数数量过多时,可以将大批量的操作拆分为多个小批量的操作。以下举例说明如何分批处理:
kotlin
@Query("DELETE FROM sync_data WHERE uuid IN (:uuids)")
suspend fun delete(uuids: List<String>): Int
@Query("UPDATE sync_data SET is_delete = 1, delete_time = :deleteTime WHERE uuid IN (:uuids)")
suspend fun softDelete(uuids: List<String>, deleteTime: Long = System.currentTimeMillis()): Int
上面两个查询分别执行物理删除和逻辑删除操作。为了代码简洁和执行效率,我们通常会过滤出需要删除的 uuid
,并通过 IN
执行批量操作。然而,如果 uuids.size > 999
,SQLite 会抛出 android.database.sqlite.SQLiteException: too many SQL variables
异常。在这种情况下,可以使用分批执行的方式避免异常:
调用示例:
kotlin
internal const val SQL_BATCH_SIZE = 500
...
/**
* bolg: https://blog.csdn.net/hx7013
*/
private suspend fun softDeleteByUuids(newUuids: Set<String>): Boolean = try {
// 加载未删除的 UUID,并过滤掉新 UUID
val overdueUuids = syncDataDao.loadNonDeletedUuids().filter { it !in newUuids }
if (overdueUuids.isNotEmpty()) {
// 使用 chunked 将列表拆分,每批执行软删除操作
val deleteRows = overdueUuids.chunked(SQL_BATCH_SIZE).sumOf { syncDataDao.softDelete(it) }
// 比较实际删除的行数是否与期望一致
overdueUuids.size == deleteRows
} else {
true
}
} catch (e: Exception) {
e.printStackTrace()
false
}
在这个例子中,使用 chunked
方法将 List
按照指定大小分批处理,每批执行数据库操作,并通过 sumOf
计算总的影响行数。这种方式避免了参数过多的问题,并确保在大数据集的情况下也能顺利执行批量操作。
其它SELECT
、DELETE
等逻辑类似。
二、问题根源
其实,该问题不仅限于 Android 环境,在所有使用 SQLite 的场景中都会出现。
在 SQLite 中,主机参数 是 SQL 语句中的占位符,通过 sqlite3_bind_XXXX()
方法进行绑定。常见的主机参数格式包括问号 (?
)、命名参数(以 :
、$
或 @
为前缀),以及编号参数(如 ?123
)。
每个 SQL 语句中的主机参数都会被分配一个编号,默认从 1 开始递增。如果使用 ?123
形式,则参数的编号是问号后的数字。需要注意的是,SQLite 为每个主机参数分配内存,编号从 1 到最大的参数编号。如果 SQL 语句中包含类似 ?1000000000
这样编号巨大的参数,会导致大量内存消耗,可能会使主机资源耗尽。
为避免这种问题,SQLite 通过 SQLITE_MAX_VARIABLE_NUMBER
限制了单个 SQL 语句中主机参数的最大数量。如果需要修改该值,可以在运行时使用 sqlite3_limit(db, SQLITE_LIMIT_VARIABLE_NUMBER, size)
来调整最大允许的参数数量。
这个默认的大小在 SQLite 3.32.0 之前的版本(2020-05-22 发布),主机参数的默认最大值为 999
;而在 3.32.0 及之后的版本中,这一限制提升到了 32766
。
如果有定制需求,可以自行编译SQLite,修改SQLITE_LIMIT_VARIABLE_NUMBER
参数。
详细可以查看:
https://www.sqlite.org/limits.html 第9节或 https://www.sqlite.org/c3ref/c_limit_attached.html#sqlitelimitvariablenumber 的说明。
转载请注明出处: https://blog.csdn.net/hx7013/article/details/143198862