PostgreSQL中shared_buffers定义数据缓冲区的大小,会在操作系统内存开辟一块共享的区域,既然是共享的,所有的后台进程都可以访问,必然会导致争用,那么哪些进程可以访问并修改,哪些进程不可以,又怎样保证数据的一致性,这就需要锁来实现。
需要注意的是,这里的锁是内存锁,和表或索引的行锁、表锁是没有任何关系的。
1.BufMappingLock
BufMappingLock保护整个缓冲表的数据完整性,他是一种轻量锁,它有共享模式和独占模式,当在缓冲表中查询条目时,后端进程会持有共享的BufMappingLock,插入或删除条目时,后端进程会持有独占的BufMappingLock。
为了减少对BufMappingLock的争用,BufMappingLock会被分为多个分区,每个BufMappingLock分区保护着一部分的缓冲表。在9.4之前,BufMappingLock默认会分为16个分区,在9.4之后,BufMappingLock默认分为128个。
当然,缓存表不仅仅只有BufMappingLock,例如缓存表内部会使用自旋锁(spin lock) 来删除数据项。
2.缓存描述符相关的锁
在9.6之前,和缓存描述符相关的有三种锁:内容锁(content_lock)、IO进行锁(io_in_progress_lock)和自旋锁(spin lock)。其中内容锁和IO进行锁是两种轻量级锁,控制对缓冲池的访问。自旋锁控制对缓存描述符的访问。
内容锁 是一个典型的强制限制访问的锁,他有共享和独占两种模式。
当读取页面时,后端进程以共享模式获取页面相应缓冲区描述符中的content_lock。
行下列操作之一时,则会获取独占模式的 content_lock。
- 将行(即元组)插入页面,或更改页面中元组的 txmin/t xmax 字段时(简单地说,这些字段会在相关元组被删除或更新行时发生更改)。
- 物理移除元组,或压紧页面上的空闲空间。
- 冻结页面中的元组。
IO进行锁(io_in_progress_lock)用于等待缓冲区上的I/0完成。当PostgreSQL进程加载/写入页面数据时,该进程在访问页面期间,持有对应描述符上独占的 io_in_progress_lock。
当访问标记字段与其他字段时,会用到自旋锁。例如refcount和usage_count的自增和自减。会先获取自旋锁,然后自增或自减,再释放自旋锁。同样在脏位设置时,也会用到自旋锁。
9.6之后使用原子操作替换了bufferdesc结构体的很多字段的功能。
refcount、usagecount标记位组合起来放入一个原子变量state字段中,而缓存区首部锁实际上是嵌入标记位中为一个bit,这种设计允许我们使用单个原子操作,而不是获取/释放自旋锁来实现一些操作。比如refcount的自增和自减,脏位的设置。
源码如下:
/*
* Buffer state is a single 32-bit variable where following data is combined.
*
* - 18 bits refcount
* - 4 bits usage count
* - 10 bits of flags
*
* Combining these values allows to perform some operations without locking
* the buffer header, by modifying them together with a CAS loop.
*
* The definition of buffer state components is below.
*/
#define BUF_REFCOUNT_ONE 1
#define BUF_REFCOUNT_MASK ((1U << 18) - 1)
#define BUF_USAGECOUNT_MASK 0x003C0000U
#define BUF_USAGECOUNT_ONE (1U << 18)
#define BUF_USAGECOUNT_SHIFT 18
#define BUF_FLAG_MASK 0xFFC00000U
/* Get refcount and usagecount from buffer state */
#define BUF_STATE_GET_REFCOUNT(state) ((state) & BUF_REFCOUNT_MASK)
#define BUF_STATE_GET_USAGECOUNT(state) (((state) & BUF_USAGECOUNT_MASK) >> BUF_USAGECOUNT_SHIFT)
/*
* Flags for buffer descriptors
*
* Note: BM_TAG_VALID essentially means that there is a buffer hashtable
* entry associated with the buffer's tag.
*/
#define BM_LOCKED (1U << 22) /* buffer header is locked */
#define BM_DIRTY (1U << 23) /* data needs writing */
#define BM_VALID (1U << 24) /* data is valid */
#define BM_TAG_VALID (1U << 25) /* tag is assigned */
#define BM_IO_IN_PROGRESS (1U << 26) /* read or write in progress */
#define BM_IO_ERROR (1U << 27) /* previous I/O failed */
#define BM_JUST_DIRTIED (1U << 28) /* dirtied since write started */
#define BM_PIN_COUNT_WAITER (1U << 29) /* have waiter for sole pin */
#define BM_CHECKPOINT_NEEDED (1U << 30) /* must write for checkpoint */
#define BM_PERMANENT (1U << 31) /* permanent buffer (not unlogged,
* or init fork) */
/*
* The maximum allowed value of usage_count represents a tradeoff between
* accuracy and speed of the clock-sweep buffer management algorithm. A
* large value (comparable to NBuffers) would approximate LRU semantics.
* But it can take as many as BM_MAX_USAGE_COUNT+1 complete cycles of
* clock sweeps to find a free buffer, so in practice we don't want the
* value to be very large.
*/
#define BM_MAX_USAGE_COUNT 5
1095

被折叠的 条评论
为什么被折叠?



