AntDB-M数据库锁分析,不要错过!

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了AntDB-M数据库锁分析,不要错过!相关的知识,希望对你有一定的参考价值。

AntDB数据库始于2008年,在运营商的核心系统上,为全国24个省份的10亿多用户提供在线服务,具备高性能、弹性扩展、高可靠等产品特性,峰值每秒可处理百万笔通信核心交易,保障系统持续稳定运行近十年,并在通信、金融、交通、能源、物联网等行业成功商用落地。锁是OLTP数据库中保证事务一致性的一种重要手段,本文主要阐述AntDB-M(AntDB内存引擎)的锁相关设计。

 

概述

AntDB-M的锁的设计分为两层,1)元数据锁;2)数据锁。 在获取数据锁之前,必须先获取元数据锁,从而保护元数据与底层数据的一致性。

 

元数据锁

元数据锁代表了一个连接对于表元数据的访问能力。 AntDB-M根据操作语句对元数据、数据的不同要求,设计了多种元数据锁类型。以满足对元数据、数据的不同读、写限制、以及并发能力。 由于排他锁具有较高优先级,并且较低的并发度。因此为了避免阻塞大量其他类型的锁请求,在排他锁获取一定数据量后,会优先授权其他锁。

 

数据锁

数据锁代表对一个数据对象(表、记录)的访问能力。能力分为两种:1)读;2)写(读、删除、更新)。根据数据对象访问特点,通过锁类型、锁粒度、锁范围等对数据对象赋予了不同的访问限制。

后文主要为数据锁相关设计。

 

数据锁

锁类型

  • 共享锁:一个数据对象(表、记录),同一时刻可以被加锁多次。代表具有读的能力。增加数据访问的并发度。
  • 排他锁:一个数据对象(表、记录),同一时刻仅可以被加锁加一次。代表具有写的能力。确保数据的完整性、一致性。
  • 锁粒度:标识锁的数据对象表、记录,分为:表锁、行锁。
  • 意向锁:意向锁为表锁的一种,表示表内“可能”有行锁。意向锁分为意向共享锁(IS)、意向排他锁(IX)。 意向锁实际为提高表锁(表共享锁、表排他锁)效率而设置的一种锁。意在避免申请表锁时去判断是否有行锁。
  • 锁兼容:AntDB-M的锁分为两级:表、记录。申请锁时必须先申请表级、然后申请记录级(有需要时)。锁兼容是指同一个数据对象(表、记录)上申请相同类型、或者不同类型锁时的许可情况。兼容则允许申请,不兼容则不允许申请。锁类型明细:意向共享锁IS、意向排他锁IX、表共享锁TS、表排他锁TX、记录共享锁RS、记录排他锁RX;

 

锁的兼容性如下。

纵轴:已有锁; 横轴:待申请锁。

"Y":兼容; "-":不兼容;

 

表级

AntDB-M数据库锁分析,不要错过!_antdb

表1:表级锁

 

记录级

AntDB-M数据库锁分析,不要错过!_antdb数据库_02

表2:记录级锁

 

锁与闩锁

锁的对象是表的数据对象(表、记录)。对于记录上的锁分为两种:行锁、闩锁。其中行锁用于非MVCC模式下对行的读写控制,闩锁用于MVCC模式下对记录的读写控制。


锁设计

每个锁都有其所归属的事务,或者说锁的申请者是事务。每个事务都有其拥有的、待申请的表锁、行锁链表。

 

表锁

每个表都有一个表锁信息,包括:表锁链表、已经加锁个数、锁个数(含待加锁)。 每个事务在开始对表访问前,都需要先对表申请相应的表锁。所有申请都按先后顺序排队。

 

加锁校验

如果一个事务在申请表锁时,表已经被删除、或者表被重命名,则不允许对表进行加锁。

 

表锁申请

在对一个表访问前,都需要先对表申请合适的表锁。

  • IS:select;
  • IX:insert, update, delete; select ... for update;
  • TS:lock tables ... read;
  • TX:lock tables ... write; alter table, drop table, truncate, rename;

 

表锁升级

一个事务,对于一个表只应当持有一种类型的表锁。 这里需要从几个方面来说,首先,从锁的兼容性,对于不兼容的锁,不允许同时持有,这类情况必须选择出一种出来。其次,从锁的约束力来看,排他锁约束力要高于共享锁(TX>TS、IX>IS),表锁高于表意向锁。再次,相同类型的锁重复申请没有必要。 结合之前的锁的兼容性来看,如表3:锁兼容性:‘-’表示不兼容的,‘X’表示同级不需重复申请的。剩下的只有A,B,C,D四种情况,这四种情况通过锁的约束力来看,都有不同的约束力,最终以较高约束力的锁为准。

AntDB-M数据库锁分析,不要错过!_antdb_03

表3:锁兼容性

 

综上,一个事务只需要一种类型的表锁。对于已经持有某类表锁时,再次申请表锁采取锁升级来处理。锁升级包括两类:1)对已申请锁升级,即改变已经持有锁,改变成功才算加锁成功;2)对新申请锁升级,不改变已经持有锁,加锁立即成功。

AntDB-M数据库锁分析,不要错过!_国产数据库_04

表4:表锁升级规则

 

横轴:已持有锁;纵轴:待申请锁
-:不需要升级;其他:升级目标

 

(1)加锁等待

不管是首次申请的锁,还是对已有的锁进行升级,都要判断其他事务已经申请到的表锁与当前申请的表锁是否兼容。

  • 当前表上没有锁:直接加锁成功。
  • 兼容:表上所有其他事务持有的锁,与当前申请的锁兼容,则加锁成功。
  • 不兼容:表上所有其他事务持有的锁,存在与当前申请的锁不兼容,当前事务需要阻塞等待持有不兼容锁的事务对表解锁。

注:加锁等待过程中,如果表被删除、或者被重命名,则仍然认为加锁失败。

 

(2)等待超时

每种表锁类型都会有相应的加锁计数,阻塞等待必须等到所有不兼容的锁都解锁。等待超时时间可配置,默认为50ms,最大600ms,超时则加锁失败。 对于DDL操作,超时时间要长一些,默认为1800ms,可配置,最大7200ms。

 

(3)不同事务间影响

不同事务间存在并发,从表锁的角度来看事务之间相互影响有两个因素:1)锁不兼容,等待持有锁的释放锁;2)并发处理,表锁临界资源等待;其中因素2为次要原因,影响很小,可以忽略。造成锁等待的主要因素为锁不兼容。 若与表上已有锁不兼容,则事务必须等待其他事务对其持有的不兼容锁释放。若兼容,则无需等待。事务的阻塞与否,只与锁不兼容相关,与事务创建先后顺序、锁申请先后顺序无关。

 

(4)锁的分配顺序

同一时刻,表上可能存在多个阻塞的表锁申请,这些被阻塞的表锁类型可能相同,可能不同。 一个表上的所有表锁,不管是否持有锁,都按申请先后顺序排队。但是该顺序并不影响锁的阻塞与否。加锁成功与否,只与已持有锁是否兼容相关,与申请先后顺序无关。

 

 

行锁

行锁只有两种1)共享锁RS;2)互斥锁RX。 一行记录上可以加多个共享锁,只能有一个互斥锁。

 

(1)事务与行锁

每个事务都有自己的锁链表,记录了该事务持有的行锁。行锁的拥有者是事务。

 

(2)行锁链表

表记录的每行都有自己的锁链表,记录了已经持有或者正在等待的锁对象。 链表按照加锁申请顺序排队。

 

(3)授予条件

对于一个锁的授予,总体原则:

  • 互斥原则,RX锁只能授予一个,且无其他RS锁;
  • 共享原则,RS锁可以授予多个,且无其他RX锁;
  • 顺序原则,沿着行锁链表从前向后依次授予; 不能乱序,否则会产生系统实现导致的数据不一致。

基于以上原则,锁授予条件如下,不满足条件的必须等待。

  • 当前唯一的锁对象:只有当前一个锁对象,满足一切原则,可以立即授予锁。
  • 申请RX锁,必须位于链表首部,没有其他锁:RX是互斥的,除了待申请的RX锁,不能有其他任何锁存在。 如果不是位于链表首部,则会违反顺序原则。 如果有其他锁,也违反顺序原则。
  • 申请RS锁,该锁行锁链表前方锁对象都已经加锁,且无RX锁:前方锁对象都已经加锁,确保顺序原则,具体实现为获取RS锁后,通知锁链表后方锁对象可以获取锁。无RX锁,确保RX互斥原则。

 

(4)新建行锁

一个事务对一行记录首次加锁、或者解锁后再次申请锁时,需要为该事务新建一个行锁对象,添加到行锁的锁链表尾部。同时会被添加到事务的行锁列表中。 对于新建的行锁对象立即判断是否可以授予锁,不满足条件的必须等待条件满足。

 

(5)行锁升级

由于RX锁具有排他性,因此认为RX锁比RS锁等级更高,即约束力更强。一个事务对于一行记录,只允许持有一种锁类型,避免过多的锁数量,也没必要,因为RX锁也具有读属性。 当一个事务已经持有某行的共享锁时,根据持有锁与申请锁等级判断是否需要对锁进行升级。有三种情况:1)同级不升级;2)对申请锁升级;3)对已有所升级;

  • 同级不升级:当申请锁等级与已持有锁相同时,无论是申请RS、RX锁,都不需要重新申请新的锁,使用已经持有的锁。此时,无需等待,立即加锁成功。
  • 对申请锁升级:当申请锁等级低于已持有锁时,即已持有RX锁,申请RS锁,无需申请新的锁,使用已经持有的锁,即对申请的锁升级为直接使用已持有的锁。此时,无需等待,立即加锁成功。
  • 对已有所升级:当申请锁等级高于已持有锁时,即已持有RS锁,申请RX锁,需要对已有的RS锁进行升级。 升级过程如下:

1. 申请锁对象

为给该行申请一个RX锁对象;

 

2. 判断是否可以立即升级
立即升级条件:1)已持有锁处于行锁链表头部;2)没有其他事务持有RS锁。 如果满足条件,就直接将已持有锁的锁类型调整为RX锁。同时释放刚申请的RX锁对象。

 

3. 加入行锁链表

如果不能立即升级,则将RX锁对象加入到行锁链表中。加入方式与新建行锁添加到行锁链表尾部不同。升级锁是添加到行锁链表中已经持有锁的最后一个锁的后边。这样做的目的是对锁升级时,不必等待其他未授予锁的锁对象。尤其如果这些未授予的锁中有RX时,会违反顺序原则。 行锁同时会被添加到事务的行锁链表中。

 

4. 等待授予
待升级锁加入链表后,便进入等待前边所有的锁的释放。一旦等到通知,此时意味着前边的锁都已经释放。此时需要将已经持有的锁从行锁链表移除,将锁授予新添加的RX锁对象。同时会将事务的该行的行锁更新为该行锁。

 

(6)临界资源

行锁的临界资源为保证行锁独占、行锁链表管理的必需资源。每行记录必须要有对应的临界资源。AntDB-M在为一个表创建出一块新的数据块空间时,就会为每行记录分配好临界资源,尽管数据空间中的记录行还没实际使用。

  • 空间问题:这样就有一些资源浪费的嫌疑,不过临界资源开销很小1000万行记录,也就40M的内存开销,并且表数据块空间不是一下开辟很多块,当没有表空间容纳数据时才会新开辟,因此从空间资源角度是可以接受的。
  • 竞争问题:如果对多行公用一个临界资源,也会引入新的竞争问题,本来锁的不是同一行记录,却产生了竞争。对于热点数据可能会有较多的竞争,相对内存资源,CPU资源更为宝贵。
  • 无效唤醒:如两行R1,R2共享一个临界资源。正常情况下,一个行锁链表,前者释放,唤醒后者。但是两行共享一个临界资源,R1释放锁后,可能其行锁链表已经空了,原本没有必要。带来一个行锁链表无效唤醒的问题。在处理时必须小心处理。
  • 虚假唤醒:临界资源必须处理好虚假唤醒问题,在被唤醒后,必须检测当前是否满足锁的授予条件。

 

闩锁

为了提升MVCC对数据的并发读写性能,对于记录的读写并不加共享锁RS、或排他锁RX。而是通过闩锁来控制。闩锁本质上是一种读写锁。

 

行锁与闩锁区别

  • 不需要升级:闩锁不需要做锁升级。
  • 不需要链表:由于闩锁不涉及升级,因此只需要计数即可,不需要区分不同的闩锁对象。
  • 锁定时间更短:相对于行锁互斥锁的锁定时间更短。

 

总结

本文主要讲述了AntDB-M的数据锁的类型、粒度、兼容性。锁的具体设计上又分为表锁、行锁、闩锁。以及不同锁的申请、释放、升级过程,以及不同锁间的区别。限于篇幅,与锁相关的死锁检测、幻读、脏读相关内容没有涉及。

 

关于AntDB数据库

AntDB数据库始于2008年,在运营商的核心系统上,为全国24个省份的10亿多用户提供在线服务,具备高性能、弹性扩展、高可靠等产品特性,峰值每秒可处理百万笔电信核心交易,保障系统持续稳定运行近十年,并在通信、金融、交通、能源、物联网等行业成功商用落地。

 

用Python实现童年的21款小游戏,有你玩过的吗?(不要错过哦)

Python为什么能这么火热,Python相对于其他语言来说比较简单,即使是零基础的普通人也能很快的掌握,在其他方面比如,处于灰色界的爬虫,要VIP的视频,小说,歌,没有爬虫解决不了的;数据挖掘及分析,淘宝就是例子,想开个淘宝店,需要获取相关商品信息,这时数据分析就能解决等等;游戏开发、自动化测试、人工智能,黑客入门语言等等等。

接着介绍下去今天就停不了啦,只要对于Python有过了解的,用过的,应该都知道他的厉害之处,
进入主题,游戏,今天直接上干货,童年你玩过的21款游戏源码分享。

1、五子棋

展示:

游戏源码:

'''游戏开始界面'''
class gameStartUI(QWidget):
    def __init__(self, parent=None, **kwargs):
        super(gameStartUI, self).__init__(parent)
        self.setFixedSize(760, 650)
        self.setWindowTitle('五子棋 —— 九歌')
        self.setWindowIcon(QIcon(cfg.ICON_FILEPATH))
        # 背景图片
        palette = QPalette()
        palette.setBrush(self.backgroundRole(), QBrush(QPixmap(cfg.BACKGROUND_IMAGEPATHS.get('bg_start'))))
        self.setPalette(palette)
        # 按钮
        # --人机对战
        self.ai_button = PushButton(cfg.BUTTON_IMAGEPATHS.get('ai'), self)
        self.ai_button.move(250, 200)
        self.ai_button.show()
        self.ai_button.click_signal.connect(self.playWithAI)
        # --联机对战
        self.online_button = PushButton(cfg.BUTTON_IMAGEPATHS.get('online'), self)
        self.online_button.move(250, 350)
        self.online_button.show()
        self.online_button.click_signal.connect(self.playOnline)
    '''人机对战'''
    def playWithAI(self):
        self.close()
        self.gaming_ui = playWithAIUI(cfg)
        self.gaming_ui.exit_signal.connect(lambda: sys.exit())
        self.gaming_ui.back_signal.connect(self.show)
        self.gaming_ui.show()
    '''联机对战'''
    def playOnline(self):
        self.close()
        self.gaming_ui = playOnlineUI(cfg, self)
        self.gaming_ui.show()
 
 
'''run'''
if __name__ == '__main__':
    app = QApplication(sys.argv)
    handle = gameStartUI()
    font = QFont()
    font.setPointSize(12)
    handle.setFont(font)
    handle.show()
    sys.exit(app.exec_())

2、保卫森林大作战

展示:

游戏源码:

import cfg
import pygame
from modules import *
  
  
'''主函数'''
def main():
 pygame.init()
 pygame.mixer.init()
 pygame.mixer.music.load(cfg.AUDIOPATHS['bgm'])
 pygame.mixer.music.play(-1, 0.0)
 pygame.mixer.music.set_volume(0.25)
 screen = pygame.display.set_mode(cfg.SCREENSIZE)
 pygame.display.set_caption("塔防游戏 —— 九歌")
 **调用游戏开始界面**
 start_interface = StartInterface(cfg)
 is_play = start_interface.update(screen)
 if not is_play:
  return
 **调用游戏界面**
 while True:
  choice_interface = ChoiceInterface(cfg)
  map_choice, difficulty_choice = choice_interface.update(screen)
  game_interface = GamingInterface(cfg)
  game_interface.start(screen, map_path=cfg.MAPPATHS[str(map_choice)], difficulty_path=cfg.DIFFICULTYPATHS[str(difficulty_choice)])
  end_interface = EndInterface(cfg)
  end_interface.update(screen)
  
'''run'''
if __name__ == '__main__':
 main()

3、超级大迷宫

展示:

游戏源码:

import cfg
import sys
import pygame
from modules import *
  
  
'''主函数'''
def main(cfg):


  **初始化**
 pygame.init()
 pygame.mixer.init()
 pygame.font.init()
 pygame.mixer.music.load(cfg.BGMPATH)
 pygame.mixer.music.play(-1, 0.0)
 screen = pygame.display.set_mode(cfg.SCREENSIZE)
 pygame.display.set_caption('Maze —— 九歌')
 font = pygame.font.SysFont('Consolas', 15)

  开始界面
 Interface(screen, cfg, 'game_start')

  记录关卡数
 num_levels = 0

  记录最少用了多少步通关
 best_scores = 'None'

 关卡循环切换
 while True:
  num_levels += 1
  clock = pygame.time.Clock()
  screen = pygame.display.set_mode(cfg.SCREENSIZE)

   --随机生成关卡地图
  maze_now = RandomMaze(cfg.MAZESIZE, cfg.BLOCKSIZE, cfg.BORDERSIZE)

   --生成hero
  hero_now = Hero(cfg.HEROPICPATH, [0, 0], cfg.BLOCKSIZE, cfg.BORDERSIZE)
 
   --统计步数
  num_steps = 0

  --关卡内主循环
  while True:
   dt = clock.tick(cfg.FPS)
   screen.fill((255, 255, 255))
   is_move = False


   # ----↑↓←→控制hero
  
 for event in pygame.event.get():
    if event.type == pygame.QUIT:
     pygame.quit()
     sys.exit(-1)
    elif event.type == pygame.KEYDOWN:
     if event.key == pygame.K_UP:
      is_move = hero_now.move('up', maze_now)
     elif event.key == pygame.K_DOWN:
      is_move = hero_now.move('down', maze_now)
     elif event.key == pygame.K_LEFT:
      is_move = hero_now.move('left', maze_now)
     elif event.key == pygame.K_RIGHT:
      is_move = hero_now.move('right', maze_now)
   num_steps += int(is_move)
   hero_now.draw(screen)
   maze_now.draw(screen)
   
    ----显示一些信息
   showText(screen, font, 'LEVELDONE: %d' % num_levels, (255, 0, 0), (10, 10))
   showText(screen, font, 'BESTSCORE: %s' % best_scores, (255, 0, 0), (210, 10))
   showText(screen, font, 'USEDSTEPS: %s' % num_steps, (255, 0, 0), (410, 10))
   showText(screen, font, 'S: your starting point D: your destination', (255, 0, 0), (10, 600))
   
    ----判断游戏是否胜利
   if (hero_now.coordinate[0] == cfg.MAZESIZE[1] - 1) and (hero_now.coordinate[1] == cfg.MAZESIZE[0] - 1):
    break
   pygame.display.update()
   
  --更新最优成绩
  if best_scores == 'None':
   best_scores = num_steps
  else:
   if best_scores > num_steps:
    best_scores = num_steps
    
   --关卡切换
  Interface(screen, cfg, mode='game_switch')
  
'''run'''
if __name__ == '__main__':
 main(cfg)

太多了,需要获得资源的,可以私信我或在评论区扣1 获得。

4、滑雪游戏

5、接金币游戏

6、塔防小游戏

7、坦克大战

8、连连看

9、扫雷

10、超级玛丽

11、炸弹人


太多了,偷懒就不一一展示,这些游戏陪伴了我们每个不同成长阶段的快乐,需要资源的可以关注我+私信。我是一名喜欢分享的程序媛,感谢观看。

以上是关于AntDB-M数据库锁分析,不要错过!的主要内容,如果未能解决你的问题,请参考以下文章

互斥锁Mutex:鸿蒙轻内核中处理临界资源独占的“法官”

ThreadX内核源码分析 - 线程同步之互斥锁及动态优先级

multi-thread debug

互斥锁Mutex:鸿蒙轻内核中处理临界资源独占的“法官”

使用Windbg分析多线程临界区死锁问题分享

使用Windbg分析多线程临界区死锁问题分享