【数据库】基于时间戳的并发访问控制,乐观模式,时间戳替代情势及存在的问 ...

打印 上一主题 下一主题

主题 856|帖子 856|积分 2568

利用时间戳的并发控制

   ​专栏内容
  

  • 手写数据库toadb
    本专栏主要介绍如何从零开发,开发的步调,以及开发过程中的涉及的原理,碰到的题目等,让大家能跟上并且可以一起开发,让每个需要的人成为加入者。
    本专栏会定期更新,对应的代码也会定期更新,每个阶段的代码会打上tag,方便阶段学习。
    ​开源贡献
  

  • toadb开源库
  个人主页:我的主页
管理社区:开源数据库
座右铭:天行健,君子以发奋图强;地势坤,君子以厚德载物.
  
  
前言

随着信息技能的飞速发展,数据已经渗透到各个领域,成为今世社会最重要的资产之一。在这个大数据期间,数据库理论在数据管理、存储和处置惩罚中发挥着至关重要的作用。然而,很多读者可能对数据库理论感到困惑,不知道如何选择合适的数据库,如何设计有用的数据库结构,以及如何处置惩罚和管理大量的数据。因此,本专栏旨在为读者提供一套全面、深入的数据库理论指南,帮助他们更好地理解和应用数据库技能。
数据库理论是研究如何有用地管理、存储和检索数据的学科。在今世信息化社会中,数据量呈指数级增长,如何高效地处置惩罚和管理这些数据成为一个重要的题目。同时,随着云计算、物联网、大数据等新兴技能的不停发展,数据库理论的重要性日益凸显。
因此,本专栏的分享盼望可以提高大家对数据库理论的熟悉和理解,对于感兴趣的朋友带来帮助。
概述

在数据库中如何包管并发事件时,数据的一致性,也就是可串行化,会有采用调度器来进行调和各事件中动作的序次,以衣是否可以执行等。调度器采用的模子主要有几种:


  • 基于封锁的调度模子
  • 基于时间戳的调度模子
  • 基于有用性确认的调度模子
前几篇博文中分享了基于封锁的调度模子,本文主要介绍基于时间戳的调度模子,主要从时间戳的概念,可以包管的行为和存在的题目,调度规则,以及多版本的优化,与封锁模子的团结利用等方面进行介绍。
时间戳介绍

也就是记录上次读和写每个数据库元素的事件时间点,同时每个事件也有一个时间戳,记录它的开始时间点。
当有事件要请求该数据库元素时,比较这两个时间,根据事件的时间戳来调度,来确保串行调度。
记录时间戳的方法



  • 理论上当多个事件开始的时间隔断大于时间最小计数时,利用时间来记录是可以到达目的的,但是往往时间的精度不足以记录多个同时开始的事件。
  • 调度器维护一个计时器。每当一个事件开始时,计数器就加1,而新值成为该事件的时间戳。这种方法与时间无关,但是它们具有时间的特性,单调递增,不会重复,总是包管晚的事件比开始早的事件具有更高的时间戳;
事件提交的记录

当一个事件T读到另一事件U所写的数据,这一行为也是符合串行化规则,但是事件U最后停止了,并没有提交,如许事件T读到的是脏数据,这一题目肯定会导致数据库状态变得不一致,这是任何调度器都要防止的脏读。
除了两个事件和数据库元素上的时间戳外,还需要记录一个事件的提交状态位,当事件没有提交时,调度器也需要阻止别的事件的访问请求。
可以解决的题目

如果事件在开始的那一时候就立即执行结束,那也就不会发生非可串行化的题目。往往事件中的各个动作都会连续一段时间,这就会过晚读和过晚写的题目发生,而当事件停止时,读取的此事件写的数据,就会发生脏读的环境。
过晚的读



  • 题目形貌
    事件执行的时间轴是如许的

如图所示,事件T的读在事件U的写之后,而事件U的开始时间晚于事件T,这就导致事件T读到的数据不一致。


  • 解决方法
    当事件T的进行读请求时,发现当前数据元素上的时间戳晚于本身的事件开始时间戳时,事件T应该是需要停止,它什么都不能做了。
过晚的写



  • 题目形貌
    事件执行的时间轴是如许的

如图所示,事件U开始时间晚于事件T,而事件U的读利用早于事件T,本应该事件U可以读到T写入的值,但是T的写入更晚。


  • 解决方法
    事件T由于时间戳晚于数据元素上的时间戳,也就是事件U访问的时间戳,应该停止事件T,让事件U可以读取正确的数据。
脏数据的题目

事件提交标志的设置,就是用来解决这个题目的,先来看两个题目。


  • 题目一
    |事件U | 事件T|
    |:–|:–|
    |begin; ||
    |write(X) | |
    || begin;|
    ||read(X)|
    |abort||
    ||commit;|
  • 题目二
    |事件U | 事件T|
    |:–|:–|
    |begin; ||
    |write(X) | |
    || begin;|
    ||write(X)|
    ||commit;|
    |abort||
对于题目一,由于事件U在事件T之前启动,并写入X,所有事件T读取X是符合上面时间戳的规则,但是当事件U终极停止时,事件T读取的X就是脏数据,是数据库中本不存在的数据;
对于题目二,风趣的事情来了,此时事件T提交后,其实它是基于事件U的,比如X=1,事件U写入后X=2, 事件T写入后X=3,那么提交乐成后X=3;而事件U回滚后,好像什么都不需要做,还是事件U回滚为X=1,事件T重新再做一遍呢?


  • 解决方法
    对于题目一的此类题目,请求读利用时,需要看当前数据元素是否已经提交,如果没有提交,需要停止当前请求,或推迟到该数据库元素提交之后再处置惩罚。
而对于题目二的此类题目,写利用请求时,也同样需要判断当前数据元素是否已经提交,如果没有提交,需要停止当前请求,或推迟到该数据库元素提交之后再处置惩罚。 当然,更晚的写也可以什么都不做,这被称为Thomas写法则,最后事件U停止后,它要回退它的写入和数据库元素上的时间戳,但是事件T的写入被跳过了,同时也提交完成了,此时想规复事件T的利用已经不可能了。
mysql中的体现

  1. mysql> show variables like 'transaction%';
  2. +----------------------------------+-----------------+
  3. | Variable_name                    | Value           |
  4. +----------------------------------+-----------------+
  5. | transaction_alloc_block_size     | 8192            |
  6. | transaction_allow_batching       | OFF             |
  7. | transaction_isolation            | REPEATABLE-READ |
  8. | transaction_prealloc_size        | 4096            |
  9. | transaction_read_only            | OFF             |
  10. | transaction_write_set_extraction | XXHASH64        |
  11. +----------------------------------+-----------------+
  12. 6 rows in set (0.00 sec)
  13. mysql> begin;
  14. Query OK, 0 rows affected (0.00 sec)
  15. mysql> select * from test_concurrent;
  16. +------+
  17. | i    |
  18. +------+
  19. |    5 |
  20. +------+
  21. 1 row in set (0.00 sec)
  22. -- 这此时另外启动一个事务,将i修改为6,并提交事务
  23. mysql> select * from test_concurrent;
  24. +------+
  25. | i    |
  26. +------+
  27. |    5 |
  28. +------+
  29. 1 row in set (0.00 sec)
  30. mysql> update test_concurrent set i = 3 where i = 5;
  31. Query OK, 0 rows affected (0.00 sec)
  32. Rows matched: 0  Changed: 0  Warnings: 0
  33. mysql> commit;
  34. Query OK, 0 rows affected (0.00 sec)
  35. mysql> select * from test_concurrent;
  36. +------+
  37. | i    |
  38. +------+
  39. |    6 |
  40. +------+
  41. 1 row in set (0.00 sec)
复制代码
可以看到mysql中,当前事件可以看到i=5,但确修改不乐成,返回0 rows被updated,这就是一个很迷惑的现象。
基于时间戳调度的规则

经过上面题目的分析,现在我们概括基于时间戳调度的规则。
调度器选择

对于来自事件的读写利用请求,调度器有几种选择:


  • 同意该请求
  • 推迟请求
  • 停止请求事件
读写请求的处置惩罚

调度器收到读写利用请求,

  • 收到读利用请求时,检查当前数据库元素上次利用事件的提交状态,
   

  • 如果已经提交,则再检查时间戳的先后序次,如果请求事件的时间戳大于当前数据元素的时间戳,则可以同意请求,并将时间戳更新为当前事件;如果事件时间戳小于当前数据元素的时间戳,则需要停止;
  • 如果尚未提交,则请求事件需要推迟;
  

  • 当收到写利用请求时,先检查当前事件与数据库元素上的时间戳,
   

  • 如果请求事件的时间戳大于当前数据元素的时间戳,再检查数据元素上次利用的事件是否提交,如果已经提交,则同意本次写请求;如果未提交,则需要推迟本次请求;
  • 如果事件时间戳小于当前数据元素的时间戳,本次请求事件需要停止;
  

  • 当收到事件提交请求时,更新数据元素上的提交状态;同时唤醒等待的事件请求;
  • 当收到事件T停止请求时,那么回退事件T对应的所有利用数据;等待的事件需要重新发起读或写请求,由于需要检查事件T的写被停止后是否合法。
多版本时间戳

基于时间戳的并发控制调度器,如上面介绍的,会存在读写之间冲突,所以在这个底子上进行了一个重要的演进,就是同时保留数据库元素的多个带不同时间戳的版本,使得读写可以同时进行。
多版本时间戳的流程与上面流程类似:

  • 当收到写利用请求时WT(X),如果请求被同意,那么X的一个新版本Xi被创建,它的时间戳为Ti(X);
  • 此时收到一个读利用请求时RU(X)时,最新版本检查不通过期,查找时间戳小于事件U的版本X;也是就WT(X)执行前的版本,就是当前可读的版本,同意RU(X)在版本X上的读请求;
  • 数据元素的时间戳与对应的版本有关;
  • 当然再有事件的写请求来时,还是需要在最后的版本Xi上处置惩罚;
  • 旧版本的清理,当X的某个版本上的时间戳小于任何当前活泼事件的时间戳时,就可以清理掉它了。
多版本时间戳的方式,解决了读写并发时的性能题目。
时间戳与封锁

在大多数只读事件大概并发读写同一元素的环境不频繁时,基于时间戳的调度比较有优势;
而当读写并发比较高,而且对同一数据库元素竞争较大时,封锁调度反而比较优,由于此种环境下基于时间戳的调度,需要进行频繁的回退利用。
在今世商用数据库中,会将事件分为只读事件和读写事件,在只读事件时,只利用时间戳的方式,而只读事件时采用两阶段锁的方式。
总结

基于时间戳的调度模子可以说是一种乐观的模子,它假设没有非可串行化行为发生,并且只有在违例发生时才会进行修正大概停止。与此相反,封锁的调度模子是假设非可串行化行为一定会发生,那么提进步行防备,并且推迟可能发生的事件,但不停止它们,它是一种灰心模子。
这两种模子,如果对于大量只读利用时,乐观型好于灰心型调度器。
末端

   非常感谢大家的支持,在欣赏的同时别忘了留下您宝贵的评论,如果觉得值得鼓励,请点赞,收藏,我会更加积极!
  作者邮箱:study@senllang.onaliyun.com
如有错误大概疏漏欢迎指出,互相学习。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农妇山泉一亩田

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表