Pergunta

Nós estamos usando AsyncTasks para acessar tabelas e cursores do banco de dados.

Infelizmente, estamos vendo exceções ocasionais em relação ao bloqueio do banco de dados.

E/SQLiteOpenHelper(15963): Couldn't open iviewnews.db for writing (will try read-only):
E/SQLiteOpenHelper(15963): android.database.sqlite.SQLiteException: database is locked
E/SQLiteOpenHelper(15963):  at     android.database.sqlite.SQLiteDatabase.native_setLocale(Native Method)
E/SQLiteOpenHelper(15963):  at     android.database.sqlite.SQLiteDatabase.setLocale(SQLiteDatabase.java:1637)
E/SQLiteOpenHelper(15963):  at     android.database.sqlite.SQLiteDatabase.<init>(SQLiteDatabase.java:1587)
E/SQLiteOpenHelper(15963):  at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:638)
E/SQLiteOpenHelper(15963):  at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:659)
E/SQLiteOpenHelper(15963):  at android.database.sqlite.SQLiteDatabase.openOrCreateDatabase(SQLiteDatabase.java:652)
E/SQLiteOpenHelper(15963):  at android.app.ApplicationContext.openOrCreateDatabase(ApplicationContext.java:482)
E/SQLiteOpenHelper(15963):  at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:193)
E/SQLiteOpenHelper(15963):  at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:98)
E/SQLiteOpenHelper(15963):  at android.database.sqlite.SQLiteOpenHelper.getReadableDatabase(SQLiteOpenHelper.java:158)
E/SQLiteOpenHelper(15963):  at com.iview.android.widget.IViewNewsTopStoryWidget.initData(IViewNewsTopStoryWidget.java:73)
E/SQLiteOpenHelper(15963):  at com.iview.android.widget.IViewNewsTopStoryWidget.updateNewsWidgets(IViewNewsTopStoryWidget.java:121)
E/SQLiteOpenHelper(15963):  at com.iview.android.async.GetNewsTask.doInBackground(GetNewsTask.java:338)
E/SQLiteOpenHelper(15963):  at com.iview.android.async.GetNewsTask.doInBackground(GetNewsTask.java:1)
E/SQLiteOpenHelper(15963):  at android.os.AsyncTask$2.call(AsyncTask.java:185)
E/SQLiteOpenHelper(15963):  at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:256)
E/SQLiteOpenHelper(15963):  at java.util.concurrent.FutureTask.run(FutureTask.java:122)
E/SQLiteOpenHelper(15963):  at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:648)
E/SQLiteOpenHelper(15963):  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:673)
E/SQLiteOpenHelper(15963):  at java.lang.Thread.run(Thread.java:1060)

Alguém tem um exemplo geral de código que grava em um banco de dados a partir de um thread diferente daquele que está lendo e como podemos garantir a segurança do thread.

Uma sugestão que tive é usar um ContentProvider, pois isso trataria o acesso do banco de dados de vários threads.Vou dar uma olhada nisso, mas esse é o método recomendado para lidar com esse problema?Parece bastante pesado, considerando que estamos falando na frente ou atrás.

Foi útil?

Solução

Resolvi a mesma exceção apenas para garantir que todas as minhas aberturas de banco de dados tenham fechado e (mais importante) para garantir isso, tornando o escopo de cada instância do banco de dados local apenas para o método que precisa. O ContentProvider é uma aula boa e segura a ser usada ao acessar um banco de dados de vários threads, mas também certifique -se de usar boas práticas de banco de dados:

  • Mantenha as instâncias do DB local (sem membros da classe sqlitedAtAtabase!)
  • ligar close() no banco de dados no mesmo método em que é aberto
  • ligar close() nos cursores que você obtém do banco de dados
  • Ouça o Logcat para que as queixas que o sqlitedatabse possa ter

Outras dicas

Usamos um ContentProvider no fim. Isso parecia esclarecer os problemas.

Antes de algum código, vamos retomar algumas das abordagens:

  • Semáforos: de longe a melhor solução apresentada. Ele fica no coração do problema: compartilhamento de recursos! Ele tratará o bloqueio do acesso ao banco de dados, evitando conflitos (database is locked).

  • Sincronização de Java: Uma espécie de implementação de semáforo, mas menos sofisticada. Usando synchronized Você não resolverá facilmente alguns casos envolvendo transações.

  • Provedor de conteúdo: implemento ContentProvider Resolva o problema apenas para alguns casos (ou varra o problema sob o tapete). Você ainda enfrentará os mesmos problemas. A diferença é que ContentProvider O padrão o orientará a não cometer alguns erros de comércio ao acessar o banco de dados SQLite. o DOCs do ContentProvider Diz: "Você não precisa de um provedor para usar um banco de dados SQLite se o uso estiver inteiramente dentro do seu próprio aplicativo".

  • Quase obrigatório: Mantenha as instâncias do DB local, ligue close() no banco de dados no mesmo método em que é aberto usando finally declarações, close() nos cursores usando finally declarações, etc, são quase obrigatório para evitar problemas usando o sqlite.

Vamos mostrar um exemplo da solução de semáforo apresentada por musgo, que eu tirei de Cl. e aprimorados para cobrir transações.

class DataAccess {
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock r = rwl.readLock();
    private final Lock w = rwl.writeLock();

    public Data readSomething(int id) {
        Cursor c = null;
        r.lock();
        try {
            c = getReadableDatabase().query(...);
            return c.getString(0);
        } finally {
            if (c != null) c.close();
            r.unlock();
        }
    }

    public void changeSomething(int id, int value) {
        w.lock();
        try {
            getWritableDatabase().update(...);
        } finally {
            w.unlock();
        }
    }

    private void beginTransactionWithSemaphores() {
        getWritableDatabase().beginTransactionWithListener(new SQLiteTransactionListener() {
            @Override
            public void onBegin() {
                w.lock();
            }

            @Override
            public void onRollback() {
                w.unlock();
            }

            @Override
            public void onCommit() {
                w.unlock();
            }
        });
    }
}

Leve em consideração que os bancos de dados SQLite são baseados em arquivos e não se destinam a serem acessados ​​de forma multiprocessada.O melhor procedimento para misturar SQLite com multiprocessamento é usar semáforos (aquire(), release()) em cada acesso relacionado ao banco de dados.

Se você criar um wrapper de banco de dados que adquira/libere um semáforo global, seu acesso ao banco de dados será thread-safe.Na verdade, isso significa que você pode ter um gargalo porque está enfileirando o acesso ao banco de dados.Além disso, você só poderá agrupar o acesso usando semáforos se for uma operação que altere o banco de dados, portanto, enquanto você estiver alterando o banco de dados, ninguém poderá acessá-lo e aguardar até que o processo de gravação seja concluído.

Não podíamos compartilhar a conexão de banco de dados com vários threads para executar a operação de leitura e gravação no banco de dados simultaneamente. Temos que fazer um único objeto de banco de dados usando o conceito de sincronização e executaremos uma tarefa de cada vez. Nós usaremos o padrão de singleton para fazer o db objeto e será compartilhado em vários threads. Em seguida, iniciaremos outra tarefa ou qualquer operação no banco de dados. O provedor de conteúdo não é a solução do problema de bloqueio de banco de dados.

import java.util.concurrent.atomic.AtomicInteger;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

public class DatabaseManager {

private AtomicInteger mOpenCounter = new AtomicInteger();

private static DatabaseManager instance;
private static SQLiteOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
//private static String DB_PATH = "";
//  private static String DB_NAME = "xyz.db";// Database name
private static String dbPathh;

public static synchronized void initializeInstance(SQLiteOpenHelper helper,
        String dbPath) {
    if (instance == null) {
        instance = new DatabaseManager();
        mDatabaseHelper = helper;
        dbPathh=dbPath;
    }
  }

public static synchronized DatabaseManager getInstance() {
    if (instance == null) {
        throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
                " is not initialized, call initializeInstance(..) method first.");
    }

    return instance;
 }

  public synchronized SQLiteDatabase openDatabase(String thread) {

    if(mOpenCounter.get() == 0) {
        // Opening new database
        // mDatabase = mDatabaseHelper.getWritableDatabase();
        MyLog.e("Path Of DataBase", dbPathh);
        //  mDatabase=mDatabaseHelper.getWritableDatabase();
        mOpenCounter.incrementAndGet();
        mDatabase=SQLiteDatabase.openDatabase(dbPathh, null,   
 SQLiteDatabase.  CREATE_IF_NECESSARY|SQLiteDatabase.OPEN_READWRITE);   
        MyLog.e("Open Data Base", " New Connection created" +thread);
    }
    else{
        MyLog.e("Open Data Base", " Old Connection given " +thread);
    }
    //  Toast.makeText(NNacres.getConfig(), "open conn: present connection = 
   "   +mOpenCounter.get(), Toast.LENGTH_LONG).show();
    return mDatabase;
   }

    public synchronized void closeDatabase() {
    MyLog.e("Close db connection", ""+mOpenCounter.get());

    if(mOpenCounter.get() == 1) {
        // Closing database

        mDatabase.close();
        mOpenCounter.decrementAndGet();

        Log.e("DB CLOSED", "DONE");
    }
    //Toast.makeText(NNacres.getConfig(), "close conn: after close =   
 " +mOpenCounter.get(), Toast.LENGTH_LONG).show();
    }

    }

e escreva este método em sua classe Helper HoursQlitedataabse, que estende a classe Sqliteopenhelper

     public SQLiteDatabase getWritableDatabase() {
DatabaseManager.initializeInstance(this,"data/data/your packgae name/databases/xyz");
    return DatabaseManager.getInstance().openDatabase(getClass().getSimpleName());

}



public static String getMyDbPath(String DB_NAME, Context context) {

    String myDbPath = context.getDatabasePath(DB_NAME).getPath();
    MyLog.e("DB Path: "+myDbPath);
    return myDbPath;
}

Você deve estar ligando getWritableDatabase() de uma função, em vez de o construtor da classe auxiliar DB. Se o objeto de classe auxiliar DB for criado com SQLiteDatabase.openOrCreateDatabase(DB_PATH, null); ou similar e então getWritableDatabase() é chamado de uma função, ele tentará fazer uma chamada síncrona para DB, causando uma exceção de bloqueio de dB.

Você está falando de uma única ação do usuário que, dentro do seu programa, faz com que vários threads sejam executados, mais de um dos quais pode estar acessando o banco de dados no modo de atualização?

Isso é um design ruim, período. Não há como você saber em que ordem os tópicos serão agendados pelo seu sistema operacional (/VM) e, portanto, não há como você saber em que ordem os acessos do banco de dados acontecerão, e isso é muito provável Que não há como você saber que os acessos ao banco de dados sempre acontecerão na ordem que você espera.

Todos os acessos de banco de dados gerados por/provenientes de alguma ação do usuário devem ser feitos em um único thread.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top