SQlite数据库的C编程接口(七)  数据库锁定(Database Locking)  by斜风细雨QQ:253786989    2012-02-09

对于《Using SQLite》的这一节内容,理解的不是很清楚。有时间要仔细看看SQLite的文档:http://www.sqlite.org/lockingv3.html(File Locking And Concurrency In SQLite Version 3

SQLite使用一些不同的锁来保护数据库,以允许多个数据库连接同时访问一个相同的数据库文件,而不会出现数据库损坏。不管是在“自动提交事务(autocommit transaction)”模式,还是“显示事务(explicit transaction)”模式,这些锁都工作良好。

SQLite锁系统(locking system)涉及几个不同层次的锁,用来减少竞争、避免死锁等等。以使SQlite允许多个数据库连接并行读取同一个数据库文件,不过任何写操作都需要完整的,整个数据库文件的独占访问。

大部分时间锁系统(locking system)工作良好,允许不同的应用程序之间方便和安全的分享同一个数据库文件。如果编码得当,大部分写操作仅仅需要几分之一秒。然而,如果多个数据库连接试图在同一时间访问同一个数据库文件,那这些操作迟早会相遇。通常情况下,如果一个数据库操作需要一个暂时无法得到的锁,那么SQLite会返回SQLITE_BUSY,或者在更极端的情况下,返回SQLITE_IOERR(或者扩展码SQLITE_IOERR_BLOCKED)。函数sqlite3_prepare_xxx、sqlite3_step、sqlite3_reset、sqlite3_finalize会返回SQLITE_BUSY。函数sqlite3_backup_step、sqlite3_blob_open也会返回SQLITE_BUSY,因为在这两个函数的内部都是通过调用sqlite3_prepare_xxx、sqlite3_step函数来完成工作的。如果调用sqlite3_close函数时,所连接的数据库存在没有销毁(unfinalize)的语句,则该函数也会返回SQLITE_BUSY。

如果想访问某个锁,就需要等待其所有者完成并释放对它的使用,通常不会等待太长时间。等待(waiting)状态可以由应用程序处理,比如接收到SQLITE_BUSY应答,则再次尝试处理该语句。或者也可以由一个忙处理程序(busy handler)来处理。

忙处理程序(Busy handlers

busy handler(忙处理程序)是一个回调函数,当SQLite library无法获取一个锁时调用。在busy handler中,可以继续尝试获取锁的操作,或者放弃并返回SQLITE_BUSY错误码。

SQLite有一个内置的基于定时器的busy handler,可以给这个busy handler设置一个以毫秒为单位的超时时间。在超时时间范围内,busy handler将继续重复尝试获取锁的操作。

int sqlite3_busy_timeout(sqlite3*, int ms);

用来设置内置busy handler的超时时间,以毫秒为单位。如果给ms参数传递0或者负值,则内置的busy handler被清除。

程序员也可以自己写busy handler,然后通过aqlite3_busy_handler函数进行设置。

int sqlite3_busy_handler(sqlite3*, int(*)(void*,int), void*);

给指定的数据库连接设置自定义的busy handler,把自己写的busy handler函数传递给该函数的第2个参数。如果给第2个参数传递NULL,则移除自定义的busy handler。第3个参数是传递给busy handler回调函数的用户数据指针。

自定义的busy handler回调函数原型如下:

int user_defined_busy_handler_callback( void *udp, int incr )

第1个参数是通过sqlite3_busy_handler函数传递过来的用户数据指针。第2个参数是一个计数器,每次busy handler被调用时该计数器的计数递增。如果该回调函数返回0,则SQLite将放弃获取锁的操作,并返回SQLITE_BUSY。如果该函数返回一个非0值,则SQLite将继续尝试获取锁的操作。

需要注意的是:一个数据库连接仅仅可以拥有一个busy handler,不能同时设置自定义的busy handler并配置内置的基于定时器的busy handler。每次设置其中一种busy handler都将删除另一个busy handler。

死锁(Deadlocks

为一个数据库连接设置busy handler不能够解决所有问题。有的时候在多个数据库连接之间会出现死锁现象。比如两个数据库连接各自持有一些锁,但是它们现在都在等待对方持有的锁被释放,这样就会造成死锁现象。唯一的解决办法,就是其中一个数据库连接放弃获取锁的操作,并释放自己所持有的锁。

如果SQLite检测到了一个潜在的死锁情况,它将跳过busy handler,并将有一个数据库连接立刻返回SQLITE_BUSY。这样做是为了鼓励应用程序释放他们自己的锁,以打破死锁现象。

避免SQLITE_BUSY(Avoiding SQLITE_BUSY

如果我们在开发某个项目时需要充分考虑数据库的并发性能,最简单的方法是使用sqlite3_busy_timeout设置内置的busy handler,并且超时时间在250-2000毫秒范围内进行调整,这样可以减少SQLITE_BUSY的产生。

如果想完全避免SQLITE_BUSY,唯一的办法就是确保对于同一个数据库某一个时刻仅仅存在一个数据库连接。这需要设置PRAGMA locking-mode为EXCLUSIVE(独占的)。

另外,应用程序可以使用独占模式(EXCLUSIVE)的事务,这样对于SQLITE_BUSY返回值的处理就更容易些。可以通过BEGIN EXCLUSIVE TRANSACTION命令开启一个独占式的事务,如果成功则在事务的执行过程中不会返回SQLITE_BUSY。不过BEGIN命令本身可能会执行失败并返回SQLITE_BUSY,但这种情况处理起来很容易,应用程序可以通过sqlite3_reset函数重置BEGIN语句,然后重试。BEGIN EXCLUSIVE的缺点是只有目前没有其他的数据库连接(包括只读事务)正在访问该数据库,它才会执行成功。而且一旦独占式的事务开始执行,它同样会锁住数据库,使其他的数据库连接(包括只读事务)无法访问该数据库。

为了允许更多的并发访问,还有一种类型的事务——IMMEDIATE TRANSACTION(即时事务)。可以通过BEGIN IMMEDIATE TRANSACTION命令启动一个即时事务,如果成功则在事务执行的过程中通常只有执行到COMMIT语句时才会返回SQLITE_BUSY。不管是事务中的哪些命令(包括COMMIT),一旦遇到了SQLITE_BUSY,应用程序可以简单的重置语句然后等待并重试。BEGIN IMMEDIATE语句本身也有可能会遭遇SQLITE_BUSY,这时应用程序也是可以简单的重置BEGIN语句然后重试。与EXCLUSIVE TRANSACTION不同的是,如果存在其它的数据库连接正在读取(非写)数据库,这时候IMMEDIATE事务是可以启动的。一旦IMMEDIATE事务成功启动,则不允许其它数据库连接进行写入操作,但只读的数据库连接仍然可以访问数据库,除非IMMEDIATE事务正在强制修改数据库文件(通常是事务正在执行COMMIT操作)。使用IMMEDIATE事务不会发生死锁现象,所有的SQLITE_BUSY可以通过重试操作进行处理。

避免死锁(Avoiding deadlocks

避免死锁的原则比较简单,不过遵循这些原则会使应用程序变得复杂一些。

首先,sqlite3_prepare_xxx、sqlite3_backup_step、sqlite3_blob_open函数调用不会产生死锁现象。任何时候如果这些函数返回SQLITE_BUSY,简单的等待一下然后重试即可。

如果在一个(deferred)事务中,sqlite3_step、sqlite3_reset、sqlite3_finalize函数返回SQLITE_BUSY,则应用程序必须回退然后重试。如果这些语句不在一个显示的(explicit)事务中,prepared语句可以简单的重置然后重新执行。如果这些语句在一个显示的(explicit)事务中,那么整个事务就必须回退,然后从头开始执行。致使回退的整个原因就是其他一些数据库连接需要修改数据库。另外需要注意的是,如果应用程序完成了一些读取操作并准备进行写操作,那么在新的事务中最好重新读取这些信息以确定这些数据仍然有效。

不管你做什么,不要忽视SQLITE_BUSY。它可能很少发生,但如果处理不当它就可能成为大麻烦的源头。

当“繁忙”转换为“被阻塞”(When BUSY becomes BLOCKED)

当一个连接需要修改数据库中的数据,数据库就要被加锁使其对于其他连接处于只读状态。事实上,事务中对于数据库的某个修改并没有马上写入数据库文件,而是保存在数据库的页缓存中。因为如果将这个修改直接写入数据库,那么这个修改对于其他的读连接就可见了,这就打破了事务的隔离性原则。

当所有需要的修改操作全部做完,事务开始准备提交。这时数据库文件会被进一步锁定,不允许新的只读事务启动。允许已经存在的读操作(reader)完成,并释放他们持有的数据库锁。当所有的读操作完成,写操作(writer)就要独占式的访问数据库,最终将页缓存中的修改刷新到数据库中。

这一过程允许写事务正在执行的同时,只读事务依然可以继续运行。当写事务确实在提交数据时,读事务被锁定。然而,一个关键的假设是在这个过程中,所有修改被放入页缓存中,直到事务提交的时候才被写入数据库。如果缓存(cache)中装满了(包含处于悬挂状态的修改的)数据页,那么写事务没有其它选择,只能给数据库加一个独占锁,并且在提交(commit)阶段之前刷新缓存。该事务仍然可以在任何时候回滚,但是写入操作必须获得独占锁的即时访问,以刷新缓存。

如果这个锁不是立即可用,写入操作就会被强制终止整个事务。写入事务将回退,然后返回扩展码SQLITE_IOERR_BLOCKED。因为事务是自动回退的,所以应用程序没有更多选择,只能重新启动事务。

为了避免这种情况,最好是用显示的BEGIN EXCLUSIVE语句启动一个可以修改多行数据的大事务。BEGIN EXCLUSIVE也许会失败,并返回SQLITE_BUSY,但是应用程序可以简单的重新尝试直到成功。一旦一个独占式的事务成功启动,它就可以完整的访问数据库,消除SQLITE_IOERR_BLOCKED的出现,甚至事务在提交之前就造成了缓冲区溢出(增加数据库缓存会有所帮助)。

SQlite数据库的C编程接口(七)  数据库锁定(Database Locking)  by斜风细雨QQ:253786989    2012-02-09

SQlite数据库的C编程接口(七) 数据库锁定(Database Locking) ——《Using SQlite》读书笔记相关推荐

  1. Python编程:从入门到实践第六章读书笔记6.3遍历字典

    Python编程:从入门到实践第六章读书笔记6.3遍历字典 #coding:gbk#6.3.1遍历所有的键-值对 user_0 = {'username': 'efermi','first': 'en ...

  2. 数据库访问抽象层系列-1(介绍数据库编程接口及数据库访问抽象层概念)

    摘要 本人最近完成了一个封装数据库访问抽象层的项目.我们开发的数据库访问抽象层作为分布式集群基础平台的一个组件.可以支持不同数据库编程接口(OCI.mysql.ODBC.pgsql)等.本系列博客主要 ...

  3. 连接mysql数据库的三个接口_数据库的三种接口

    数据库(Database)是按照数据结构来组织.存储和管理数据的仓库,它产生于距今六十多年前,随着信息技术和市场的发展,特别是二十世纪九十年代以后,数据管理不再仅仅是存储和管理数据,而转变成用户所需要 ...

  4. 金仓数据库 KingbaseES 客户端编程接口指南 - JDBC(11. JDBC 示例说明)

    11. JDBC 示例说明 在所提供的用例中,使用的数据库信息为,用户名:system; 密码:manager; 数据库名:test; 端口号:54321 数据源示例 连接池示例 Statement ...

  5. 金仓数据库KingbaseES客户端编程接口指南-DCI(3. DCI 工程配置)

    3. DCI 工程配置¶ Windows 平台工程搭建(vs2008) Linux平台工程搭建 服务的配置方法与参数说明 多主机地址配置 3.1. Windows 平台工程搭建(VS2008) 3.2 ...

  6. 金仓数据库KingbaseES客户端编程接口指南-ODBC(6. KingbaseES ODBC 的扩展属性)

    6. KingbaseES ODBC 的扩展属性 KingbaseES ODBC 数据源的高级选项 Linux环境下SQLDriverConnect()连接串中KingbaseES ODBC的扩展连接 ...

  7. 金仓数据库 KingbaseES 客户端编程接口指南 - ODBC 驱动使用

    7. KingabseES ODBC 驱动使用 Windows 中 ODBC 驱动使用步骤(VS2013) Linux 下调用 ODBC 驱动步骤 7.1. Windows 中 ODBC 驱动使用步骤 ...

  8. python编程从入门到实践读书笔记-《Python编程:从入门到实践》项目部分读书笔记(二)...

    鸽了一个暑假没有更新,现在趁着还没开学更一下.咕咕咕 上期作业: 请创建一个Django项目,命名为Blog,建不建立虚拟环境随便你,最后本地跑成了就行. 步骤: ①在需要创建工程的文件夹下打开cmd ...

  9. 《Java并发编程实战》第十章 避免活跃性危急 读书笔记

    一.死锁 所谓死锁: 是指两个或两个以上的进程在运行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去. 百科百科 当两个以上的运算单元,两方都在等待对方停止执行,以取得 ...

  10. python编程快速上手第四章_《Python编程快速上手——让繁琐的工作自动化》读书笔记 第四章 列表...

    接下来我们来学习 python 中的列表(有点像 Java 中的数组,但并不是数组) "列表"是一个值,它包括多个字构成的序列,术语"列表值"指的是列表本身(它 ...

最新文章

  1. [转]Java + TestNG + Appium 实现单机多个Android终端并发测试
  2. java.sql.Connection.close() vs null
  3. NameValueCollection类总结和一个例子源码
  4. RocketMQ避坑指南:springcloud教程权威指南
  5. linux进程cpu时间片,能讲一下在Linux系统中时间片是怎么分配的还有优先级的具体算法是...
  6. FPGA系统设计考虑因素
  7. 小米机器人 尘盒配件_石头扫地机器人T6评测:一款提升生活辛福感的宝物
  8. 没有配置默认路由_网络路由选择原理
  9. http status 400 – bad request 亚马逊_蛮拼的!这个亚马逊卖家为Prime Day做了这三大准备,销量暴涨58倍...
  10. signature=ed12edba242e439d545c9f98deb5e896,PROJECTION APPARATUS
  11. DEA博弈交叉效率matlab,dea的交叉效率
  12. 请问最早的计算机语言是什么,最早的计算机语言是什么?
  13. python 报错“xxx is not defined”
  14. 三角肌前束(04):杠铃立正划船
  15. 富爸爸穷爸爸第二章的思考
  16. 欧拉图简述---(一笔画问题)
  17. WPF中使用Winform控件
  18. 1617: Special Formation - 规律题
  19. 高等数学笔记-乐经良老师-第八章-多元函数微分学(Ⅱ)
  20. 解决本地计算机上的MySQL80服务启动后停止。某些服务在未由其他服务或程序使用时将自动停止

热门文章

  1. 2015年7月15日 JS第一课(JS,声明变量,数据类型)
  2. Spark OOM:java heap space,OOM:GC overhead limit exceeded解决方法
  3. (JS基础)操作表单
  4. shell自动收集服务器硬件系统信息通过web页面显示
  5. STL sort 函数实现详解 ZZ
  6. 那天有个小孩跟我说LINQ(三)
  7. Java基础之Java 修饰符
  8. 阿里云基于NVM的持久化高性能Redis数据库 1
  9. Linux中使用Vim快速更换文档中Windows换行符为Linux平台
  10. 解决Spark集群无法停止