精华内容
下载资源
问答
  • 什么是数据? 按照百度百科的词条定义: 数据就是数值,也就是我们通过观察、实验或计算得出的结果。数据有很多种,最简单的就是数字。数据也可以文字、图像、声音等。数据可以用于科学研究、设计、查证、数学等。...

    数据库系统概述(内核)

    本章将分为以下几个部分来讲诉:

    数据库系统的一些基本概念

    下面用问题的形式来回答一下一些数据库基本概念的定义:

    1. 什么是数据?
      按照百度百科的词条定义:
      数据就是数值,也就是我们通过观察、实验或计算得出的结果。数据有很多种,最简单的就是数字。数据也可以是文字、图像、声音等。数据可以用于科学研究、设计、查证、数学等。
      但在研究中的数据定义会更加宽泛,数据是:人们用来反映客观世界时记录下来可以鉴别的一些符号。当然,这些符号就有文字,数字,数据还包括多媒体数据。

    2. 数据有什么特点?
      数据是有语义的,比如说90这个数据,放在不同的情况下就有不同的含义,比如考试成绩90分,汽车车速90 km/h等等。

    3. 什么是数据库?
      数据库又叫Database,在程序员中又经常叫做DB,它的职责:
      1.就是让数据可以在计算机内可以长期的存储,同时数据又可以按照一定的数据模型组织,通俗的来讲就是表结构。
      2.可以让用户共享,就是可以让人进行crud。(Create)、(Retrieve)、(Update)、(Delete)
      3.为某个应用服务,现在的数据库可能服务的系统不止一个,但就是提供服务的意思。
      4.数据之间的关系,密切联系。
      当然,这种功能在操作系统中的文件系统也是可以做到的,但为啥用数据库系统不用文件系统这里就不过多的阐述了。

    4. 什么是数据库管理系统
      数据库管理系统又叫Database Management System (DBMS) ,就是我们通常用的用来创建和维护数据库的一些程序,比如Oracle11g,MySql等等。这里就会有人混了,咦你说的这个不是数据库吗?数据库其实就是一个存储数据的仓库,有了一些大家熟悉的数据库系统,程序员才能很好的操作数据。
      数据库系统和数据库之间还夹着操作系统呢。

    5. 什么是数据库模式?
      大学生在课堂上上数据库的课的时候老师就会讲数据库啊,有三种模式,内模式,外模式,模式(逻辑模式),但由于大学生一开始学的时候对数据库概念的不熟悉,所以就不容易弄懂,但这是内核讲解就不多阐述数据库的使用与设计过程了。

    数据库系统的一些实现问题

    接下来假装我们自己实现了一个简陋的数据库吧!暂且起名就叫Treeses DBMS吧!
     1.首先 咱DBMS要解决的第一件事就是 数据库文件怎么存储?
     第一步,我们要考虑我们的数据库要使用什么编码方式来存储,二进制?ASC-II码?

     东西都是要用文件来存储的,数据库也不例外,文件在存储器上最终都是用二进制来存储的,为了让我们看起来方便,我们这里就给咱数据库用 ASC-II码存储吧!

     于是我们就实现了细节,关系通过文件(ASC-II)存储,其中文件存在位置 /user/db/data/table/user.db里吧。
    文件表结构
     以上我们就是我们的数据库表的存储,但是既然是关系型数据库,那么一些模式(表结构)总也是要存储的吧,要不然怎么知道这个表是用来干啥的呢,于是,我们需要把这个表的一个表结构存储在文件 /user/db/model/table_structure.db 里吧。

    在这里插入图片描述
     于是乎,我们解决了表结构的存储问题。

     有了表结构,我们忽略中间的缓存,内存,索引,SQL解析等等等等的问题,我们要直接使用我们做好的SQL语句来进行查询吧!
      我们运行了以下语句:

    在这里插入图片描述
      然后我们要怎么处理这些个数据呢?
        第一步我们要读取table_structure.db的文件,获取user表的表结构;
        然后读取user对应的文件user.db,对于文件的每一行做检查,如果满足条件则输出,没有满足条件则跳到下一行。
       直到表结束了。

    很简单吧,接下来:
     假设我们还有一个表叫标准身材表(standard_stature.db),里面每一个身高对应一个标准的体重。
     我们运行了以下语句:

    在这里插入图片描述

      这个语句又应该怎么处理呢?

      第一步一样的,读取table_structure文件,获取user表和standard_stature表的表结构;
      然后我们忽略表连接的以下成本分析过程,就按规则来。
      读user文件,对于每一行数据,读standard_stature文件,对于每一行数据,生成连接元组,检查条件,若条件满足,则输出

      这个时候,Treeses DBMS就具备了定义表和一些数据操作的功能了。但是我们的Treeses DBMS存在一些什么问题呢?

    • 我们的查询效率真的很高吗?
        如果我们能先把选择操作先做了呢?那连接的操作是不是可以更少?以下做个简单的计算吧!
        假设table_structure表有1000个数据,standard_stature有100个数据,按照原来的方案。(不考虑内存)
            1000100=100000 io。
        现在,我们先对表进行筛选,比如A表满足要求的数据500条,B表满足要求的数据50条,那先两个表进行筛选的io次数 1000+50=1500,剩余的数据量为500条与50条,io次数500
      50=25000次,一共使用25000+1500=16500次。很明显,效率快了将近10倍,但实际上真的是所有情况下io次数都会减少吗?这个留待读者自己思考。

    • 我们的元组实际上是平铺在磁盘上的,考虑ASCII存储的代价是否过于昂贵呢?我们要修改表的元组是否太过于麻烦呢?我们要删除表元组是否代价也很高呢?

    • 我们的数据全都是从磁盘直接读取,要知道磁盘io的代价是非常高的,我们是不是要考虑缓冲区来优化我们的数据存取效率呢?

    • 如果有很多用户一起访问,我们怎么保证数据是否是正确的呢?比如说我修改了这个数据的时候,你要在读取这个数据,怎么办?

    • 数据库缺乏索引,查询效率是不是有点低下呢?每次查询数据都要读入整个的关系呢?至于索引为什么可以提高查询效率在后续的文章中会有详细的讲解。

    • 数据库的可靠性怎么保证呢?比如说突然断电了?咱数据库是不是没有一个数据库系统出现故障的时候的一个恢复机制,容易出现数据不一致的情况?

    • 数据库怎么提供给应用程序使用?没有API?

    • 数据字典的组织是不是做的特别差呢?

    综合评价,这数据库系统是否没法商用呢(唉,时隔多年这么评价自己当初做过的数据库系统也是尴尬,大家可以去 https://github.com/nainaiguang/Treeses.git )

    实际上数据库怎么表达是个很复杂的问题,在后续的文章中会陆续的说到,上面的方式只是一个简单的说明。
    

    数据库系统的一些设计问题

    数据库系统的设计问题指我们在使用数据库系统时候的一些问题,本文提供思路,具体学习还是要读者自己去学习。不是本系列的重点哈哈哈哈哈

    数据库模式设计的不规范可能带来的问题。如数据冗余,更新异常,插入异常,删除异常等等。

    数据库系统的一些存取问题

    就是如何定义数据库语言,数据库语言如下:

    1. 数据库定义语言 (Data Definition Language DDL),数据库存取模式
      包括:create bable;alter table drop table
    2. 数据库操作语言 (Data Manipulation Language DML),数据库存取数据
      包括:insert,delete,select update
    3. 数据库控制语言 (Data Control Language DCL) 存取访问控制信息
      包括: grant;recoke

    码字好累,各位看官支持支持,谢谢,下一文我将会为大家绘画一个数据库完整应该有的架构,以后的文章将会围绕这个架构内核进行展开,谢谢大家!

    展开全文
  • PostgreSQL系统概述_PG数据库内核分析学习笔记 PG简介和发展历程 PG以一种先进的对象-关系数据库管理系统(ORDBMS), 它不仅支持关系数据库的各种功能, 而且还具备类, 继承等对象数据库的特征. 它目前功能最强大, ...

    PostgreSQL系统概述_PG数据库内核分析学习笔记

    PG简介和发展历程

    PG以一种先进的对象-关系数据库管理系统(ORDBMS), 它不仅支持关系数据库的各种功能, 而且还具备类, 继承等对象数据库的特征.
    它是目前功能最强大, 特性最丰富的和结构最复杂的开源数据库管理系统, 其中有些特性甚至连商业数据库都不具备.

    什么是对象数据库?

    面向对象数据库系统(OODBS)支持定义和操作OODB,应满足两个标准:首先它是数据库系统,其次它也是面向对象系统。第一个标准即作为数据库系统应具备的能力(持久性、事务管理、并发控制、恢复、查询、版本管理、完整性、安全性)。第二个标准就是要求面向对象数据库充分支持完整的面向对象(OO)概念和控制机制。

    面向对象数据库和关系数据库的区别?

    以关系数据库为例,SQL语言是一种非过程化的面向集合的语言,它虽然用起来非常简单,但由于是解释实现,效率不如人意。因此许多应用仍然是由高级程序设计语言(如C)来实现的,但是高级程序设计语言是过程化的,而且是面向单个数据的,这使得SQL与它之间存在着不匹配,我们把它叫做“阻抗失配”。不论你是用嵌入式SQL,还是CLI,都需要化很多工夫去完成两种语言之间的相互转化。 但是关系数据库在数据库系统方面发展地非常完善,有强大的管理功能和可操纵性。另外,关系模型具有坚实的数学基础。
    面向对象数据库的产生主要是为了解决“阻抗失配”,它强调高级程序设计语言与数据库的无缝连接。什么叫无缝连接,假设你 不用数据库,用C语言编了一个程序,你可以不需要(或基本不需要)任何改动就将它作用于数据库,即你可以用C语言透明访问数据库,就好象数据库根本不存在一样,所以也有人把面向对象数据库理解为语言的持久化。怎么样,听起来挺玄的吧,可是我们自己开发的数据库系统——OSCAR早就做到了这一点。 由于实现了无缝连接,使得面向对象数据库能够支持非常复杂的数据模型,从而特别适用于工程设计领域。打个比方,想象CAD中的一个复杂部件,它可能由成千上万个不同的零件组成,要是用关系模型中的表来表达,得用多少张表啊?而描述这种复杂的部件,正好是高级程序设计语言的强项。
    此外,面向对象数据库还吸收了面向对象程序设计语言的思想,如支持类、方法、继承等概念。 面向对象数据库很好地解决了阻抗失配的问题,但它也有缺点。它的缺点正好是关系数据库的强项,由于模型较为复杂(而且缺乏数学基础),使得很多系统管理功能难以实现(如权限管理),也不具备SQL处理集合数据的强大能力。
    另一方面,随着多媒体数据的大量出现和应用的日益复杂,关系数据库也在不断吸收面向对象数据库的优点,出现了现在所谓的对象关系型数据库(象ORACLE8i、DB2-5以上都是这种系统)。其主要改进包括支持自定义类型(UDT),方法,继承(目前仅DB2-6支持),和引用(使得对象间可以直接引用,想想原来的关系数据库需要靠连接来实现引用,性能真是惨不忍睹)。 总之,数据库系统发展的趋势是,面向对象数据库和关系数据库将不断融合。而对象关系数据库由于继承了上述两者的优点,已经成为目前数据库发展的主流。

    Oracle和MySQL、PostgreSQL特性对比

    Oracle与PostGreSQL的差异对比

    PostgreSQL与MySQL比较

    PostgreSQL简介及发展历程

    PG的特性

    PG是一种几乎可以运行在各种平台上的免费的开放源码的对象关系数据库管理系统, 拥有与企业级数据库相媲美的特性, 如完善的SQL标准支持, 多版本并发控制(MVCC), 时间点恢复(PITR), 表空间机制, 异步复制, 嵌套事务, 在线(online)/热备份, 一个复杂的查询优化器, 预写式(WAL)日志容错技术.
    PG在管理大数据量方面有良好的可扩展性, 对并发用户管理具有自适应性.
    现在已经出现具有管理超过4万亿字节数据能力的使用版本产品.

    PG的应用

    1. Sony在线娱乐网站

    Sony在线娱乐网站(SOE)经营着很多知名游戏, 因此其数据库需要快速处理大量的用户数据, 其原有数据库系统为几十个Oracle 9i RAC集群. SOE分析业务后发现, Oracle数据库过于昂贵, 且许可证限制过多, 缺乏灵活性, 最终SOE将其数据库系统转换为稳定, 可扩展, 高性能的使用PG技术的EnterpriseDB数据库.

    Enterprise DB是提供基于PostgreSQL的企业级产品与服务厂商之一。

    1. Hi5社交网站

    Hi5从2003发展开始已经成为世界上最大的社交网站之一, 有超过200个国家的八千万注册用户, 每个月用户访问量超过5600万, 是在Alexa全球排名前20的网站; Hi5拥有一个运行在几百万台服务器节点上的商业PG OLTP集群, 这也是世界上最大的PG集群, 运行着Hi5社交网络的所有服务, 包括用户数据, 配置信息, 图片, 评论等数据, 并提供高效的检索. 另外, FortiusOne, Florists’Transworld Delivery, NNT等公司都选择廉价而高效的PG作为其数据管理基础设施, 取得了良好的应用效果.

    1. PG的国内的应用情况(知乎回答)

    总的份额现在还不算非常大,不过上升挺快的。第一,因为授权友善,代码质量高,现在很多高校都用PostgreSQL为原型做数据库工程的研究项目。第二,现在的云主机厂商越来越多的提供PostgreSQL数据源。第三,PostgreSQL比MySQL易用性好多了。这个居然还有人觉得MySQL占优势……我就说一句,我从2000年入行到现在,西方哪个流行的数据库我没用过?第四,企业自己维护的PostgreSQL数据库越来越多,因为基础运维环境在不断发展,现在像我们这种创业企业用PostgreSQL比以前方便多了。PostgreSQL可以一站式实现几乎所有的数据库服务需求,这对于新创企业是非常有诱惑力的。第五,大企业的OLAP需求,越来越多的在用PG,前年鹅厂的同行介绍他们有一个70PB的PostgreSQL集群,现在的规模肯定更大多了。阿里、百度、华为这几年对这个领域投入都在上升。第六,Greenplum开源,在今年会对PostgreSQL的发展有很大推动,这是业界非常有名的分布式数据库集群技术,基于PostgreSQL。

    PG代码结构

    初始化部分(Initialization)

    bootstrap:用于支持Bootstrap运行模式, 该模式主要用来创建初始的模板数据库. 和系统表相关。
    main:主程序模块, 它负责将控制权转到Postmaster进程. 即传递参数到后台的pg进程。
    postmaster:监听用户请求的守护进程, 并控制Postgres进程的启动和终止. 即, 控制pg服务开关,创建共享内存,循环等待连接并分配服务。
    libpq:C/C++的库函数, 处理与客户端间的通信, 几乎所有的模块都依赖它.即, 与子进程通讯相关的库。

    查询部分(Main Query Flow)

    tcop:traffic cop(交警),Postgres服务进程的主要处理部分, 它调用Parser, Optimizer, Executor, 和 Commands中的函数来执行客户端提交的查询. 即分配请求到对应模块如解析器、优化器、执行器和命令行功能。
    parser:编译器, 将SQL查询转化为内部查询树. 即把sql语句转化成查询执行树。
    rewrite:查询重写, 根据规则系统对查询进行重写. 即规则和视图支持部分。
    optimizer:优化器, 根据查询树创建最优化的查询路径和查询计划. 即优化查询路径生成计划。
    optimizer_path:生成所有可能的路径,检查表连接顺序,where条件限制,优化表的统计信息,对于可执行的计划评估开销。
    optimizer_geqo:遗传(或者是祖传?)查询优化器评估的路径对于要连接的表,当表的数量很少时会给很优化的结果,表太大就直接给一个一般的,可以通过参数控制是否使用这个功能。
    optimizer_plan:拿着“optimizer_path”的结果输出最小开销的路径。
    optimizer_prep:处理特殊的查询计划。
    optimizer_util:优化支持部分。
    executor:执行复杂的计划包括,增删改查。算子举例:堆扫描、索引扫描、排序、连接、聚合、分组、去重等。

    指令支持(Command Support)

    commands:响应指令,以及简单的功能如vacuum、create table直接在元数据(catalog)上面做了。
    catalog:元数据,直接操作系统表和目录,如索引、表、存储过程、操作符等。
    access:大量的数据访问函数,如索引访问、堆访问、和事务日志。
    access_common:通用访问方法。
    access_gin:索引相关访问。
    access_gist:搜索树相关访问。
    access_hash:哈希相关访问方法。
    access_heap:存储数据行。
    access_index:被索引类型使用。
    access_nbtree:Lehman和Yao的B树管理算法。
    access_spgist:空间分片的全局特征信息相关访问。(GiST)
    access_transam:事务管理,如日志读写。(BEGIN/ABORT/COMMIT)
    nodes:关于SQL查询的创建或者操作叫做节点,节点一般都有类型和特殊的数据结构,节点间经常被放在链表里。
    storage:管理大量存储系统,提供统一后台访问。
    storage_buffer:共享缓存管理。
    storage_file:文件管理。
    storage_freespace:剩余空间映射表。
    storage_ipc:信号量和共享内存。
    storage_large_object:超大对象。
    storage_lmgr:锁管理。
    storage_page:页管理。
    storage_smgr:磁盘和存储设备管理。
    utils:工具类。
    utils_adt:包含所有PG中內建数据类型(如bool、char、date)。
    utils_cache:因为PG支持很多随意数据类型,后台系统需要查询一个系统表关于这些随意类型时会先走这个缓存。
    utils_error:错误反馈,从后端反馈到前端去。
    utils_fmgr:函数管理,处理动态绑定的函数调用,和系统表中定义的函数调用。
    utils_hash:哈希函数管理,被缓存和内存管理器使用以做快速查询动态数据存储结构。
    utils_init:大量初始化。
    utils_mb:单字节和多字节的编码。
    utils_misc:冗余的东西(官方叫miscellaneous stuff)
    utils_mmgr:内存管理(PG进程本地)。
    utils_resowner:资源拥有者追溯。
    utils_sort:排序工具,如对元祖排序。
    utils_time:事务时间,与mvcc有关,判断一个元祖是过期了还是未提交。

    基础支持(Support Facilities)

    include:引用的文件。
    lib:支持的库,有二插堆,布隆过滤器。
    port:兼容性部分。
    regex:正则表达式。
    snowball:语法库,雪球是一个语法库,各国语言的规范都有。
    replication:通过日志迁移支持流式备份。
    tsearch:文本查询库用于支持全文查询。

    参考

    <PG数据库内核分析> 1 PostSQL系统概述
    小麦带你看postgres(代码模块结构)

    展开全文
  • 是这样的:我对每个新建的文件,调用sys_open时,在内核中同时给该文件在数据库中建立一个对应的表。...接口是什么?比如mysal数据库,或者还是内核不能直接访问数据库,那需要做什么才可以让内核访问数据库呢?
  • 在操作数据库系统的时候,有个常识就是在建表的时候一定要建索引。为什么要建索引呢? 这里以MySQL的InnoDB存储引擎为例,因为InnoDB会以索引的排序为基准建立B+树,这样在检索数据的时候就可以通过B+树来查找,查找...

    前言

    在操作数据库系统的时候,有个常识就是在建表的时候一定要建索引。为什么要建索引呢?

    这里以MySQL的InnoDB存储引擎为例,因为InnoDB会以索引的排序为基准建立B+树,这样在检索数据的时候就可以通过B+树来查找,查找算法的时间复杂度是O(logn)级别的,避免全表扫描带来的性能下降和额外资源损耗。

    理论上一个表所有的字段都可以建索引,那么给哪些字段建索引效果好呢?

    一个想法是给频繁在SQL的where条件中出现的字段建立索引,这样可以保证通过索引来查找数据。

    有一点是经常被忽略的,那就是索引的过滤性。比如我们给一个整型字段加索引,而这个字段在几乎所有的记录上的值都是1(过滤性很差),那么我们通过这个索引来查找数据就会遍历大部分无关记录,造成浪费。

    我们知道update语句也是通过索引来查找待更新的数据的,而且update会给索引查找的记录加上X锁,因此索引过滤性不好不但造成性能下降,还有可能造成锁争夺和锁等待的损耗。

    下面给出一个具体的因为索引过滤性太差引起CPU飙高的case,在RDS的线上实例曾出现过类似的case。

    场景构造

    在MySQL里我们建立这样一个表:

    CREATE TABLE `sbtest1` (
      `id` int(10) unsigned NOT NULL,
      `k` int(10) unsigned NOT NULL DEFAULT '0',
      `n` int(10) unsigned NOT NULL DEFAULT '0',
      `c` char(120) NOT NULL DEFAULT '',
      `pad` char(60) NOT NULL DEFAULT '',
      PRIMARY KEY (`id`),
      KEY `k_1` (`k`)
    ) ENGINE=InnoDB;
    

    然后我们给sbtest1加点数据,并且让索引k_1(k)的过滤性不好,表内一共10000000条数据,索引k只有2个值50,51,如下所示:

    mysql> select count(*) from sbtest1;
    +----------+
    | count(*) |
    +----------+
    | 10000000 |
    +----------+
    1 row in set (1.80 sec)
    
    mysql> select distinct k from sbtest1;
    +----+
    | k  |
    +----+
    | 50 |
    | 51 |
    +----+
    2 rows in set (2.22 sec)
    

    然后我们用sysbench开32个并发的update,update语句如下:

    UPDATE sbtest1 SET c='随机字符串' WHERE k=5051 and n=随机值
    

    执行show full processlist\G,可以看到这些update的状态大多处于”Searching rows for update”的状态。

    mysql> show full processlist\G
    *************************** 1. row ***************************
                      Id: 2
                    User: root
                    Host:
                      db: test
                 Command: Sleep
                    Time: 6
                   State:
                    Info: NULL
             Memory_used: 1146520
    Memory_used_by_query: 8208
            Logical_read: 53
      Physical_sync_read: 2
     Physical_async_read: 0
    Temp_user_table_size: 0
    Temp_sort_table_size: 0
     Temp_sort_file_size: 0
    *************************** 2. row ***************************
                      Id: 6
                    User: root
                    Host:
                      db: sbtest
                 Command: Query
                    Time: 21
                   State: Searching rows for update
                    Info: UPDATE sbtest1 SET c='96372750646-31206582030-89561475094-70112992370-09982266420-13264143120-70453817624-14068123856-50060327807-36562985632' WHERE k=50 and n=4951641
             Memory_used: 119840
    Memory_used_by_query: 232
            Logical_read: 4935
      Physical_sync_read: 0
     Physical_async_read: 0
    Temp_user_table_size: 0
    Temp_sort_table_size: 0
     Temp_sort_file_size: 0
    *************************** 3. row ***************************
                      Id: 7
                    User: root
                    Host:
                      db: sbtest
                 Command: Query
                    Time: 21
                   State: Searching rows for update
                    Info: UPDATE sbtest1 SET c='28921237680-50951214786-47793625883-44090170070-31354117142-11520543175-97262835853-83486109785-32721666363-10671483869' WHERE k=51 and n=5033717
             Memory_used: 119840
    Memory_used_by_query: 232
            Logical_read: 4949
      Physical_sync_read: 5
     Physical_async_read: 0
    Temp_user_table_size: 0
    Temp_sort_table_size: 0
     Temp_sort_file_size: 0
    
    ...
    

    “Searching rows for update”即MySQL正在寻找待更新的记录的状态,正常情况这个状态是非常快就结束的,但是这里却长时间处于这个状态,为什么呢?

    由于表的索引过滤性太差,每个线程在查找的时候会遇到很多冲突的记录。

    InnoDB在通过索引拿到记录后,会给这些记录上X锁,同时也会请求全局的lock_sys->mutextrx_sys->mutex,所以这里我们判断每个线程都堵在锁等待这里。(ps: 关于InnoDB加锁的逻辑,可以查看这篇博文

    这时候对系统用一下top命令,可以发现这个MySQL实例CPU飚的很高,我们再用perf工具看一下CPU飙高的MySQL调用堆栈是怎么样的,如下所示:

        83.77%   mysqld  mysqld              [.] _Z8ut_delaym
                 |
                 --- _Z8ut_delaym
                    |
                    |--99.99%-- _Z15mutex_spin_waitP10ib_mutex_tPKcm
                    |          |
                    |          |--88.88%-- _ZL20pfs_mutex_enter_funcP10ib_mutex_tPKcm.constprop.68
                    |          |          |
                    |          |          |--54.05%-- _ZL29lock_rec_convert_impl_to_explPK11buf_block_tPKhP12dict_index_tPKm
                    |          |          |          _Z34lock_clust_rec_read_check_and_lockmPK11buf_block_tPKhP12dict_index_tPKm9lock_modemP9que_thr_t
                    |          |          |          _ZL16sel_set_rec_lockPK11buf_block_tPKhP12dict_index_tPKmmmP9que_thr_t
                    |          |          |          _Z20row_search_for_mysqlPhmP14row_prebuilt_tmm
                    |          |          |          _ZN11ha_innobase10index_nextEPh
                    |          |          |          _ZN7handler13ha_index_nextEPh
                    |          |          |          _ZL8rr_indexP11READ_RECORD
                    |          |          |          _Z12mysql_updateP3THDP10TABLE_LISTR4ListI4ItemES6_PS4_jP8st_ordery15enum_duplicatesbPySB_
                    |          |          |          _Z21mysql_execute_commandP3THD
                    |          |          |          _Z11mysql_parseP3THDPcjP12Parser_state
                    |          |          |          _Z16dispatch_command19enum_server_commandP3THDPcj
                    |          |          |          _Z26threadpool_process_requestP3THD
                    |          |          |          _ZL11worker_mainPv
                    |          |          |          start_thread
                    |          |          |
                    |          |           --45.95%-- _Z15lock_rec_unlockP5trx_tPK11buf_block_tPKh9lock_mode
                    |          |                     _Z20row_unlock_for_mysqlP14row_prebuilt_tm
                    |          |                     _Z12mysql_updateP3THDP10TABLE_LISTR4ListI4ItemES6_PS4_jP8st_ordery15enum_duplicatesbPySB_
                    |          |                     _Z21mysql_execute_commandP3THD
                    |          |                     _Z11mysql_parseP3THDPcjP12Parser_state
                    |          |                     _Z16dispatch_command19enum_server_commandP3THDPcj
                    |          |                     _Z26threadpool_process_requestP3THD
                    |          |                     _ZL11worker_mainPv
                    |          |                     start_thread
    
    

    我们看到耗CPU最高的调用函数栈是…mutex_spin_wait->ut_delay,属于锁等待的逻辑。InnoDB在这里用的是自旋锁,锁等待是通过调用ut_delay做空循环实现的,会消耗CPU。这里证明了上面的判断是对的。

    在这个case里涉及到的锁有记录锁、lock_sys->mutextrx_sys->mutex,究竟是哪个锁等待时间最长呢?我们可以用下面的方法确认一下:

    mysql> SELECT COUNT_STAR, SUM_TIMER_WAIT, AVG_TIMER_WAIT, EVENT_NAME FROM performance_schema.events_waits_summary_global_by_event_name where COUNT_STAR > 0 and EVENT_NAME like 'wait/synch/%' order by SUM_TIMER_WAIT desc limit 10;
    +------------+------------------+----------------+--------------------------------------------+
    | COUNT_STAR | SUM_TIMER_WAIT   | AVG_TIMER_WAIT | EVENT_NAME                                 |
    +------------+------------------+----------------+--------------------------------------------+
    |   36847781 | 1052968694795446 |       28575867 | wait/synch/mutex/innodb/lock_mutex         |
    |       8096 |   81663413514785 |    10086883818 | wait/synch/cond/threadpool/timer_cond      |
    |         19 |    3219754571347 |   169460766775 | wait/synch/cond/threadpool/worker_cond     |
    |   12318491 |    1928008466219 |         156446 | wait/synch/mutex/innodb/trx_sys_mutex      |
    |   36481800 |    1294486175099 |          35397 | wait/synch/mutex/innodb/trx_mutex          |
    |   14792965 |     459532479943 |          31027 | wait/synch/mutex/innodb/os_mutex           |
    |    2457971 |      62564589052 |          25346 | wait/synch/mutex/innodb/mutex_list_mutex   |
    |    2457939 |      62188866940 |          24909 | wait/synch/mutex/innodb/rw_lock_list_mutex |
    |     201370 |      32882813144 |         163001 | wait/synch/rwlock/innodb/hash_table_locks  |
    |       1555 |      15321632528 |        9853039 | wait/synch/mutex/innodb/dict_sys_mutex     |
    +------------+------------------+----------------+--------------------------------------------+
    10 rows in set (0.01 sec)
    

    从上面的表可以确认,lock_mutex(在MySQL源码里对应的是lock_sys->mutex)的锁等待累积时间最长(SUM_TIMER_WAIT)。lock_sys表示全局的InnoDB锁系统,在源码里看到InnoDB加/解某个记录锁的时候(这个case里是X锁),同时需要维护lock_sys,这时会请求lock_sys->mutex。

    在这个case里,因为在Searching rows for update的阶段频繁地加/解X锁,就会频繁请求lock_sys->mutex,导致lock_sys->mutex锁总等待时间过长,同时在等待的时候消耗了大量CPU。

    当我们将索引改成过滤性好的(比如字段n),再做上述实验,就看不到那么多线程堵在”Searching rows for update”的阶段,而且实例的CPU消耗也降了很多。

    结语

    通过以上实验,我们看到索引过滤性不好可能带来灾难性的结果:语句hang住以及主机CPU耗尽。因此我们在设计表的时候,应该对业务上的数据有充分的估计,选择过滤性好的字段作为索引。

    展开全文
  • 本文分为三节,分别介绍clog的fsync频率,原子操作,与异步提交...分析一下pg_clog什么时候需要调用fsync的? 首先引用wiki里的一段pg_clog的介绍 Some details here are in src/backend/access/transam/READ...

    本文分为三节,分别介绍clog的fsync频率,原子操作,与异步提交一致性。

    PostgreSQL pg_clog fsync 频率分析

    分析一下pg_clog是在什么时候需要调用fsync的?

    首先引用wiki里的一段pg_clog的介绍

    Some details here are in src/backend/access/transam/README:
    1. “pg_clog records the commit status for each transaction that has been assigned an XID.”
    2. “Transactions and subtransactions are assigned permanent XIDs only when/if they first do something that requires one — typically, insert/update/delete a tuple, though there are a few other places that need an XID assigned.”

    pg_clog is updated only at sub or main transaction end. When the transactionid is assigned the page of the clog that contains that transactionid is checked to see if it already exists and if not, it is initialised.
    pg_clog is allocated in pages of 8kB apiece(和BLOCKSZ一致,所以不一定是8K,见后面的分析). 
    Each transaction needs 2 bits, so on an 8 kB page there is space for 4 transactions/byte * 8k bytes = 32k transactions.
    On allocation, pages are zeroed, which is the bit pattern for “transaction in progress”. 
    So when a transaction starts, it only needs to ensure that the pg_clog page that contains its status is allocated, but it need not write anything to it. 
    In 8.3 and later, this happens not when the transaction starts, but when the Xid is assigned (i.e. when the transaction first calls a read-write command). 
    In previous versions it happens when the first snapshot is taken, normally on the first command of any type with very few exceptions.

    This means that one transaction in every 32K writing transactions does have to do extra work when it assigns itself an XID, namely create and zero out the next page of pg_clog. 
    And that doesn’t just slow down the transaction in question, but the next few guys that would like an XID but arrive on the scene while the zeroing-out is still in progress. 
    This probably contributes to reported behavior that the transaction execution time is subject to unpredictable spikes.

    每隔32K个事务,要扩展一个CLOG PAGE,每次扩展需要填充0,同时需要调用PG_FSYNC,这个相比FSYNC XLOG应该是比较轻量级的。但是也可能出现不可预知的响应延迟,因为如果堵塞在扩展CLOG PAGE,所有等待clog PAGE的会话都会受到影响。

    这里指当CLOG buffer没有空的SLOT时,会从所有的CLOG buffer SLOT选择一个脏页,将其刷出,这个时候才会产生pg_fsync。
    CLOG pages don’t make their way out to disk until the internal CLOG buffers are filled, at which point the least recently used buffer there is evicted to permanent storage.

    下面从代码中分析一下pg_clog是如何调用pg_fsync刷脏页的。

    每次申请新的事务ID时,都需要调用ExtendCLOG,如果通过事务ID计算得到的CLOG PAGE页不存在,则需要扩展;但是并不是每次扩展都需要调用pg_fsync,因为checkpoint会将clog buffer刷到磁盘,除非在申请新的CLOG PAGE时所有的clog buffer都没有刷出脏页,才需要主动选择一个page并调用pg_fsync刷出对应的pg_clog/file。
    src/backend/access/transam/varsup.c

    /*
     * Allocate the next XID for a new transaction or subtransaction.
     *
     * The new XID is also stored into MyPgXact before returning.
     *
     * Note: when this is called, we are actually already inside a valid
     * transaction, since XIDs are now not allocated until the transaction
     * does something.  So it is safe to do a database lookup if we want to
     * issue a warning about XID wrap.
     */
    TransactionId
    GetNewTransactionId(bool isSubXact)
    {
    ......
            /*
             * If we are allocating the first XID of a new page of the commit log,
             * zero out that commit-log page before returning. We must do this while
             * holding XidGenLock, else another xact could acquire and commit a later
             * XID before we zero the page.  Fortunately, a page of the commit log
             * holds 32K or more transactions, so we don't have to do this very often.
             *
             * Extend pg_subtrans too.
             */
            ExtendCLOG(xid);
            ExtendSUBTRANS(xid);
    ......
    

    ExtendCLOG(xid)扩展clog page,调用TransactionIdToPgIndex计算XID和CLOG_XACTS_PER_PAGE的余数,如果不为0,则不需要扩展。
    src/backend/access/transam/clog.c

    #define TransactionIdToPgIndex(xid) ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
    
    /*
     * Make sure that CLOG has room for a newly-allocated XID.
     *
     * NB: this is called while holding XidGenLock.  We want it to be very fast
     * most of the time; even when it's not so fast, no actual I/O need happen
     * unless we're forced to write out a dirty clog or xlog page to make room
     * in shared memory.
     */
    void
    ExtendCLOG(TransactionId newestXact)
    {
            int                     pageno;
    
            /*
             * No work except at first XID of a page.  But beware: just after
             * wraparound, the first XID of page zero is FirstNormalTransactionId.
             */
            if (TransactionIdToPgIndex(newestXact) != 0 &&    // 余数不为0,说明不需要扩展。
                    !TransactionIdEquals(newestXact, FirstNormalTransactionId))
                    return;
    
            pageno = TransactionIdToPage(newestXact);
    
            LWLockAcquire(CLogControlLock, LW_EXCLUSIVE);
    
            /* Zero the page and make an XLOG entry about it */
            ZeroCLOGPage(pageno, true);
    
            LWLockRelease(CLogControlLock);
    }
    

    ZeroCLOGPage(pageno, true),调用SimpleLruZeroPage,扩展并初始化CLOG PAGE,写XLOG日志。

    /*
     * Initialize (or reinitialize) a page of CLOG to zeroes.
     * If writeXlog is TRUE, also emit an XLOG record saying we did this.
     *
     * The page is not actually written, just set up in shared memory.
     * The slot number of the new page is returned.
     *
     * Control lock must be held at entry, and will be held at exit.
     */
    static int
    ZeroCLOGPage(int pageno, bool writeXlog)
    {
            int                     slotno;
    
            slotno = SimpleLruZeroPage(ClogCtl, pageno);
    
            if (writeXlog)
                    WriteZeroPageXlogRec(pageno);
    
            return slotno;
    }
    

    SimpleLruZeroPage(ClogCtl, pageno),调用SlruSelectLRUPage(ctl, pageno),从clog shared buffer中选择SLOT。
    src/backend/access/transam/slru.c

    /*
     * Initialize (or reinitialize) a page to zeroes.
     *
     * The page is not actually written, just set up in shared memory.
     * The slot number of the new page is returned.
     *
     * Control lock must be held at entry, and will be held at exit.
     */
    int
    SimpleLruZeroPage(SlruCtl ctl, int pageno)
    {
            SlruShared      shared = ctl->shared;
            int                     slotno;
    
            /* Find a suitable buffer slot for the page */
            slotno = SlruSelectLRUPage(ctl, pageno);
            Assert(shared->page_status[slotno] == SLRU_PAGE_EMPTY ||
                       (shared->page_status[slotno] == SLRU_PAGE_VALID &&
                            !shared->page_dirty[slotno]) ||
                       shared->page_number[slotno] == pageno);
    
            /* Mark the slot as containing this page */
            shared->page_number[slotno] = pageno;
            shared->page_status[slotno] = SLRU_PAGE_VALID;
            shared->page_dirty[slotno] = true;
            SlruRecentlyUsed(shared, slotno);
    
            /* Set the buffer to zeroes */
            MemSet(shared->page_buffer[slotno], 0, BLCKSZ);
    
            /* Set the LSNs for this new page to zero */
            SimpleLruZeroLSNs(ctl, slotno);
    
            /* Assume this page is now the latest active page */
            shared->latest_page_number = pageno;
    
            return slotno;
    }
    

    SlruSelectLRUPage(SlruCtl ctl, int pageno),从clog buffer选择一个空的SLOT,如果没有空的SLOT,则需要调用SlruInternalWritePage(ctl, bestvalidslot, NULL),写shared buffer page。

    /*
     * Select the slot to re-use when we need a free slot.
     *
     * The target page number is passed because we need to consider the
     * possibility that some other process reads in the target page while
     * we are doing I/O to free a slot.  Hence, check or recheck to see if
     * any slot already holds the target page, and return that slot if so.
     * Thus, the returned slot is *either* a slot already holding the pageno
     * (could be any state except EMPTY), *or* a freeable slot (state EMPTY
     * or CLEAN).
     *
     * Control lock must be held at entry, and will be held at exit.
     */
    static int
    SlruSelectLRUPage(SlruCtl ctl, int pageno)
    {
    ......
    		/* See if page already has a buffer assigned */  先查看clog buffer中是否有空SLOT,有则返回,不需要调pg_fsync
    		for (slotno = 0; slotno < shared->num_slots; slotno++)
    		{
    			if (shared->page_number[slotno] == pageno &&
    				shared->page_status[slotno] != SLRU_PAGE_EMPTY)
    				return slotno;
    		}
    ...... 
    		/*  如果没有找到空SLOT,则需要从clog buffer中选择一个使用最少的PAGE,注意他不会选择最近临近的PAGE,优先选择IO不繁忙的PAGE
    		 * If we find any EMPTY slot, just select that one. Else choose a
    		 * victim page to replace.  We normally take the least recently used
    		 * valid page, but we will never take the slot containing
    		 * latest_page_number, even if it appears least recently used.  We
    		 * will select a slot that is already I/O busy only if there is no
    		 * other choice: a read-busy slot will not be least recently used once
    		 * the read finishes, and waiting for an I/O on a write-busy slot is
    		 * inferior to just picking some other slot.  Testing shows the slot
    		 * we pick instead will often be clean, allowing us to begin a read at
    		 * once.
    		 *  
    		 * Normally the page_lru_count values will all be different and so
    		 * there will be a well-defined LRU page.  But since we allow
    		 * concurrent execution of SlruRecentlyUsed() within
    		 * SimpleLruReadPage_ReadOnly(), it is possible that multiple pages
    		 * acquire the same lru_count values.  In that case we break ties by
    		 * choosing the furthest-back page.
    		 *
    		 * Notice that this next line forcibly advances cur_lru_count to a
    		 * value that is certainly beyond any value that will be in the
    		 * page_lru_count array after the loop finishes.  This ensures that
    		 * the next execution of SlruRecentlyUsed will mark the page newly
    		 * used, even if it's for a page that has the current counter value.
    		 * That gets us back on the path to having good data when there are
    		 * multiple pages with the same lru_count.
    		 */
    		cur_count = (shared->cur_lru_count)++;
    		for (slotno = 0; slotno < shared->num_slots; slotno++)
    		{
    			int			this_delta;
    			int			this_page_number;
    
    			if (shared->page_status[slotno] == SLRU_PAGE_EMPTY)  // 如果在此期间出现了空SLOT,返回这个slotno
    				return slotno;
    			this_delta = cur_count - shared->page_lru_count[slotno];
    			if (this_delta < 0)
    			{
    				/*
    				 * Clean up in case shared updates have caused cur_count
    				 * increments to get "lost".  We back off the page counts,
    				 * rather than trying to increase cur_count, to avoid any
    				 * question of infinite loops or failure in the presence of
    				 * wrapped-around counts.
    				 */
    				shared->page_lru_count[slotno] = cur_count;
    				this_delta = 0;
    			}
    			this_page_number = shared->page_number[slotno];
    			if (this_page_number == shared->latest_page_number)
    				continue;
    			if (shared->page_status[slotno] == SLRU_PAGE_VALID)  // IO不繁忙的脏页
    			{
    				if (this_delta > best_valid_delta ||
    					(this_delta == best_valid_delta &&
    					 ctl->PagePrecedes(this_page_number,
    									   best_valid_page_number)))
    				{
    					bestvalidslot = slotno;
    					best_valid_delta = this_delta;
    					best_valid_page_number = this_page_number;
    				}
    			}
    			else
    			{
    				if (this_delta > best_invalid_delta ||
    					(this_delta == best_invalid_delta &&
    					 ctl->PagePrecedes(this_page_number,
    									   best_invalid_page_number)))
    				{
    					bestinvalidslot = slotno;  // 当所有页面IO都繁忙时,无奈只能从IO繁忙中选择一个.
    					best_invalid_delta = this_delta;
    					best_invalid_page_number = this_page_number;
    				}
    			}
    		}
    
    		/*  如果选择到的PAGE
    		 * If all pages (except possibly the latest one) are I/O busy, we'll
    		 * have to wait for an I/O to complete and then retry.  In that
    		 * unhappy case, we choose to wait for the I/O on the least recently
    		 * used slot, on the assumption that it was likely initiated first of
    		 * all the I/Os in progress and may therefore finish first.
    		 */
    		if (best_valid_delta < 0)  // 说明没有找到SLRU_PAGE_VALID的PAGE,所有PAGE都处于IO繁忙的状态。
    		{
    			SimpleLruWaitIO(ctl, bestinvalidslot);
    			continue;
    		}
    
    		/*
    		 * If the selected page is clean, we're set.
    		 */
    		if (!shared->page_dirty[bestvalidslot])  // 如果这个页面已经不是脏页(例如被CHECKPOINT刷出了),那么直接返回
    			return bestvalidslot;
    
    ......
    仅仅当以上所有的步骤,都没有找到一个EMPTY SLOT时,才需要主动刷脏页(在SlruInternalWritePage调用pg_fsync)。
                    /*
                     * Write the page.  注意第三个参数为NULL,即fdata
                     */
                    SlruInternalWritePage(ctl, bestvalidslot, NULL);
    ......
    

    SlruInternalWritePage(SlruCtl ctl, int slotno, SlruFlush fdata),调用SlruPhysicalWritePage,执行write。

    /*
     * Write a page from a shared buffer, if necessary.
     * Does nothing if the specified slot is not dirty.
     *
     * NOTE: only one write attempt is made here.  Hence, it is possible that
     * the page is still dirty at exit (if someone else re-dirtied it during
     * the write).  However, we *do* attempt a fresh write even if the page
     * is already being written; this is for checkpoints.
     *
     * Control lock must be held at entry, and will be held at exit.
     */
    static void
    SlruInternalWritePage(SlruCtl ctl, int slotno, SlruFlush fdata)
    {
    ......
            /* Do the write */
            ok = SlruPhysicalWritePage(ctl, pageno, slotno, fdata);
    ......
    

    SLRU PAGE状态

    /*
     * Page status codes.  Note that these do not include the "dirty" bit.
     * page_dirty can be TRUE only in the VALID or WRITE_IN_PROGRESS states;
     * in the latter case it implies that the page has been re-dirtied since
     * the write started.
     */
    typedef enum
    {
    	SLRU_PAGE_EMPTY,			/* buffer is not in use */
    	SLRU_PAGE_READ_IN_PROGRESS, /* page is being read in */
    	SLRU_PAGE_VALID,			/* page is valid and not being written */
    	SLRU_PAGE_WRITE_IN_PROGRESS /* page is being written out */
    } SlruPageStatus;
    

    SlruPhysicalWritePage(ctl, pageno, slotno, fdata),这里涉及pg_clog相关的SlruCtlData结构,do_fsync=true。

    /*
     * Physical write of a page from a buffer slot
     *
     * On failure, we cannot just ereport(ERROR) since caller has put state in
     * shared memory that must be undone.  So, we return FALSE and save enough
     * info in static variables to let SlruReportIOError make the report.
     *
     * For now, assume it's not worth keeping a file pointer open across
     * independent read/write operations.  We do batch operations during
     * SimpleLruFlush, though.
     *
     * fdata is NULL for a standalone write, pointer to open-file info during
     * SimpleLruFlush.
     */
    static bool
    SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno,
                                              SlruFlush fdata);
    ......
            int                     fd = -1;
    ......
    //   如果文件不存在,自动创建  
            if (fd < 0)
            {
                    /*
                     * If the file doesn't already exist, we should create it.  It is
                     * possible for this to need to happen when writing a page that's not
                     * first in its segment; we assume the OS can cope with that. (Note:
                     * it might seem that it'd be okay to create files only when
                     * SimpleLruZeroPage is called for the first page of a segment.
                     * However, if after a crash and restart the REDO logic elects to
                     * replay the log from a checkpoint before the latest one, then it's
                     * possible that we will get commands to set transaction status of
                     * transactions that have already been truncated from the commit log.
                     * Easiest way to deal with that is to accept references to
                     * nonexistent files here and in SlruPhysicalReadPage.)
                     *
                     * Note: it is possible for more than one backend to be executing this
                     * code simultaneously for different pages of the same file. Hence,
                     * don't use O_EXCL or O_TRUNC or anything like that.
                     */
                    SlruFileName(ctl, path, segno);
                    fd = OpenTransientFile(path, O_RDWR | O_CREAT | PG_BINARY,
                                                               S_IRUSR | S_IWUSR);
    ......
            /*
             * If not part of Flush, need to fsync now.  We assume this happens
             * infrequently enough that it's not a performance issue.
             */
            if (!fdata)  // 因为传入的fdata=NULL,并且ctl->do_fsync=true,所以以下pg_fsync被调用。
            {
                    if (ctl->do_fsync && pg_fsync(fd))  // 对于pg_clog和multixact,do_fsync=true。
                    {
                            slru_errcause = SLRU_FSYNC_FAILED;
                            slru_errno = errno;
                            CloseTransientFile(fd);
                            return false;
                    }
    
                    if (CloseTransientFile(fd))
                    {
                            slru_errcause = SLRU_CLOSE_FAILED;
                            slru_errno = errno;
                            return false;
                    }
            }
    

    ctl->do_fsync && pg_fsync(fd)涉及的代码:
    src/include/access/slru.h

    /*
     * SlruCtlData is an unshared structure that points to the active information
     * in shared memory.
     */
    typedef struct SlruCtlData
    {
            SlruShared      shared;
    
            /*
             * This flag tells whether to fsync writes (true for pg_clog and multixact
             * stuff, false for pg_subtrans and pg_notify).
             */
            bool            do_fsync;
    
            /*
             * Decide which of two page numbers is "older" for truncation purposes. We
             * need to use comparison of TransactionIds here in order to do the right
             * thing with wraparound XID arithmetic.
             */
            bool            (*PagePrecedes) (int, int);
    
            /*
             * Dir is set during SimpleLruInit and does not change thereafter. Since
             * it's always the same, it doesn't need to be in shared memory.
             */
            char            Dir[64];
    } SlruCtlData;
    typedef SlruCtlData *SlruCtl;
    

    src/backend/access/transam/slru.c

    ......
    void
    SimpleLruInit(SlruCtl ctl, const char *name, int nslots, int nlsns,
                              LWLock *ctllock, const char *subdir)
    ......
            ctl->do_fsync = true;           /* default behavior */  // 初始化LRU时,do_fsync默认是true的。
    ......
    

    以下是clog初始化LRU的调用,可以看到它没有修改do_fsync,所以是TURE。
    src/backend/access/transam/clog.c

    /*
     * Number of shared CLOG buffers.
     *
     * Testing during the PostgreSQL 9.2 development cycle revealed that on a
     * large multi-processor system, it was possible to have more CLOG page
     * requests in flight at one time than the number of CLOG buffers which existed
     * at that time, which was hardcoded to 8.  Further testing revealed that
     * performance dropped off with more than 32 CLOG buffers, possibly because
     * the linear buffer search algorithm doesn't scale well.
     *
     * Unconditionally increasing the number of CLOG buffers to 32 did not seem
     * like a good idea, because it would increase the minimum amount of shared
     * memory required to start, which could be a problem for people running very
     * small configurations.  The following formula seems to represent a reasonable
     * compromise: people with very low values for shared_buffers will get fewer
     * CLOG buffers as well, and everyone else will get 32.
     *
     * It is likely that some further work will be needed here in future releases;
     * for example, on a 64-core server, the maximum number of CLOG requests that
     * can be simultaneously in flight will be even larger.  But that will
     * apparently require more than just changing the formula, so for now we take
     * the easy way out.
     */
    Size
    CLOGShmemBuffers(void)
    {
            return Min(32, Max(4, NBuffers / 512));
    }
    
    void
    CLOGShmemInit(void)
    {
            ClogCtl->PagePrecedes = CLOGPagePrecedes;
            SimpleLruInit(ClogCtl, "CLOG Ctl", CLOGShmemBuffers(), CLOG_LSNS_PER_PAGE,
                                      CLogControlLock, "pg_clog");
    }
    

    以下是subtrans初始化LRU的调用,看到它修改了do_fsync=false。所以subtrans扩展PAGE时不需要调用pg_fsync。
    src/backend/access/transam/subtrans.c

    void
    SUBTRANSShmemInit(void)
    {
            SubTransCtl->PagePrecedes = SubTransPagePrecedes;
            SimpleLruInit(SubTransCtl, "SUBTRANS Ctl", NUM_SUBTRANS_BUFFERS, 0,
                                      SubtransControlLock, "pg_subtrans");
            /* Override default assumption that writes should be fsync'd */
            SubTransCtl->do_fsync = false;
    }
    

    multixact.c也没有修改do_fsync,所以也是需要fsync的。
    MultiXactShmemInit(void)@src/backend/access/transam/multixact.c

    pg_fsync代码:
    src/backend/storage/file/fd.c

    /*
     * pg_fsync --- do fsync with or without writethrough
     */
    int
    pg_fsync(int fd)
    {
            /* #if is to skip the sync_method test if there's no need for it */
    #if defined(HAVE_FSYNC_WRITETHROUGH) && !defined(FSYNC_WRITETHROUGH_IS_FSYNC)
            if (sync_method == SYNC_METHOD_FSYNC_WRITETHROUGH)
                    return pg_fsync_writethrough(fd);
            else
    #endif
                    return pg_fsync_no_writethrough(fd);
    }
    
    /*
     * pg_fsync_no_writethrough --- same as fsync except does nothing if
     *      enableFsync is off
     */
    int
    pg_fsync_no_writethrough(int fd)
    {
            if (enableFsync)
                    return fsync(fd);
            else
                    return 0;
    }
    
    /*
     * pg_fsync_writethrough
     */
    int
    pg_fsync_writethrough(int fd)
    {
            if (enableFsync)
            {
    #ifdef WIN32
                    return _commit(fd);
    #elif defined(F_FULLFSYNC)
                    return (fcntl(fd, F_FULLFSYNC, 0) == -1) ? -1 : 0;
    #else
                    errno = ENOSYS;
                    return -1;
    #endif
            }
            else
                    return 0;
    }
    

    从上面的代码分析,扩展clog page时,如果在CLOG BUFFER中没有EMPTY SLOT,则需要backend process主动刷CLOG PAGE,所以会有调用pg_fsync的动作。

    clog page和数据库BLOCKSZ (database block size)一样大,默认是8K(如果编译数据库软件时没有修改的话,默认是8KB),最大可以设置为32KB。每个事务在pg_clog中需要2个比特位来存储事务信息(xmin commit/abort,xmax commit/abort)。所以8K的clog page可以存储32K个事务信息,换句话说,每32K个事务,需要扩展一次clog page。

    下面的代码是clog的一些常用宏。
    src/backend/access/transam/clog.c

    /*
     * Defines for CLOG page sizes.  A page is the same BLCKSZ as is used
     * everywhere else in Postgres.
     *
     * Note: because TransactionIds are 32 bits and wrap around at 0xFFFFFFFF,
     * CLOG page numbering also wraps around at 0xFFFFFFFF/CLOG_XACTS_PER_PAGE,
     * and CLOG segment numbering at
     * 0xFFFFFFFF/CLOG_XACTS_PER_PAGE/SLRU_PAGES_PER_SEGMENT.  We need take no
     * explicit notice of that fact in this module, except when comparing segment
     * and page numbers in TruncateCLOG (see CLOGPagePrecedes).
     */
    
    /* We need two bits per xact, so four xacts fit in a byte */
    #define CLOG_BITS_PER_XACT      2
    #define CLOG_XACTS_PER_BYTE 4
    #define CLOG_XACTS_PER_PAGE (BLCKSZ * CLOG_XACTS_PER_BYTE)
    #define CLOG_XACT_BITMASK       ((1 << CLOG_BITS_PER_XACT) - 1)
    
    #define TransactionIdToPage(xid)         ((xid) / (TransactionId) CLOG_XACTS_PER_PAGE)
    #define TransactionIdToPgIndex(xid)     ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE)
    #define TransactionIdToByte(xid)          (TransactionIdToPgIndex(xid) / CLOG_XACTS_PER_BYTE)
    #define TransactionIdToBIndex(xid)       ((xid) % (TransactionId) CLOG_XACTS_PER_BYTE)
    

    查看数据库的block size:

    postgres@digoal-> pg_controldata |grep block  
    Database block size:                  8192  
    WAL block size:                       8192  
    

    我们可以使用stap来跟踪是否调用pg_fsync,如果你要观察backend process主动刷clog 脏页,可以把checkpoint间隔开大,同时把clog shared buffer pages。
    你就会观察到backend process主动刷clog 脏页。

    Size
    CLOGShmemBuffers(void)
    {
    	return Min(32, Max(4, NBuffers / 512));
    }
    

    跟踪

    src/backend/access/transam/slru.c
    SlruPhysicalWritePage
    ......
                    SlruFileName(ctl, path, segno);
                    fd = OpenTransientFile(path, O_RDWR | O_CREAT | PG_BINARY,
                                                               S_IRUSR | S_IWUSR);
    ......
    src/backend/storage/file/fd.c
    OpenTransientFile
    pg_fsync(fd)
    

    stap脚本

    [root@digoal ~]# cat trc.stp
    global f_start[999999]
    
    probe process("/opt/pgsql/bin/postgres").function("SlruPhysicalWritePage@/opt/soft_bak/postgresql-9.4.4/src/backend/access/transam/slru.c").call { 
       f_start[execname(), pid(), tid(), cpu()] = gettimeofday_ms()
       printf("%s <- time:%d, pp:%s, par:%s\n", thread_indent(-1), gettimeofday_ms(), pp(), $$parms$$)
       # printf("%s -> time:%d, pp:%s\n", thread_indent(1), f_start[execname(), pid(), tid(), cpu()], pp() )
    }
    
    probe process("/opt/pgsql/bin/postgres").function("SlruPhysicalWritePage@/opt/soft_bak/postgresql-9.4.4/src/backend/access/transam/slru.c").return {
      t=gettimeofday_ms()
      a=execname()
      b=cpu()
      c=pid()
      d=pp()
      e=tid()
      if (f_start[a,c,e,b]) {
      printf("%s <- time:%d, pp:%s, par:%s\n", thread_indent(-1), t - f_start[a,c,e,b], d, $return$$)
      # printf("%s <- time:%d, pp:%s\n", thread_indent(-1), t - f_start[a,c,e,b], d)
      }
    }
    
    probe process("/opt/pgsql/bin/postgres").function("OpenTransientFile@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c").call {
       f_start[execname(), pid(), tid(), cpu()] = gettimeofday_ms()
       printf("%s <- time:%d, pp:%s, par:%s\n", thread_indent(-1), gettimeofday_ms(), pp(), $$parms$$)
       # printf("%s -> time:%d, pp:%s\n", thread_indent(1), f_start[execname(), pid(), tid(), cpu()], pp() )
    }
    
    probe process("/opt/pgsql/bin/postgres").function("OpenTransientFile@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c").return {
      t=gettimeofday_ms()
      a=execname()
      b=cpu()
      c=pid()
      d=pp()
      e=tid()
      if (f_start[a,c,e,b]) {
      printf("%s <- time:%d, pp:%s, par:%s\n", thread_indent(-1), t - f_start[a,c,e,b], d, $return$$)
      # printf("%s <- time:%d, pp:%s\n", thread_indent(-1), t - f_start[a,c,e,b], d)
      }
    }
    
    probe process("/opt/pgsql/bin/postgres").function("pg_fsync@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c").call {
       f_start[execname(), pid(), tid(), cpu()] = gettimeofday_ms()
       printf("%s <- time:%d, pp:%s, par:%s\n", thread_indent(-1), gettimeofday_ms(), pp(), $$parms$$)
       # printf("%s -> time:%d, pp:%s\n", thread_indent(1), f_start[execname(), pid(), tid(), cpu()], pp() )
    }
    
    probe process("/opt/pgsql/bin/postgres").function("pg_fsync@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c").return {
      t=gettimeofday_ms()
      a=execname()
      b=cpu()
      c=pid()
      d=pp()
      e=tid()
      if (f_start[a,c,e,b]) {
      printf("%s <- time:%d, pp:%s, par:%s\n", thread_indent(-1), t - f_start[a,c,e,b], d, $return$$)
      # printf("%s <- time:%d, pp:%s\n", thread_indent(-1), t - f_start[a,c,e,b], d)
      }
    }
    

    开启一个pgbench执行txid_current()函数申请新的事务号。

    postgres@digoal-> cat 7.sql
    select txid_current();
    

    测试,约每秒产生32K左右的请求。

    postgres@digoal-> pgbench -M prepared -n -r -P 1 -f ./7.sql -c 1 -j 1 -T 100000
    progress: 240.0 s, 31164.4 tps, lat 0.031 ms stddev 0.183
    progress: 241.0 s, 33243.3 tps, lat 0.029 ms stddev 0.127
    progress: 242.0 s, 32567.3 tps, lat 0.030 ms stddev 0.179
    progress: 243.0 s, 33656.6 tps, lat 0.029 ms stddev 0.038
    progress: 244.0 s, 33948.1 tps, lat 0.029 ms stddev 0.021
    progress: 245.0 s, 32996.8 tps, lat 0.030 ms stddev 0.046
    progress: 246.0 s, 34156.7 tps, lat 0.029 ms stddev 0.015
    progress: 247.0 s, 33259.5 tps, lat 0.029 ms stddev 0.074
    progress: 248.0 s, 32979.6 tps, lat 0.030 ms stddev 0.043
    progress: 249.0 s, 32892.6 tps, lat 0.030 ms stddev 0.039
    progress: 250.0 s, 33090.7 tps, lat 0.029 ms stddev 0.020
    progress: 251.0 s, 33238.3 tps, lat 0.029 ms stddev 0.017
    progress: 252.0 s, 32341.3 tps, lat 0.030 ms stddev 0.045
    progress: 253.0 s, 31999.0 tps, lat 0.030 ms stddev 0.167
    progress: 254.0 s, 33332.6 tps, lat 0.029 ms stddev 0.056
    progress: 255.0 s, 30394.6 tps, lat 0.032 ms stddev 0.027
    progress: 256.0 s, 31862.7 tps, lat 0.031 ms stddev 0.023
    progress: 257.0 s, 31574.0 tps, lat 0.031 ms stddev 0.112
    

    跟踪backend process

    postgres@digoal-> ps -ewf|grep postgres
    postgres  2921  1883 29 09:37 pts/1    00:00:05 pgbench -M prepared -n -r -P 1 -f ./7.sql -c 1 -j 1 -T 100000
    postgres  2924  1841 66 09:37 ?        00:00:13 postgres: postgres postgres [local] SELECT
    

    从日志中抽取pg_clog相关的跟踪结果。

    [root@digoal ~]# stap -vp 5 -DMAXSKIPPED=9999999 -DSTP_NO_OVERLOAD -DMAXTRYLOCK=100 ./trc.stp -x 2924 >./stap.log 2>&1
    
         0 postgres(2924): -> time:1441503927731, pp:process("/opt/pgsql9.4.4/bin/postgres").function("SlruPhysicalWritePage@/opt/soft_bak/postgresql-9.4.4/src/backend/access/transam/slru.c:699").call, par:ctl={.shared=0x7f74a9fe39c0, .do_fsync='\001', .PagePrecedes=0x4b1960, .Dir="pg_clog"} pageno=12350 slotno=10 fdata=ERROR
        31 postgres(2924): -> time:1441503927731, pp:process("/opt/pgsql9.4.4/bin/postgres").function("OpenTransientFile@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:1710").call, par:fileName="pg_clog/0181" fileFlags=66 fileMode=384
        53 postgres(2924): <- time:0, pp:process("/opt/pgsql9.4.4/bin/postgres").function("OpenTransientFile@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:1710").return, par:14
       102 postgres(2924): -> time:1441503927731, pp:process("/opt/pgsql9.4.4/bin/postgres").function("pg_fsync@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:315").call, par:fd=14
      1096 postgres(2924): <- time:1, pp:process("/opt/pgsql9.4.4/bin/postgres").function("pg_fsync@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:315").return, par:0
      1113 postgres(2924): <- time:1, pp:process("/opt/pgsql9.4.4/bin/postgres").function("SlruPhysicalWritePage@/opt/soft_bak/postgresql-9.4.4/src/backend/access/transam/slru.c:699").return, par:'\001'
    
    1105302 postgres(2924): -> time:1441503928836, pp:process("/opt/pgsql9.4.4/bin/postgres").function("SlruPhysicalWritePage@/opt/soft_bak/postgresql-9.4.4/src/backend/access/transam/slru.c:699").call, par:ctl={.shared=0x7f74a9fe39c0, .do_fsync='\001', .PagePrecedes=0x4b1960, .Dir="pg_clog"} pageno=12351 slotno=11 fdata=ERROR
    1105329 postgres(2924): -> time:1441503928836, pp:process("/opt/pgsql9.4.4/bin/postgres").function("OpenTransientFile@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:1710").call, par:fileName="pg_clog/0181" fileFlags=66 fileMode=384
    1105348 postgres(2924): <- time:0, pp:process("/opt/pgsql9.4.4/bin/postgres").function("OpenTransientFile@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:1710").return, par:14
    1105405 postgres(2924): -> time:1441503928836, pp:process("/opt/pgsql9.4.4/bin/postgres").function("pg_fsync@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:315").call, par:fd=14
    1106440 postgres(2924): <- time:1, pp:process("/opt/pgsql9.4.4/bin/postgres").function("pg_fsync@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:315").return, par:0
    1106452 postgres(2924): <- time:1, pp:process("/opt/pgsql9.4.4/bin/postgres").function("SlruPhysicalWritePage@/opt/soft_bak/postgresql-9.4.4/src/backend/access/transam/slru.c:699").return, par:'\001'
    
    2087891 postgres(2924): -> time:1441503929819, pp:process("/opt/pgsql9.4.4/bin/postgres").function("SlruPhysicalWritePage@/opt/soft_bak/postgresql-9.4.4/src/backend/access/transam/slru.c:699").call, par:ctl={.shared=0x7f74a9fe39c0, .do_fsync='\001', .PagePrecedes=0x4b1960, .Dir="pg_clog"} pageno=12352 slotno=12 fdata=ERROR
    2087917 postgres(2924): -> time:1441503929819, pp:process("/opt/pgsql9.4.4/bin/postgres").function("OpenTransientFile@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:1710").call, par:fileName="pg_clog/0182" fileFlags=66 fileMode=384
    2087958 postgres(2924): <- time:0, pp:process("/opt/pgsql9.4.4/bin/postgres").function("OpenTransientFile@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:1710").return, par:14
    2088013 postgres(2924): -> time:1441503929819, pp:process("/opt/pgsql9.4.4/bin/postgres").function("pg_fsync@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:315").call, par:fd=14
    2089250 postgres(2924): <- time:1, pp:process("/opt/pgsql9.4.4/bin/postgres").function("pg_fsync@/opt/soft_bak/postgresql-9.4.4/src/backend/storage/file/fd.c:315").return, par:0
    2089265 postgres(2924): <- time:1, pp:process("/opt/pgsql9.4.4/bin/postgres").function("SlruPhysicalWritePage@/opt/soft_bak/postgresql-9.4.4/src/backend/access/transam/slru.c:699").return, par:'\001'
    

    计算估计,每隔1秒左右会产生一次fsync。

    postgres=# select 1441503928836-1441503927731;
     ?column? 
    ----------
         1105
    (1 row)
    
    postgres=# select 1441503929819-1441503928836;
     ?column? 
    ----------
          983
    (1 row)
    

    前面pgbench的输出看到每秒产生约32000个事务,刚好等于一个clog页的事务数(本例数据块大小为8KB)。
    每个事务需要2个比特位,每个字节存储4个事务信息,8192*4=32768。

    如果你需要观察backend process不刷clog buffer脏页的情况。可以把checkpoint 间隔改小,或者手动执行checkpoint,同时还需要把clog buffer pages改大,例如:

    Size
    CLOGShmemBuffers(void)
    {
    	return Min(1024, Max(4, NBuffers / 2));
    }
    

    使用同样的stap脚本,你就观察不到backend process主动刷clog dirty page了。

    通过以上分析,如果你发现backend process频繁的clog,可以采取一些优化手段。

    1. 因为每次扩展pg_clog文件后,文件大小都会发生变化,此时如果backend process调用pg_fdatasync也会写文件系统metadata journal(以EXT4为例,假设mount参数data不等于writeback),这个操作是整个文件系统串行的,容易产生堵塞;
      所以backend process挑选clog page时,不选择最近的page number可以起到一定的效果,(最好是不选择最近的clog file中的pages);
      另一种方法是先调用sync_file_range, SYNC_FILE_RANGE_WAIT_BEFORE | SYNC_FILE_RANGE_WRITE | SYNC_FILE_RANGE_WAIT_AFTER,它不需要写metadata。将文件写入后再调用pg_fsync。减少等待data fsync的时间;
    2. pg_clog文件预分配,目前pg_clog单个文件的大小是由CLOGShmemBuffers决定的,为BLOCKSZ的32倍。可以尝试预分配这个文件,而不是每次都扩展,改变它的大小;
    3. 延迟backend process 的 fsync请求到checkpoint处理。

    [参考]
    https://wiki.postgresql.org/wiki/Hint_Bits
    http://blog.163.com/digoal@126/blog/static/1638770402015840480734/
    src/backend/access/transam/varsup.c
    src/backend/access/transam/clog.c
    src/backend/access/transam/slru.c
    src/include/access/slru.h
    src/backend/access/transam/subtrans.c
    src/backend/storage/file/fd.c

    pg_clog的原子操作与pg_subtrans(子事务)

    如果没有子事务,其实很容易保证pg_clog的原子操作,但是,如果加入了子事务并为子事务分配了XID,并且某些子事务XID和父事务的XID不在同一个CLOG PAGE时,保证事务一致性就涉及CLOG的原子写了。

    PostgreSQL是通过2PC来实现CLOG的原子写的:

    1. 首先将主事务以外的CLOG PAGE中的子事务设置为sub-committed状态;
    2. 然后将主事务所在的CLOG PAGE中的子事务设置为sub-committed,同时设置主事务为committed状态,将同页的子事务设置为committed状态;
    3. 将其他CLOG PAGE中的子事务设置为committed状态;

    src/backend/access/transam/clog.c

    /*
     * TransactionIdSetTreeStatus
     *
     * Record the final state of transaction entries in the commit log for
     * a transaction and its subtransaction tree. Take care to ensure this is
     * efficient, and as atomic as possible.
     *
     * xid is a single xid to set status for. This will typically be
     * the top level transactionid for a top level commit or abort. It can
     * also be a subtransaction when we record transaction aborts.
     *
     * subxids is an array of xids of length nsubxids, representing subtransactions
     * in the tree of xid. In various cases nsubxids may be zero.
     *
     * lsn must be the WAL location of the commit record when recording an async
     * commit.  For a synchronous commit it can be InvalidXLogRecPtr, since the
     * caller guarantees the commit record is already flushed in that case.  It
     * should be InvalidXLogRecPtr for abort cases, too.
     *
     * In the commit case, atomicity is limited by whether all the subxids are in
     * the same CLOG page as xid.  If they all are, then the lock will be grabbed
     * only once, and the status will be set to committed directly.  Otherwise
     * we must
     *       1. set sub-committed all subxids that are not on the same page as the
     *              main xid
     *       2. atomically set committed the main xid and the subxids on the same page
     *       3. go over the first bunch again and set them committed
     * Note that as far as concurrent checkers are concerned, main transaction
     * commit as a whole is still atomic.
     *
     * Example:
     *              TransactionId t commits and has subxids t1, t2, t3, t4
     *              t is on page p1, t1 is also on p1, t2 and t3 are on p2, t4 is on p3
     *              1. update pages2-3:
     *                                      page2: set t2,t3 as sub-committed
     *                                      page3: set t4 as sub-committed
     *              2. update page1:
     *                                      set t1 as sub-committed,
     *                                      then set t as committed,
                                            then set t1 as committed
     *              3. update pages2-3:
     *                                      page2: set t2,t3 as committed
     *                                      page3: set t4 as committed
     *
     * NB: this is a low-level routine and is NOT the preferred entry point
     * for most uses; functions in transam.c are the intended callers.
     *
     * XXX Think about issuing FADVISE_WILLNEED on pages that we will need,
     * but aren't yet in cache, as well as hinting pages not to fall out of
     * cache yet.
     */
    

    实际调用的入口代码在transam.c,subtrans.c中是一些低级接口。

    那么什么是subtrans?
    当我们使用savepoint时,会产生子事务。子事务和父事务一样,可能消耗XID。一旦为子事务分配了XID,那么就涉及CLOG的原子操作了,因为要保证父事务和所有的子事务的CLOG一致性。
    当不消耗XID时,需要通过SubTransactionId来区分子事务。

    src/backend/acp:process("/opt/pgsql9.4.4/bin/postgres").function("SubTransSetParent@/opt/soft_bak/postgresql-9.4.4/src/backend/access/transam/subtrans.c:75").return, par:pageno=? entryno=? slotno=607466858 ptr=0
    

    重新开一个会话,你会发现,子事务也消耗了XID。因为重新分配的XID已经从607466859开始了。

    postgres@digoal-> psql
    psql (9.4.4)
    Type "help" for help.
    postgres=# select txid_current();
     txid_current 
    --------------
        607466859
    (1 row)
    

    [参考]
    src/backend/access/transam/clog.c
    src/backend/access/transam/subtrans.c
    src/backend/access/transam/transam.c
    src/backend/access/transam/README
    src/include/c.hr

    CLOG一致性和异步提交

    异步提交是指不需要等待事务对应的wal buffer fsync到磁盘,即返回,而且写CLOG时也不需要等待XLOG落盘。
    而pg_clog和pg_xlog是两部分存储的,那么我们想一想,如果一个已提交事务的pg_clog已经落盘,而XLOG没有落盘,刚好此时数据库CRASH了。数据库恢复时,由于该事务对应的XLOG缺失,数据无法恢复到最终状态,但是PG_CLOG却显示该事务已提交,这就出问题了。

    所以对于异步事务,CLOG在write前,务必等待该事务对应的XLOG已经FLUSH到磁盘。

    PostgreSQL如何记录事务和它产生的XLOG的LSN的关系呢?
    其实不是一一对应的关系,而是记录了多事务对一个LSN的关系。
    src/backend/access/transam/clog.c
    LSN组,每32个事务,记录它们对应的最大LSN。
    也就是32个事务,只记录最大的LSN。节约空间?

    /* We store the latest async LSN for each group of transactions */
    #define CLOG_XACTS_PER_LSN_GROUP        32      /* keep this a power of 2 */
    
    每个CLOG页需要分成多少个LSN组。
    #define CLOG_LSNS_PER_PAGE      (CLOG_XACTS_PER_PAGE / CLOG_XACTS_PER_LSN_GROUP)
    
    #define GetLSNIndex(slotno, xid)        ((slotno) * CLOG_LSNS_PER_PAGE + \
            ((xid) % (TransactionId) CLOG_XACTS_PER_PAGE) / CLOG_XACTS_PER_LSN_GROUP)
    

    LSN被存储在这个数据结构中
    src/include/access/slru.h

    /*
     * Shared-memory state
     */
    typedef struct SlruSharedData
    {
    ......
    	/*
             * Optional array of WAL flush LSNs associated with entries in the SLRU
             * pages.  If not zero/NULL, we must flush WAL before writing pages (true
             * for pg_clog, false for multixact, pg_subtrans, pg_notify).  group_lsn[]
             * has lsn_groups_per_page entries per buffer slot, each containing the
             * highest LSN known for a contiguous group of SLRU entries on that slot's
             * page.  仅仅pg_clog需要记录group_lsn
             */
            XLogRecPtr *group_lsn;  // 一个数组,存储32个事务组成的组中最大的LSN号。
            int                     lsn_groups_per_page;
    ......
    
    

    src/backend/access/transam/clog.c

     * lsn must be the WAL location of the commit record when recording an async
     * commit.  For a synchronous commit it can be InvalidXLogRecPtr, since the
     * caller guarantees the commit record is already flushed in that case.  It
     * should be InvalidXLogRecPtr for abort cases, too.
    
    void
    TransactionIdSetTreeStatus(TransactionId xid, int nsubxids,
                                            TransactionId *subxids, XidStatus status, XLogRecPtr lsn)
    {
    ......
    

    更新事务状态时,同时更新对应LSN组的LSN为最大LSN值。(CLOG BUFFER中的操作)

    /*
     * Sets the commit status of a single transaction.
     *
     * Must be called with CLogControlLock held
     */
    static void
    TransactionIdSetStatusBit(TransactionId xid, XidStatus status, XLogRecPtr lsn, int slotno)
    {
    ......
            /*
             * Update the group LSN if the transaction completion LSN is higher.
             *
             * Note: lsn will be invalid when supplied during InRecovery processing,
             * so we don't need to do anything special to avoid LSN updates during
             * recovery. After recovery completes the next clog change will set the
             * LSN correctly.
             */
            if (!XLogR       int                     lsnindex = GetLSNIndex(slotno, xid);
    
                    if (ClogCtl->shared->group_lsn[lsnindex] < lsn)  // 更新组LSN
                            ClogCtl->shared->group_lsn[lsnindex] = lsn;
            }
    ......
    

    将事务标记为commit状态,对于异步事务,多一个LSN参数,用于修改事务组的最大LSN。

    /*
     * TransactionIdCommitTree
     *              Marks the given transaction and children as committed
     *
     * "xid" is a toplevel transaction commit, and the xids array contains its
     * committed subtransactions.
     *
     * This commit operation is not guaranteed to be atomic, but if not, subxids
     * are correctly marked subcommit first.
     */
    void
    TransactionIdCommitTree(TransactionId xid, int nxids, TransactionId *xids)
    {
            TransactionIdSetTreeStatus(xid, nxids, xids,
                                                               TRANSACTION_STATUS_COMMITTED,
                                                               InvalidXLogRecPtr);
    }
    
    /*
     * TransactionIdAsyncCommitTree
     *              Same as above, but for async commits.  The commit record LSN is needed.
     */
    void
    TransactionIdAsyncCommitTree(TransactionId xid, int nxids, TransactionId *xids,
                                                             XLogRecPtr lsn)
    {
            TransactionIdSetTreeStatus(xid, nxids, xids,
                                                               TRANSACTION_STATUS_COMMITTED, lsn);
    }
    
    /*
     * TransactionIdAbortTree
     *              Marks the given transaction and children as aborted.
     *
     * "xid" is a toplevel transaction commit, and the xids array contains its
     * committed subtransactions.
     *
     * We don't need to worry about the non-atomic behavior, since any onlookers
     * will consider all the xacts as not-yet-committed anyway.
     */
    void
    TransactionIdAbortTree(TransactionId xid, int nxids, TransactionId *xids)
    {
            TransactionIdSetTreeStatus(xid, nxids, xids,
                                                               TRANSACTION_STATUS_ABORTED, InvalidXLogRecPtr);
    }
    

    从XID号,获取它对应的LSN,需要注意的是,这个XID如果是一个FROZEN XID,则返回一个(XLogRecPtr) invalid lsn。
    src/backend/access/transam/transam.c

    /*
     * TransactionIdGetCommitLSN
     *
     * This function returns an LSN that is late enough to be able
     * to guarantee that if we flush up to the LSN returned then we
     * will have flushed the transaction's commit record to disk.
     *
     * The result is not necessarily the exact LSN of the transaction's
     * commit record!  For example, for long-past transactions (those whose
     * clog pages already migrated to disk), we'll return InvalidXLogRecPtr.
     * Also, because we group transactions on the same clog page to conserve
     * storage, we might return the LSN of a later transaction that falls into
     * the same group.
     */
    XLogRecPtr
    TransactionIdGetCommitLSN(TransactionId xid)
    {
            XLogRecPtr      result;
    
            /*
             * Currently, all uses of this function are for xids that were just
             * reported to be committed by TransactionLogFetch, so we expect that
             * checking TransactionLogFetch's cache will usually succeed and avoid an
             * extra trip to shared memory.
             */
            if (TransactionIdEquals(xid, cachedFetchXid))
                    return cachedCommitLSN;
    
            /* Special XIDs are always known committed */
            if (!TransactionIdIsNormal(xid))
                    return InvalidXLogRecPtr;
    
            /*
             * Get the transaction status.
             */
            (void) TransactionIdGetStatus(xid, &result);
    
            return result;
    }
    
    
    /*
     * Interrogate the state of a transaction in the commit log.
     *
     * Aside from the actual commit status, this function returns (into *lsn)
     * an LSN that is late enough to be able to guarantee that if we flush up to
     * that LSN then we will have flushed the transaction's commit record to disk.
     * The result is not necessarily the exact LSN of the transaction's commit
     * record!      For example, for long-past transactions (those whose clog pages  // long-past事务,指非标准事务号。例), we'll return InvalidXLogRecPtr.  Also, because
     * we group transactions on the same clog page to conserve storage, we might
     * return the LSN of a later transaction that falls into the same group.
     *
     * NB: this is a low-level routine and is NOT the preferred entry point
     * for most uses; TransactionLogFetch() in transam.c is the intended caller.
     */
    XidStatus
    TransactionIdGetStatus(TransactionId xid, XLogRecPtr *lsn)
    {
            int                     pageno = TransactionIdToPage(xid);
            int                     byteno = TransactionIdToByte(xid);
            int                     bshift = TransactionIdToBIndex(xid) * CLOG_BITS_PER_XACT;
            int                     slotno;
            int                     lsnindex;
            char       *byteptr;
            XidStatus       status;
    
            /* lock is acquired by SimpleLruReadPage_ReadOnly */
    
            slotno = SimpleLruReadPage_ReadOnly(ClogCtl, pageno, xid);
            byteptr = ClogCtl->shared->page_buffer[slotno] + byteno;
    
            status = (*byteptr >> bshift) & CLOG_XACT_BITMASK;
    
            lsnindex = GetLSNIndex(slotno, xid);
            *lsn = ClogCtl->shared->group_lsn[lsnindex];
    
            LWLockRelease(CLogControlLock);
    
            return status;
    }
    

    前面所涉及的都是CLOG BUFFER中的操作,如果要将buffer写到磁盘,则真正需要涉及到一致性的问题,即在将CLOG write到磁盘前,必须先确保对应的事务产生的XLOG已经flush到磁盘。那么这里就需要用到前面每个LSN组中记录的max LSN了。
    代码如下:
    src/backend/access/transam/slru.c

    /*
     * Physical write of a page from a buffer slot
     *
     * On failure, we cannot just ereport(ERROR) since caller has put state in
     * shared memory that must be undone.  So, we return FALSE and save enough
     * info in static variables to let SlruReportIOError make the report.
     *
     * For now, assume it's not worth keeping a file pointer open across
     * independent read/write operations.  We do batch operations during
     * SimpleLruFlush, though.
     *
     * fdata is NULL for a standalone write, pointer to open-file info during
     * SimpleLruFlush.
     */
    static bool
    SlruPhysicalWritePage(SlruCtl ctl, int pageno, int slotno, SlruFlush fdata)
    {
            SlruShared      shared = ctl->shared;
            int                     segno = pageno / SLRU_PAGES_PER_SEGMENT;
            int                     rpageno = pageno % SLRU_PAGES_PER_SEGMENT;
            int                     offset = rpageno * BLCKSZ;
            char            path[MAXPGPATH];
            int                     fd = -1;
    
            /*
             * Honor the write-WAL-before-data rule, if appropriate, so that we do not
             * write out data before associated WAL records.  This is the same action
             * performed during FlushBuffer() in the main buffer manager.
             */
            if (shared->group_lsn != NULL)
            {
                    /*
                     * We must determine the largest async-commit LSN for the page. This
                     * is a bit tedious, but since this entire function is a slow path
                     * anyway, it seems better to do this here than to maintain a per-page
                     * LSN variable (which'd need an extra comparison in the
                     * transaction-commit path).
                     */
                    XLogRecPtr      max_lsn;
                    int                     lsnindex,
                                            lsnoff;
    
                    lsnindex = slotno * shared->lsn_groups_per_page;
                    max_lsn = shared->group_lsn[lsnindex++];
                    for (lsnoff = 1; lsnoff < shared->lsn_groups_per_page; lsnoff++)
                    {
                            XLogRecPtr      this_lsn = shared->group_lsn[lsnindex++];
    
                            if (max_lsn < this_lsn)
                                    max_lsn = this_lsn;
                    }
    
                    if (!XLogRecPtrIsInvalid(max_lsn))  // 判断max_lsn是不是一个有效的LSN,如果是有效的LSN,说明需要先调用xlogflush将wal buffer中小于该LSN以及以前的buffer写入磁盘。
    		                                            则。
                    {
                            /*
                             * As noted above, elog(ERROR) is not acceptable here, so if
                             * XLogFlush were to fail, we must PANIC.  This isn't much of a
                             * restriction because XLogFlush is just about all critical
                             * section anyway, but let's make sure.
                             */
                            START_CRIT_SECTION();
                            XLogFlush(max_lsn);
                            END_CRIT_SECTION();
                    }
            }
    ......
    

    小结
    对于异步事务,如何保证write-WAL-before-data规则?
    pg_clog将32个事务分为一组,存储这些事务的最大LSN。存储在SlruSharedData结构中。
    在将clog buffer write到磁盘前,需要确保该clog page对应事务的xlog LSN已经flush到磁盘。

    [参考]
    src/backend/access/transam/clog.c
    src/include/access/slru.h
    src/backend/access/transam/transam.c
    src/backend/access/transam/slru.c

    展开全文
  • TXSQL是什么云计算时代数据库核弹头 背景5月23-24日以焕启为主题的腾讯云+未来峰会在广州召开广东省各级政府机构领导海内外业内学术专家行业大咖及技术大牛等在现场共议云计算与数字化产业创新发展 腾讯MySQL内核...
  • 其实云数据库是指被优化或部署到一个虚拟计算环境中的数据库,可以实现按需付费、按需扩展、高可用性以及存储整合等优势。那么,云数据库什么用呢?下面,我们就来看看吧。 传统数据库强调维护数据的完整性、一致...
  • 如果您打算不准备使用此功能,则需要设置一个MySql数据库(除非您只是删除使用数据库的方法并输入一个硬编码的密码),最好从Microsoft的Azure服务获得(最好ez且价格便宜) )。 完成操作后,将您的凭证填充到...
  • 作者介绍:简怀兵,腾讯云数据库高级工程师,负责腾讯云CDB内核及基础设施建设;先后供职于Thomson Reuters和YY等公司,PTimeDB作者,曾获一项发明专利;从事MySQL内核开发工作8年,具有丰富的优化经验;在分布式...
  • 什么PG只是用REDO方法来进行数据库的恢复 在阅读<PG数据内核分析>一书中, 看到这样一句话: PG只是用REDO方法来进行数据库的恢复, 它不使用UNDO因为其数据的多版本使得UNDO没有必要. 但从本质上来讲, PG...
  • 数据库优化每个开发、运维同学成长过程中的必修课,特此,我们从腾云阁文章中梳理了一部分受开发者欢迎的数据库文章。供大家参考 腾讯云开发者社区今天想和各位开发者一起探讨下你有什么独家数据库优化技巧?欢迎...
  • 一直以来,数据库的核心研发团队都十分神秘,作为隐藏在幕后的隐士高人,他们对数据库发展以及数据库研发团队的看法是什么呢?本文我们就由巨杉数据库核心技术研发团队的“老司机”,向大家分享他分布式数据库的自研...
  • 在internet services课上老师说到Capriccio 用户模式下的thread library,OS课里第2个project也...但内核就像一个黑盒,一直不明白它做了什么,怎么做到的。Modern Operating System有章讲user space和kernel...
  • 数据库基础

    2012-09-22 16:27:04
    §11.2.1 什么是PL/SQL? 231 §11.2.1 PL/SQL的好处 232 §11.2.1.1 有利于客户/服务器环境应用的运行 232 §11.2.1.2 适合于客户环境 232 §11.2.1.3 客户及服务器端的好处 232 §11.2.2 PL/SQL 可用的SQL语句 233 ...
  • oracle数据库hanganalyze

    2013-10-13 13:20:32
    什么要使用hanganalyze ...而当这种死锁发生在争夺内核级别的资源(比如说pins或latches)时,Oracle并不能自动的监测并处理这种死锁。 其实很多时候数据库并没有hang住,而只是由于数据库
  • GPU数据库介绍

    千次阅读 2018-05-17 22:41:47
    一、为什么需要GPU数据库 CPU CPU需要很强的通用性来处理各种不同的数据类型,同时又要逻辑判断又会引入大量的分支跳转和中断的处理。这些都使得CPU的内部结构异常复杂; 所以CPU一般由几个针对顺序串行处理而优化...
  • 前几天在看 2018 云栖大会,来自中科院计算所的陈世敏研究员在“数据库内核专场”做了一场《NVM在数据库领域的研究和探索》的报告演讲。在30分钟的演讲中,其中有近10页PPT的内容和B+Tree这种索引有关。 例如其中的...
  • 分布式数据库的数据一致性管理是其最重要的内核技术之一,也是保证分布式数据库满足数据库最基本的ACID特性中的“一致性”(Consistency)的保障。...1.1数据一致性是什么 大部份使用传统关系型数据库的DBA在看到...
  • 【Linux 6】通过Shell编程实现定时备份数据库

    千次阅读 多人点赞 2021-04-17 12:13:44
    一、为什么要学习Shell编程 1、Linux运维工程师在进行服务器...二、Shell是什么 Shell是一个命令行解释器,它为用户提供了一个向Linux内核发送请求以便运行程序的界面系统级程序,用户可以用Shell来启动、挂起、停止
  •   本项目用于Linux Cpp后台开发秋招学习,内容主要涵盖以下几个部分:Cpp进阶,操作系统, 计算机网络, Linux内核,MySQL数据库, Redis数据库, 数据结构与算法,Leetcode刷题等内容。我会对校招所需掌握的基础...
  • Mysql 数据库优化

    2021-03-19 14:15:53
    公司项目添加新功能,上线后发现有些功能的列表查询时间很久,需要仔细排查到底什么原因导致的,不一定sql语句的问题 导致数据查询慢的原因有多种,如:缓存失效,由于高并发访问导致 MySQL 服务器崩溃;SQL 语句...
  • 前言 分布式数据库的数据一致性管理是其最重要的内核技术之一,也是保证分布式数据库满足数据库最基本的ACID特性中的 “一致性”(Consistency)的保障。在分布式技术发展下,...1.1数据一致性是什么 大部份使用传统关
  • Android中的SQLite数据库的简单使用什么是SQLiteSQLite,一款轻型的数据库遵守ACID(原子性、一致性、隔离性、持久性)的关联式数据库管理系统,多用于嵌入式开发中。 它D.Richard Hipp用C语言编写的开源...
  • Android系统内核是Linux系统,Android系统很特殊,他自带了一个SQLite数据库,轻量型的一款嵌入式的数据库 它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。操作简单,你如果是之前学过其他的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 600
精华内容 240
关键字:

数据库内核是什么