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 }
View Code
复制代码

    注:相比源代码,该代码为更加精简直观,删除了大量类似OSTRACE(),用于Debug的代码,如需阅读源码,可查看os_unix.c

    其流程图大致如下:

 

posted @     阅读(557)  评论(0编辑  收藏  举报
编辑推荐:
· 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框架的用法!
点击右上角即可分享
微信分享提示