SQLiteReadOnlyDatabaseException: попытка написать базу данных только для чтения (код 1032)

Итак, в некоторых редких случаях я вижу сообщение "попытка написать сообщение для базы данных", и я не могу понять, где проблема. Я начну с stacktrace в моем logcat... как вы можете видеть из timestamp, я проверяю db.isReadOnly() всего 1 мс, прежде чем пытаюсь записать. (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);
        }
    }
}

Итак, я думаю, что может произойти одна из двух вещей.

  • БД действительно закрывается/устанавливается на "readonly" в течение 1 мс между проверкой и попыткой вставки пакета.
  • isReadOnly лжет мне и не точно сообщает состояние базы данных.
  • База данных удаляется частично через мою вставку! См. Последнюю строку журнала выше. Я включил строгую регистрацию для SQLite и заметил выше. У меня есть подозрение, что библиотека сторонних разработчиков может удалить все мои базы данных.

Из идей на данный момент, хотя, но я готов попробовать что-нибудь предлагаемое.

Ответ 1

Таким образом, первопричина этого, на первый взгляд, появляется в сторонней библиотеке. Если я не ошибаюсь, Tagit от Mobeix удаляет базу данных при запуске приложения. Я добавил подробное ведение журнала 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

Так что мой файл базы данных отсоединен. Weird. Следующий вызов getWritableDatabase() воссоздает его снова, а затем он отлично до тех пор, пока приложение не будет убито и перезапущено, после чего его удалят и воссоздают.

Я обновлю это, если я когда-нибудь выясню, что вызывает unlink.

Ответ 2

Я придерживаюсь более или менее точно такой же проблемы, и я обнаружил открытый дефект в этом вопросе, который имеет смысл...

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

Мое решение - хотя и не лучшее решение - никогда не делать шаг по изменению базы данных и не отслеживать это сам, тем самым никогда не вызывая onUpgrade() и вручную обновляя при обновлении приложения.

Альтернативно, если у вас есть небольшая БД, которая доступна только для чтения, вы можете вызвать копию базы данных в активах в каждом классе onCreate() в DBHelper, но это может привести к нежелательным проблемам, если файловая система заполнена, это в то же время ищет лучшее решение.

@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());
    }
}

Мое приложение теперь обновляется, как и должно быть с моим обходным путем, и, судя, сколько времени прошло с тех пор, как этот дефект был поднят изначально, он никогда не может быть исправлен вообще...

Мне жаль, что это не полное решение проблемы, но, по крайней мере, это путь вперед.

Ответ 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 - это мой эквивалент /getaround для onUpgrade/versions. Он добавляет таблицы и столбцы в соответствии с псевдосхемой, сравниваемой с фактической базой данных.

Полный 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);
    }
}

Ответ 4

У меня есть аналогичная проблема, которая очень раздражает, так это то, что это происходит в любое время, и трудно воспроизвести точные условия, которые делают это возможным. Я только закрываю DDBB в методе ondestroy класса MainActivity. Я сделал, чтобы добавить try/catch при каждом использовании db и добавить следующий 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();
}

До сих пор у меня до сих пор была эта ошибка, я еще не мог понять причину, но, по крайней мере, мое приложение не разбивается и возобновляет то, что ему нужно делать. Я не вижу, удаляется ли файл, но эти решения работают для меня.