【问题标题】:SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032)SQLiteReadOnlyDatabaseException:尝试写入只读数据库(代码 1032)
【发布时间】:2016-05-07 13:32:42
【问题描述】:

所以在极少数情况下,我会看到“尝试写入只读数据库”消息,但我无法弄清楚问题出在哪里。我将从我的 logcat 中的堆栈跟踪开始......正如您从时间戳中看到的那样,我在尝试写入前仅 1 毫秒检查 db.isReadOnly()。 (isOpen=true, readOnly=false)

01-29 13:47:49.115: D/AWT(11055): #479.Got writable database (230537815): isOpen: (true) isReadOnly: (false) inTransaction: (false)
01-29 13:47:49.116: D/AWT(11055): #479.in transaction: Got writable database (230537815): isOpen: (true) isReadOnly: (false) inTransaction: (true)
01-29 13:47:49.116: E/SQLiteLog(11055): (1032) statement aborts at 15: [INSERT INTO Events(col1,col2,col3,col4) VALUES (?,?,?,?)] 
01-29 13:47:49.117: E/SQLiteDatabase(11055): Error inserting data="scrubbed"
01-29 13:47:49.117: E/SQLiteDatabase(11055): android.database.sqlite.SQLiteReadOnlyDatabaseException: attempt to write a readonly database (code 1032)
01-29 13:47:49.117: E/SQLiteDatabase(11055):    at android.database.sqlite.SQLiteConnection.nativeExecuteForLastInsertedRowId(Native Method)
01-29 13:47:49.117: E/SQLiteDatabase(11055):    at android.database.sqlite.SQLiteConnection.executeForLastInsertedRowId(SQLiteConnection.java:780)
01-29 13:47:49.117: E/SQLiteDatabase(11055):    at android.database.sqlite.SQLiteSession.executeForLastInsertedRowId(SQLiteSession.java:788)
01-29 13:47:49.117: E/SQLiteDatabase(11055):    at android.database.sqlite.SQLiteStatement.executeInsert(SQLiteStatement.java:86)
01-29 13:47:49.117: E/SQLiteDatabase(11055):    at android.database.sqlite.SQLiteDatabase.insertWithOnConflict(SQLiteDatabase.java:1471)
01-29 13:47:49.117: E/SQLiteDatabase(11055):    at android.database.sqlite.SQLiteDatabase.insert(SQLiteDatabase.java:1341)
01-29 13:47:49.117: E/SQLiteDatabase(11055):    at com.company.DbHelper.insertBatch(EventsDbHelper.java:174)
01-29 13:47:49.117: D/AWT(11055): #479.finalizing transaction: Got writable database (230537815): isOpen: (true) isReadOnly: (false) inTransaction: (true)
01-29 13:47:49.118: W/SQLiteLog(12120): (28) file unlinked while open: /data/user/0/com.company.app/databases/MyDatabase.db

来自我的来源:

public void insertBatch(LinkedList<WriteQueue.DatabaseRecord> writeQueue) throws Exception {
    Log.d("AWT", "EventsDbHelper->insertBatch()");

    if (writeQueue == null) {
        return;
    }

    Iterator<DatabaseRecord> it = writeQueue.iterator();

    SQLiteDatabase db = this.getWritableDatabase();

    Log.d("AWT", String.format("Got writable database (%s): isOpen: (%s) isReadOnly: (%s) inTransaction: (%s)",
            db.hashCode(), db.isOpen(), db.isReadOnly(), db.inTransaction()));

    try {
        db.beginTransaction();

        while (it.hasNext()) {
            DatabaseRecord record = it.next();

            ContentValues initialValues = new ContentValues();
            initialValues.put(col1, val1);
            initialValues.put(col2, val2);
            initialValues.put(col3, val3);
            initialValues.put(col4, val4);

            Log.d("AWT", String.format("in transaction: Got writable database (%s): isOpen: (%s) isReadOnly: (%s) inTransaction: (%s)",
                    db.hashCode(), db.isOpen(), db.isReadOnly(), db.inTransaction()));

            db.insert(DBTBL, null, initialValues);
        }
        Log.d("AWT", String.format("finalizing transaction: Got writable database (%s): isOpen: (%s) isReadOnly: (%s) inTransaction: (%s)",
                db.hashCode(), db.isOpen(), db.isReadOnly(), db.inTransaction()));
        db.setTransactionSuccessful();

    } catch (Exception e) {
        Log.e(TAG, "Error inserting batch record into database.", e);
    } finally {
        try {
            db.endTransaction();
            db.close();
        } catch (Exception e) {
            Log.e(TAG, Global.DB_ERROR, e);
        }
    }
}

所以我认为可能发生了两件事之一。

  1. 在检查和尝试批量插入之间的 1 毫秒内,数据库确实被关闭/设置为“只读”。
  2. isReadOnly 在骗我,没有准确报告数据库的状态。
  3. 数据库在我的插入过程中被删除了!请参阅上面日志的最后一行。我为 SQLite 打开了严格的日志记录并注意到了上述情况。我怀疑第三方库可能会删除我的所有数据库。

虽然目前没有想法,但我愿意尝试任何建议。

【问题讨论】:

  • 在批量插入完成后,在 dbHelper 类的其他地方完成关闭。我也取消了所有关闭呼叫,但这对这种行为没有任何影响。
  • 也添加了(参见更新的代码和 logcat)。没有任何区别。
  • 我会尝试一个完全干净的项目,只要有足够的支持就可以插入数据库。这将有助于隔离问题。我还尝试设置一个标志,以便该呼叫一次只允许一个呼叫。您可能会在第一个事件完成之前开始第二个事件。
  • 我也遇到了类似类型的异常,并且严重坚持了下来。您找到任何解决方案还是找到原因?

标签: android sqlite android-sqlite


【解决方案1】:

因此,乍一看,问题的根本原因似乎是第三方库。除非我弄错了,否则 Mobeix 的 Tagit 会在应用程序启动时删除数据库。我添加了一些详细的 SQLite 日志记录,包括这些策略:

StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
    .detectLeakedSqlLiteObjects()
    .detectLeakedClosableObjects()
    .penaltyLog()
    .penaltyDeath()
    .build());

我在日志中注意到我的数据库在我创建并打开它后被取消链接。更详细的日志记录表明它发生在初始化 Mobeix 库时。有问题的违规行:

01-29 13:47:49.118: W/SQLiteLog(12120): (28) file unlinked while open: /data/user/0/com.company.app/databases/MyDatabase.db

所以我的数据库文件没有链接。诡异的。对 getWritableDatabase() 的下一次调用会再次重新创建它,然后一切正常,直到应用程序被杀死并重新启动,此时它被删除并重新创建。

如果我确切地找出导致取消链接的原因,我会更新此内容。

【讨论】:

  • 我也遇到了同样的问题,你找到解决办法了吗?
  • 没有。我无法阻止它删除我的数据库,它只发生在我的应用程序第一次启动时。所以我不得不添加一堆检查以确保它在那里、打开和可写。
  • 我从来没有找到删除我的数据库的答案。我能做的最好的事情就是在我每次使用它之前确保它存在,并在它被删除时做出相应的反应。
【解决方案2】:

我被或多或少完全相同的问题所困扰,我发现了一个有意义的公开缺陷......

https://code.google.com/p/android/issues/detail?id=174566

我的解决方法——尽管不是最好的解决方案——是永远不要自己进行数据库修订并跟踪它,因此永远不要调用onUpgrade(),并在更新应用程序时手动进行升级。

或者,如果您有一个只读的小型数据库,您可以在DBHelper 类中的每个onCreate() 上触发资产中数据库的副本,但是如果文件系统已满,这可能会产生不必要的问题,所以只能这样做这同时寻找更好的解决方案。

@Override
public void onCreate(SQLiteDatabase db) {
    // Workaround for Issue 174566
    myContext.deleteDatabase(DB_NAME);
    try {
        copyDataBase();
    }
    catch(IOException e) {
        System.out.println("IOException " + e.getLocalizedMessage());
    }
}

我的应用程序现在可以根据我的解决方法进行升级,并通过判断自最初提出此缺陷以来的时间长短,它可能永远无法修复...

很抱歉,这不是问题的完整解决方案,但至少是前进的方向。

【讨论】:

  • 这没有提供问题的答案。要批评或要求作者澄清,请在他们的帖子下方发表评论 - 您可以随时对自己的帖子发表评论,一旦您earn足够reputation,您就可以comment on any post。如果您有一个相关但不同的问题,ask a new question 参考这个问题,如果它有助于提供上下文。
  • 我已经更新了我的答案,以便为原始问题提供一些答案。我同意这可能是一条评论,但现在它至少提供了一种解决问题的方法。
  • 您好 Jens,很高兴这对您有用,但我的问题不仅仅发生在 onUpgrade() 期间。它发生在一批写入的中间,根本没有调用 onUpgrade()。根本原因是文件被...删除了。还不确定是什么。我的解决方法是在每次批量插入之前调用 getWriteableDatabase() 。不是最佳解决方案 - 只是一个 hacky 解决方法,直到我弄清楚是什么原因造成的。
【解决方案3】:

我也遇到过类似的问题。但是,作为还原的一部分,我故意删除了当前数据库。

我认为正在发生的事情是 SQLite 将数据库标记为只读,以提供针对 file unlinked while open: 的保护。

还原后,任何更新都会失败并显示attempt to write a readonly database (code 1032)

我的解决方案是重新实例化 DBHelper。我通过添加我调用的reopen 方法来完成此操作。

例如

    public static void reopen(Context context) {
        instance = new DBHelper(context);
    }

然后我使用

调用/调用它
                if(copytaken && origdeleted && restoredone) {
                    DBHelper.reopen(context);
                    DBHelper.getHelper(context).expand(null,true);
                }

对 expand 方法的调用是我对 onUpgrade/versions 的等效/getaround。它根据与实际数据库进行比较的伪模式添加表和列。

完整的 DBHelper 是:-

/**
 * DBHelper
 */
@SuppressWarnings("WeakerAccess")
class DBHelper extends SQLiteOpenHelper {

    private static final String LOGTAG = "SW-DBHelper";
    private static final String DBNAME = DBConstants.DATABASE_NAME;
    private static final String dbcreated =
            "001I Database " + DBNAME + " created.";
    private static final String dbunusable =
            "002E Database " + DBNAME +
            " has been set as unusable (according to schema).";
    private   static final String dbexpanded =
            "003I Database " + DBNAME + " expanded.";
    private static final String dbexpandskipped =
            "004I Database " + DBNAME + " expand skipped - nothing to alter.";
    private static final String dbbuildskipped =
            "005I Database" + DBNAME + " build skipped - no tables to add";
    public static final String THISCLASS = DBHelper.class.getSimpleName();

    /**
     * Consrtuctor
     *
     * @param context activity context
     * @param name    database name
     * @param factory cursorfactory
     * @param version database version
     */
    DBHelper(Context context, @SuppressWarnings("SameParameterValue") String name, @SuppressWarnings("SameParameterValue") SQLiteDatabase.CursorFactory factory, @SuppressWarnings("SameParameterValue") int version) {
        super(context, name, factory, version);
    }

    /**
     * Instantiates a new Db helper.
     *
     * @param context the context
     */
    DBHelper(Context context) {
        super(context, DBConstants.DATABASE_NAME, null, 1);
    }

    private static DBHelper instance;

    /**
     * Gets helper.
     *
     * @param context the context
     * @return the helper
     */
    static synchronized DBHelper getHelper(Context context) {
        if(instance == null) {
            instance = new DBHelper(context);
        }
        return instance;
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        expand(db, false);
    }


    @Override
    public void onUpgrade(SQLiteDatabase db, int oldversion, int newversion) {

    }

    /**
     * expand create database tables
     *
     * @param db             SQLIte Database, if null then instance is used
     * @param buildandexpand to attempt both create and expand
     */
    void expand(SQLiteDatabase db, boolean buildandexpand) {

        String mode = "Create Mode.";
        if (buildandexpand) {
            mode = "Expand Mode.";
        }
        String msg = mode;
        String methodname = new Object(){}.getClass().getEnclosingMethod().getName();
        LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
        // if no database has been passed then get the database
        if(db == null) {
            db = instance.getWritableDatabase();
        }
        // Build Tables to reflect schema (SHOPWISE) only if schema is usable
        if(DBConstants.SHOPWISE.isDBDatabaseUsable()) {
            // Check to see if any tables need to be added
            ArrayList<String> buildsql = DBConstants.SHOPWISE.generateDBBuildSQL(db);
            if (!buildsql.isEmpty()) {
                DBConstants.SHOPWISE.actionDBBuildSQL(db);
                msg = dbcreated + buildsql.size() + " tables added.";
                LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
            } else {
                msg = dbbuildskipped;
                LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
            }
            if(buildandexpand) {
                ArrayList<String> altersql = DBConstants.SHOPWISE.generateDBAlterSQL(db);
                if(!altersql.isEmpty()) {
                    msg = dbexpanded + altersql.size() + " columns added.";
                    LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
                    DBConstants.SHOPWISE.actionDBAlterSQL(db);
                }  else {
                    msg = dbexpandskipped;
                    LogMsg.LogMsg(LogMsg.LOGTYPE_INFORMATIONAL,LOGTAG,msg,THISCLASS,methodname);
                }
            }
        } else {
            msg = dbunusable + "\n" +
                    DBConstants.SHOPWISE.getAllDBDatabaseProblemMsgs();
            LogMsg.LogMsg(LogMsg.LOGTYPE_ERROR,LOGTAG,msg,THISCLASS,methodname);
        }
    }
    public static void reopen(Context context) {
        instance = new DBHelper(context);
    }
}

【讨论】:

  • 嗨,迈克,感谢您提供详细信息。就我而言,磁盘上的整个数据库文件都被删除了。我一直追到它,但再也没有走得更远。我怀疑某些第三方库正在删除我的数据库(所有数据库!)作为其初始化过程的一部分。我也在使用数据库助手,调用 getWritableDatabase() 确实会重新创建我的数据库。我只是对为什么我的数据库首先被删除感到困惑。
【解决方案4】:

我遇到了类似的问题,这真的很烦人,因为它随时都会发生,很难复制导致它发生的确切条件。我只在 MainActivity 类的 ondestroy 方法中关闭 DDBB。我所做的是在每次使用db时添加一个try/catch并添加以下catch,在这种情况下它处于while循环的中间,在其他函数中我再次调用该函数一次:

catch (SQLException e) {
    e.printStackTrace();
    Log.d(TAG, "MainService: error in AccSaveToDB with "+mainDB.getPath()+" in iteration "+j+". Closing and re-opening DB");
    DBHelper.close();
    mainDB.close();
    j--;
}

这在每个访问数据库的函数的开头:

if (mainDB==null || !mainDB.isOpen()) {
    DBHelper = DefSQLiteHelper.getInstance(getApplicationContext(), "Data.db", null, 1);
    mainDB = DBHelper.getWritableDatabase();
}

到目前为止,我仍然有时会出现此错误,我还无法找出原因,但至少我的应用程序没有崩溃并且它恢复了它必须做的事情。我看不到文件是否正在被删除,但这个解决方案对我有用

【讨论】:

  • 请尽量减少答案。
猜你喜欢
  • 2017-03-12
  • 2018-07-03
  • 1970-01-01
  • 2021-03-02
  • 1970-01-01
  • 2016-10-20
  • 1970-01-01
  • 1970-01-01
  • 2018-05-23
相关资源
最近更新 更多