SQLite文件锁
一、SQLite3文件锁描述
SQLite3中总体提供五种锁状态,按级别依次是: NONE_LOCK / SHARED_LOCK / RESERVED_LOCK / PENDING_LOCK / EXCLUSIVE_LOCK
(1) NONE_LOCK:无锁
当前文件不持有任何锁,对文件没有任何读写操作,默认状态
(2) SHARED_LOCK:共享锁
该状态下数据库仅能读取,不可写入,允许任意数量线程对文件进行同时读,但拒绝任何线程对其进行写操作,类似通常文件锁中的读锁。
(3) RESERVED_LOCK:保留锁
该状态表示存在线程打算对数据库进行写入,但当前正从文件中读取相应数据,该状态下仍相当于读写锁中的读锁,允许继续添加共享锁但同时仅能存在一个保留锁。
(4) PENDING_LOCK:等待锁(写等待)
该状态表示原持有保留锁的线程数据读取已完成,等待所有共享锁释放后对文件进行写入,该状态下不可再添加共享锁,防止写饥饿。
注:等待锁不可主动请求获得,而是在请求排它锁失败时,由保留锁自动转化为等待锁形成
(5) EXCLUSIVE_LOCK:排它锁
在执行写操作前需先获取排它锁,持有该锁的线程将独占文件,其他线程将不能对文件进行任何操作,类似于读写锁中的写锁。
锁状态转换如下图:
二、SQLite3文件锁加锁实现(UNIX操作系统版本)
(1)、锁页
为了实现读写锁,SQLite3特别预留了锁页,其起始位置位于 0x40000000,该页不保存数据,在SQLite3中对数据库的加解锁实际是对特定区域的加解锁
1、请求EXCLUSIVE_LOCK
尝试对PENDING_BYTE添加写锁,如果失败,说明存在其他SHARED_LOCK,这种情况下EXCLUSIVE_LOCK会变为PENDING_LOCK,如果成功则继续对SHARED区域加写锁
2、请求RESERVED_LOCK
尝试对RESERVED_BYTE添加写锁,如果失败,说明已存在其他RESERVED_LOCK.
3、添加SHARED_LOCK
尝试对PENDING_BYTE添加读锁,如果失败,说明已存在PENDING_LOCK或EXCLUSIVE_LOCK,若成功则继续对SHARED区域加读锁后解除PENDING_BYTE的写锁
布局如图
注: fcntl可对文件不存在的区域进行加锁,故将锁页选在1G处,避免空间浪费;因为windows下不可对文件同一位置重复加锁,故特别划分出SHARED区段便于添加多个共享锁
(2)、具体实现

1 static int unixLock(sqlite3_file *id, int eFileLock){ 2 3 int rc = SQLITE_OK; 4 unixFile *pFile = (unixFile*)id; 5 unixInodeInfo *pInode; 6 struct flock lock; 7 int tErrno = 0; 8 9 10 11 /* If there is already a lock of this type or more restrictive on the 12 ** unixFile, do nothing. Don't use the end_lock: exit path, as 13 ** unixEnterMutex() hasn't been called yet. 14 */ 15 if( pFile->eFileLock>=eFileLock ){ 16 return SQLITE_OK; 17 } 18 19 20 /* This mutex is needed because pFile->pInode is shared across threads 21 */ 22 pInode = pFile->pInode; 23 sqlite3_mutex_enter(pInode->pLockMutex); 24 25 /* If some thread using this PID has a lock via a different unixFile* 26 ** handle that precludes the requested lock, return BUSY. 27 */ 28 if( (pFile->eFileLock!=pInode->eFileLock && 29 (pInode->eFileLock>=PENDING_LOCK || eFileLock>SHARED_LOCK)) 30 ){ 31 rc = SQLITE_BUSY; 32 goto end_lock; 33 } 34 35 /* If a SHARED lock is requested, and some thread using this PID already 36 ** has a SHARED or RESERVED lock, then increment reference counts and 37 ** return SQLITE_OK. 38 */ 39 if( eFileLock==SHARED_LOCK && 40 (pInode->eFileLock==SHARED_LOCK || pInode->eFileLock==RESERVED_LOCK) ){ 41 pFile->eFileLock = SHARED_LOCK; 42 pInode->nShared++; 43 pInode->nLock++; 44 goto end_lock; 45 } 46 47 48 /* A PENDING lock is needed before acquiring a SHARED lock and before 49 ** acquiring an EXCLUSIVE lock. For the SHARED lock, the PENDING will 50 ** be released. 51 */ 52 lock.l_len = 1L; 53 lock.l_whence = SEEK_SET; 54 if( eFileLock==SHARED_LOCK 55 || (eFileLock==EXCLUSIVE_LOCK && pFile->eFileLock<PENDING_LOCK) 56 ){ 57 lock.l_type = (eFileLock==SHARED_LOCK?F_RDLCK:F_WRLCK); 58 lock.l_start = PENDING_BYTE; 59 if( unixFileLock(pFile, &lock) ){ 60 tErrno = errno; 61 rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); 62 if( rc!=SQLITE_BUSY ){ 63 storeLastErrno(pFile, tErrno); 64 } 65 goto end_lock; 66 } 67 } 68 69 70 /* If control gets to this point, then actually go ahead and make 71 ** operating system calls for the specified lock. 72 */ 73 if( eFileLock==SHARED_LOCK ){ 74 75 /* Now get the read-lock */ 76 lock.l_start = SHARED_FIRST; 77 lock.l_len = SHARED_SIZE; 78 if( unixFileLock(pFile, &lock) ){ 79 tErrno = errno; 80 rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); 81 } 82 83 /* Drop the temporary PENDING lock */ 84 lock.l_start = PENDING_BYTE; 85 lock.l_len = 1L; 86 lock.l_type = F_UNLCK; 87 if( unixFileLock(pFile, &lock) && rc==SQLITE_OK ){ 88 /* This could happen with a network mount */ 89 tErrno = errno; 90 rc = SQLITE_IOERR_UNLOCK; 91 } 92 93 if( rc ){ 94 if( rc!=SQLITE_BUSY ){ 95 storeLastErrno(pFile, tErrno); 96 } 97 goto end_lock; 98 }else{ 99 pFile->eFileLock = SHARED_LOCK; 100 pInode->nLock++; 101 pInode->nShared = 1; 102 } 103 }else if( eFileLock==EXCLUSIVE_LOCK && pInode->nShared>1 ){ 104 /* We are trying for an exclusive lock but another thread in this 105 ** same process is still holding a shared lock. */ 106 rc = SQLITE_BUSY; 107 }else{ 108 /* The request was for a RESERVED or EXCLUSIVE lock. It is 109 ** assumed that there is a SHARED or greater lock on the file 110 ** already. 111 */ 112 lock.l_type = F_WRLCK; 113 114 if( eFileLock==RESERVED_LOCK ){ 115 lock.l_start = RESERVED_BYTE; 116 lock.l_len = 1L; 117 }else{ 118 lock.l_start = SHARED_FIRST; 119 lock.l_len = SHARED_SIZE; 120 } 121 122 if( unixFileLock(pFile, &lock) ){ 123 tErrno = errno; 124 rc = sqliteErrorFromPosixError(tErrno, SQLITE_IOERR_LOCK); 125 if( rc!=SQLITE_BUSY ){ 126 storeLastErrno(pFile, tErrno); 127 } 128 } 129 } 130 131 if( rc==SQLITE_OK ){ 132 pFile->eFileLock = eFileLock; 133 pInode->eFileLock = eFileLock; 134 }else if( eFileLock==EXCLUSIVE_LOCK ){ 135 pFile->eFileLock = PENDING_LOCK; 136 pInode->eFileLock = PENDING_LOCK; 137 } 138 139 end_lock: 140 sqlite3_mutex_leave(pInode->pLockMutex); 141 return rc; 142 }
注:相比源代码,该代码为更加精简直观,删除了大量类似OSTRACE(),用于Debug的代码,如需阅读源码,可查看os_unix.c
其流程图大致如下:
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!