web-dev-qa-db-ja.com

SQLiteReadOnlyDatabaseException:読み取り専用データベースへの書き込みを試みます(コード1032)

そのため、まれに、「読み取り専用データベースへの書き込みを試みています」というメッセージが表示され、問題の原因がわからない場合があります。私のログキャットのスタックトレースから始めます...タイムスタンプからわかるように、書き込みを試みる前に1msだけ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);
        }
    }
}

だから、おそらく2つのうちの1つが起こっていると思います。

  1. チェックと試行されたバッチ挿入の間の1ミリ秒の間、DBは実際に閉じられているか、「読み取り専用」に設定されています。
  2. isReadOnlyは私に嘘をついており、データベースの状態を正確に報告していません。
  3. 挿入の途中でデータベースが削除されます!上記のログの最後の行を参照してください。 SQLiteの厳密なロギングをオンにしたところ、上記に気づきました。サードパーティのライブラリが私のデータベースをすべて削除しているのではないかと疑っています。

現時点ではアイデアはありませんが、提案されたものはすべて試してみたいと思っています。

13
AWT

私は多かれ少なかれまったく同じ問題で立ち往生しており、理にかなっている問題で未解決の欠陥を見つけました...

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

最善の解決策ではありませんが、私の回避策は、データベースのリビジョンをステップ実行して自分で追跡しないことです。したがって、onUpgrade()を呼び出さず、アプリの更新時に手動でアップグレードを実行します。

または、読み取り専用の小さなDBがある場合、DBHelperクラスのすべてのonCreate()のアセットにあるdbのコピーをトリガーできますが、ファイルシステムがいっぱいなので、より良い解決策を探している間だけこれを行ってください。

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

私のアプリは、私の回避策で必要に応じてアップグレードされ、この欠陥が最初に発生してからそれがどれくらいの時間であるかを判断することにより、まったく修正されない可能性があります...

これは問題の完全な解決策ではありませんが、少なくとも前進する方法です。

1
Jens Andree

したがって、この根本的な原因は、一見すると、サードパーティのライブラリであるように見えます。誤解しない限り、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()への次の呼び出しで再度作成され、アプリが強制終了されて再起動されるまで問題ありません。その時点で、アプリは削除されて再作成されます。

正確にリンク解除の原因が判明したら、これを更新します。

1
AWT

同様の問題がありました。ただし、私は意図的に現在のデータベースを復元の一部として削除していました。

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の同等の/回避策です。これは、実際のデータベースと比較される疑似スキーマに従ってテーブルと列を追加します。

完全な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);
    }
}
_
0
MikeT

私も同じ問題を抱えていました。最も簡単な方法は、データベースオブジェクトでenableWriteAheadLogging()を使用することです。

databaseObject.enableWriteAheadLogging()
0
Sam Joos

私は同様の問題を抱えていますが、これはいつでも発生することであり、それを発生させる正確な条件を再現するのが難しいのは本当に迷惑です。 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();
}

これまでのところ、まだこのエラーが発生していますが、原因を特定することはできませんでしたが、少なくとも私のアプリはクラッシュせず、実行する必要があります。ファイルが削除されているかどうかはわかりませんが、この解決策は私のために働いています

0
Ale