您现在的位置是:首页 > 文章详情

十、数据库锁

日期:2020-12-02点击:515

加锁是为了保证数据的一致性

一、按照锁粒度进行划分

    缺点 优点
表锁 数据表进行锁定
  • 锁定粒度很大
  • 发生锁冲突的概率也会较高
  • 数据访问的并发度低
  • 对锁的使用开销小,加锁会很快
页锁 数据页的粒度上进行锁定
  • 锁定的数据资源比行锁要多,一个页中可以有多个行记录
  • 会出现数据浪费的现象,但这样的浪费最多也就是一个页上的数据行
  • 页锁的开销介于表锁和行锁之间,会出现死锁
  • 锁定粒度介于表锁和行锁之间
  • 会出现死锁
  • 并发度一般
行锁 行的粒度对数据进行锁定
  • 锁的开销比较大
  • 加锁会比较慢,
  • 容易出现死锁情况
  • 锁定力度小
  • 发生锁冲突概率低
  • 可以实现的并发度高

  • 每个层级的锁数量是有限制的,锁会占用内存空间,锁空间的大小是有限的
  • 当某个层级的锁数量超过了这个层级的阈值时,就会进行锁升级
  • 锁升级就是用更大粒度的锁替代多个更小粒度的锁
  • 比如 InnoDB 中行锁升级为表锁,这样做的好处是占用的锁空间降低了,但同时数据的并发度也下降

二、数据库管理的角度对锁进行划分

共享锁
  • 读锁或 S 锁,共享锁锁定的资源可以被其他用户读取,但不能修改
  • 在进行SELECT的时候,会将对象进行共享锁锁定
  • 当数据读取完毕之后,就会释放共享锁,保证数据在读取时不被修改
  • 数据表加上共享锁的时候,该数据表就变成了只读模式
  • 表共享锁:LOCK TABLE 表名 READ;
  • 行共享锁:select语句后面 接上LOCK IN SHARE MODE
  • 解锁:UNLOCK TABLE;
排它锁
  • 独占锁、写锁或 X 锁,排它锁锁定的数据只允许进行锁定操作的事务使用
  • 其他事务无法对已锁定的数据进行查询或修改
  • 表排它锁:LOCK TABLE 表名 WRITE;
  • 行排他锁:select语句后面 接上FOR UPDATE
  • 解锁:UNLOCK TABLE;
意向锁
  • 给更大一级别的空间示意里面是否已经上过锁
  • 给某一行数据加上了排它锁,数据库会自动给更大一级的空间
  • 比如数据页或数据表加上意向锁,告诉其他人这个数据页或数据表已经有人上过排它锁了
  • 当其他人想要获取数据表排它锁的时候,只需要了解是否有人已经获取了这个数据表的意向排他锁即可
  • 比如加了一把行锁
  • 数据库在表或者页上添加意向锁
  • 当有其他操作需要添加表锁或者页锁时
  • 直接看到意向标示,就不同整表或者整页扫描
  • 来判断能不能上锁

 

三、共享锁会发生死锁

  • 当有多个事务对同一数据获得读锁的时候,可能会出现死锁的情况
  • 相互等待相互不释放手上资源

四、程序员的角度对进行划分

乐观锁
  • 认为对同一数据的并发操作不会总发生,属于小概率事件,不用每次都对数据上锁,也就是不采用数据库自身的锁机制
  • 通过程序来实现
  • 可以采用版本号机制或者时间戳机制实现
  • 版本号:UPDATE ... SET version=version+1 WHERE version=version
悲观锁
  • 一种思想,对数据被其他事务的修改持保守态度,
  • 通过数据库自身的锁机制来实现,从而保证数据操作的排它性

 

  • 乐观锁适合读操作多的场景,相对来说写的操作比较少。它的优点在于程序实现,不存在死锁问题,不过适用场景也会相对乐观,因为它阻止不了除了程序以外的数据库操作
  • 悲观锁适合写操作多的场景,因为写的操作具有排它性。采用悲观锁的方式,可以在数据库层面阻止其他事务对该数据的操作权限,防止读 - 写和写 - 写的冲突。

五、MVCC

全称是 Multiversion Concurrency Control,中文翻译过来就是多版本并发控制技术。从名字中也能看出来,MVCC 是通过数据行的多个版本管理来实现数据库的并发控制,简单来说它的思想就是保存数据的历史版本。这样我们就可以通过比较版本号决定数据是否显示出来(具体的规则后面会介绍到),读取数据的时候不需要加锁也可以保证事务的隔离效果

快照读读取的是快照数据,不加锁的简单的 SELECT 都属于快照读

  • 读写之间阻塞的问题,通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,提升事务并发处理能力
  • 降低了死锁的概率。MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行
  • 解决一致性读的问题。一致性读也被称为快照读,当查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果

InnoDB 中的 MVCC 实现

InnoDB 是如何存储记录的多个版本的。多版本对应 MVCC 前两个字母的释义:Multi Version

事务版本号
  • 每开启一个事务,我们都会从数据库中获得一个事务 ID(事务版本号)
  • 事务 ID 是自增长的,通过 ID 大小,我们就可以判断事务的时间顺序
 
行记录的隐藏列
  • InnoDB 的叶子段存储了数据页
  • 数据页中保存了行记录,而在行记录中有一些重要的隐藏字段
db_row_id 隐藏的行 ID
  • 用来生成默认聚集索引
  • 如果创建数据表的时候没有指定聚集索引
  • nnoDB 就会用这个隐藏 ID 来创建聚集索引
  • 采用聚集索引的方式可以提升数据的查找效率
db_trx_id 操作这个数据的事务 ID
  • 最后一个对该数据进行插入或更新的事务 ID
db_roll_ptr db_roll_ptr
  • 指向这个记录的 Undo Log 信息

Undo Log
  • InnoDB 将行记录快照保存在了 Undo Log 里,我们可以在回滚段中找到它们

  • 回滚指针将数据行的所有快照记录都通过链表的结构串联了起来
  • 每个快照的记录都保存了当时的 db_trx_id,那个时间点操作这个数据的事务 ID
  • 如果我们想要找历史快照,就可以通过遍历回滚指针的方式进行查找

Read View 如何工作

  • 多个事务对同一个行记录进行更新会产生多个历史快照
  • 历史快照保存在 Undo Log 里
  • 如果一个事务想要查询这个行记录,需要读取哪个版本的行记录?
  • 需要用到 Read View 它帮我们解决了行的可见性问题
  • Read View 保存了当前事务开启时所有活跃(还没有提交)的事务列表
  • 换个角度你可以理解为 Read View 保存了不应该让这个事务看到的其他的事务 ID 列表
trx_ids 系统当前正在活跃的事务 ID 集合
low_limit_id 活跃的事务中最大的事务 ID
up_limit_id 活跃的事务中最小的事务 ID
creator_trx_id 创建这个 Read View 的事务 ID

trx_ids 为 trx2、trx3、trx5 和 trx8 的集合,活跃的最大事务 ID(low_limit_id)为 trx8,活跃的最小事务 ID(up_limit_id)为 trx2

  1. 当前有事务 creator_trx_id 想要读取某个行记录,这个行记录的事务 ID 为 trx_id,那么会出现以下几种情况
  2. 如果 trx_id < 活跃的最小事务 ID(up_limit_id),也就是说这个行记录在这些活跃的事务创建之前就已经提交了,那么这个行记录对该事务是可见的
  3. 如果 trx_id > 活跃的最大事务 ID(low_limit_id),这说明该行记录在这些活跃的事务创建之后才创建,那么这个行记录对当前事务不可见
  4. 如果 up_limit_id < trx_id < low_limit_id,说明该行记录所在的事务 trx_id 在目前 creator_trx_id 这个事务创建的时候,可能还处于活跃的状态
  5. 因此需要在 trx_ids 集合中进行遍历,如果 trx_id 存在于 trx_ids 集合中,证明这个事务 trx_id 还处于活跃状态,不可见
  6. 否则,如果 trx_id 不存在于 trx_ids 集合中,证明事务 trx_id 已经提交了,该行记录可见

当查询一条记录的时候,系统如何通过多版本并发控制技术找到它

  1. 首先获取事务自己的版本号,也就是事务 ID
  2. 获取 Read View
  3. 查询得到的数据,然后与 Read View 中的事务版本号进行比较
  4. 如果不符合 ReadView 规则,就需要从 Undo Log 中获取历史快照
  5. 最后返回符合规则的数据
  • InnoDB 中,MVCC 是通过 Undo Log + Read View 进行数据读取,Undo Log 保存了历史快照,而 Read View 规则帮我们判断当前版本的数据是否可见
  1. 在隔离级别为读已提交(Read Commit)时,一个事务中的每一次 SELECT 查询都会获取一次 Read View
  2. 当隔离级别为可重复读的时候,就避免了不可重复读,这是因为一个事务只在第一次 SELECT 的时候会获取一次 Read View,而后面所有的 SELECT 都会复用这个 Read View

InnoDB 如何解决幻读

记录锁
  • 针对单个行记录添加锁
间隙锁(Gap Locking)
  • 可以帮我们锁住一个范围(索引之间的空隙)
  • 不包括记录本身
  • 采用间隙锁的方式可以防止幻读情况的产生
Next-Key 锁
  • 锁住一个范围,同时锁定记录本身,相当于间隙锁 + 记录锁
  • 可以解决幻读的问题

 

原文链接:https://my.oschina.net/u/3945954/blog/4770869
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章