android 数据库锁的问题

最近在项目中遇到一个错误:

acquireConnection
releaseConnection

这两个方法调用的地方有两个:

1.修改数据库的地方,需要在修改之前,获取锁,修改之后,释放锁。

2.当我们获取事务的时候,获取锁,结束事务的时候,释放锁。

我们在操作数据库的时候,有时候,不熟悉相关机制,就有可能增加锁,这时就会导致又可能有两个锁互相锁住的情况,就会造成死锁发生,一旦发生死锁,每个30s就会打印一次上面的log

举一个发生死锁的问题代码:(这个是我当时犯的一个真实线上问题)

SQLITE_PRIVATE const char *sqlite3ErrStr(int rc){static const char* const aMsg[] = {*********************************************/* SQLITE_BUSY        */ "database is locked",/* SQLITE_LOCKED      */ "database table is locked",*********************************************};

到这里你是不是有些懵逼,sqlite不是说是线程安全的吗?为什么会直接崩溃呢?

这里需要强调几个概念:

1.connection :每个SQLiteOpenHelper,都会对应一个database,而在android 的java层的数据库的锁机制,都是在对象锁的维度上,所以说两个SQLiteOpenHelper之间是不会造成线程之间的排斥的,所以两个,线程可以同时写入

2.对于sqlite c部分的逻辑,我们看到上面图中的锁,他是不允许两个线程同时写入的,如果有一个线程已经获取了RESOLVED,PENDING,EXCLUSIVE的锁的时候,如果有另一个线程尝试去获取该种类型的锁的时候,就会返回SQLITE_BUSY的错误信息,反应到上层就是上面这个错误

现在说一下上面的结论:

1.数据库是线程安全的,这个无论是在android java端,还是在sqlite的c端,都是做了相关的保护的,但java层的保护是不发生异常,而sqlite c这层保护是保证不发生脏数据。

2.android 端如果采用多个SQLiteOpenHelper的对象,那么这个在java这一层的保护就跳过了,不再提供线程安全的保护,这样就有可能发生崩溃,

3.如果你在创建和Drop数据库的时候,查询数据库,那也会发生崩溃(但这个没有复现,验证的时候,虽然是同时,但如果先查询,验证没问题,如果先drop,则查询的时候,找不到table)

 

到这里就要总结一下需要注意的点了:

1.数据库的connection尽量维持一个,这样更安全一些。

2.没有特殊需求,不要自作多情,在增删改查的时候,增加锁。

 

介绍完以上这个问题之后,我们再来讨论一个问题就是

数据库是否是进程安全的?

先来看一下官网的解答:(我们只关注android,Win95 / 98 / ME下,缺少对读取器/写入器锁的支持,情况不一样)

1.sqlite 采用的是读取器/写入器

2.sqlite 允许多个进程一次打开数据库文件

3.sqlite允许多个进程一次读取数据库文件

4.sqlite只允许一个进程写入文件,并且在写入时,锁住整个数据库文件(几毫秒),如果其他进程如果访问会返回SQLITE_BUSY的错误

看到这里我们可以得出与线程安全同样的结论:

1.sqlite可以保证不产生脏数据

2.sqlite多进程写入时会发生崩溃(而且多进程没办法公用一个connection,所以这个问题android端并没有给解决方案)

多进程同时写文件时发生的崩溃:

2018-12-28 16:00:03.381 27025-27041/com.redare.dbmultprocess E/AndroidRuntime: FATAL EXCEPTION: Thread-2Process: com.redare.dbmultprocess, PID: 27025android.database.sqlite.SQLiteException: Failed to change locale for db '/data/user/0/com.redare.dbmultprocess/databases/my_db' to 'en_US'.at android.database.sqlite.SQLiteConnection.setLocaleFromConfiguration(SQLiteConnection.java:393)at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:218)at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:808)at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:793)at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:696)at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:723)at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:299)at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:254)at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:194)at com.redare.dbmultprocess.db.SQLite_db.exeDO(SQLite_db.java:34)at com.redare.dbmultprocess.db.SQLite_db.insertNewTask(SQLite_db.java:49)at com.redare.dbmultprocess.MainActivity$1.run(MainActivity.java:35)at java.lang.Thread.run(Thread.java:764)Caused by: android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)at android.database.sqlite.SQLiteConnection.nativeExecute(Native Method)at android.database.sqlite.SQLiteConnection.execute(SQLiteConnection.java:555)at android.database.sqlite.SQLiteConnection.setLocaleFromConfiguration(SQLiteConnection.java:371)at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:218) at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193) at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463) at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185) at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177) at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:808) at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:793) at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:696) at android.app.ContextImpl.openOrCreateDatabase(ContextImpl.java:723) at android.content.ContextWrapper.openOrCreateDatabase(ContextWrapper.java:299) at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:254) at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:194) at com.redare.dbmultprocess.db.SQLite_db.exeDO(SQLite_db.java:34) at com.redare.dbmultprocess.db.SQLite_db.insertNewTask(SQLite_db.java:49) at com.redare.dbmultprocess.MainActivity$1.run(MainActivity.java:35) at java.lang.Thread.run(Thread.java:764) 
2018-12-28 16:02:55.331 27149-27167/com.redare.dbmultprocess:myservice E/AndroidRuntime: FATAL EXCEPTION: Thread-2Process: com.redare.dbmultprocess:myservice, PID: 27149android.database.sqlite.SQLiteDatabaseLockedException: database is locked (code 5)at android.database.sqlite.SQLiteConnection.nativeExecuteForChangedRowCount(Native Method)at android.database.sqlite.SQLiteConnection.executeForChangedRowCount(SQLiteConnection.java:734)at android.database.sqlite.SQLiteSession.executeForChangedRowCount(SQLiteSession.java:754)at android.database.sqlite.SQLiteStatement.executeUpdateDelete(SQLiteStatement.java:64)at android.database.sqlite.SQLiteDatabase.executeSql(SQLiteDatabase.java:1679)at android.database.sqlite.SQLiteDatabase.execSQL(SQLiteDatabase.java:1608)at com.redare.dbmultprocess.db.SQLite_db.exeDO(SQLite_db.java:35)at com.redare.dbmultprocess.db.SQLite_db.insertNewTask(SQLite_db.java:49)at com.redare.dbmultprocess.MyService$1.run(MyService.java:46)at java.lang.Thread.run(Thread.java:764)

最后总结一下数据库的知识:

1.sqlite尽量保持一个connection

2.多进程操作时,尽量保证只有一个进程写入,可以有多个进程读取

3.不要私自加锁

 

但是你以为这样就完美了?实际上还有坑:

当我们使用数据库连接的时候,我们涉及到关闭连接的问题,如果多个连接使用同一个connection,一旦有一个先close了,那么后面正在使用的就会崩溃(这里android在关闭的时候,会有一个计数器,但没搞明白这里实现的乱七八糟的,明显存在问题,一直没有修复)

同一个SqliteHelper对象进行读写操作:

2018-12-28 18:08:13.739 7603-7932/com.redare.dbmultprocess E/AndroidRuntime: FATAL EXCEPTION: Thread-313Process: com.redare.dbmultprocess, PID: 7603java.lang.IllegalStateException: Cannot perform this operation because the connection pool has been closed.at android.database.sqlite.SQLiteConnectionPool.throwIfClosedLocked(SQLiteConnectionPool.java:962)at android.database.sqlite.SQLiteConnectionPool.waitForConnection(SQLiteConnectionPool.java:677)at android.database.sqlite.SQLiteConnectionPool.acquireConnection(SQLiteConnectionPool.java:348)at android.database.sqlite.SQLiteSession.acquireConnection(SQLiteSession.java:894)at android.database.sqlite.SQLiteSession.executeForCursorWindow(SQLiteSession.java:834)at android.database.sqlite.SQLiteQuery.fillWindow(SQLiteQuery.java:62)at android.database.sqlite.SQLiteCursor.fillWindow(SQLiteCursor.java:143)at android.database.sqlite.SQLiteCursor.getCount(SQLiteCursor.java:132)at android.database.AbstractCursor.moveToPosition(AbstractCursor.java:219)at android.database.AbstractCursor.moveToFirst(AbstractCursor.java:258)at com.redare.dbmultprocess.db.SQLite_db.query(SQLite_db.java:64)at com.redare.dbmultprocess.MainActivity$2.run(MainActivity.java:48)at java.lang.Thread.run(Thread.java:764)

我在github上复现了所有的崩溃,感兴趣的可以自己查看。

https://github.com/xiepengchong/DbException

参考文章:

https://www.sqlite.org/lockingv3.html

https://www.sqlite.org/faq.html#q5

https://www.sqlite.org/rescode.html


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部