From 1d7e7ebd96f37f2d3b41385502221e9683a52b3d Mon Sep 17 00:00:00 2001 From: huzheyuan123 <1980452465@qq.com> Date: Wed, 12 Apr 2023 13:53:38 +0800 Subject: [PATCH 1/2] Push huzheyuan123 Test Task --- TestTasks/huzheyuan123/openGauss.md | 1725 +++++++++++++++++++++++++++ 1 file changed, 1725 insertions(+) create mode 100644 TestTasks/huzheyuan123/openGauss.md diff --git a/TestTasks/huzheyuan123/openGauss.md b/TestTasks/huzheyuan123/openGauss.md new file mode 100644 index 0000000..c0864f7 --- /dev/null +++ b/TestTasks/huzheyuan123/openGauss.md @@ -0,0 +1,1725 @@ +# openGauss + +openGauss是一款全面友好开放,携手伙伴共同打造的企业级开源关系型数据库。openGauss提供面向多核架构的极致性能、全链路的业务、数据安全、基于AI的调优和高效运维的能力。 + +* openGauss是一个数据库管理系统。 + +* openGauss数据库是关系型的。 + +* openGauss软件是开源的。 + +* openGauss数据库具有高性能、高可用、高安全、易运维、全开放的特点。 + + * 高性能 + + 提供了面向多核架构的并发控制技术,结合鲲鹏硬件优化方案,在两路鲲鹏下,TPCC Benchmark可以达到150万tpmc的性能。 + 针对当前硬件多核numa的架构趋势, 在内核关键结构上采用了Numa-Aware的数据结构。 + 提供Sql-bypass智能快速引擎技术。 + 针对数据频繁更新的场景,提供ustore存储引擎。 + + * 高可用 + + 支持主备同步、异步以及级联备机多种部署模式。 + 数据页CRC校验,损坏数据页通过备机自动修复。 + 备机并行恢复,10秒内可升主提供服务。 + 提供基于paxos分布式一致性协议的日志复制及选主框架。 + 高安全 + + 支持全密态计算,访问控制、加密认证、数据库审计、动态数据脱敏等安全特性,提供全方位端到端的数据安全保护。 + + * 易运维 + + 基于AI的智能参数调优和索引推荐,提供AI自动参数推荐。 + 慢SQL诊断,多维性能自监控视图,实时掌控系统的性能表现。 + 提供在线自学习的SQL时间预测。 + + * 全开放 + 采用木兰宽松许可证协议,允许对代码自由修改,使用,引用。 + 数据库内核能力全开放。 + 提供丰富的伙伴认证,培训体系和高校课程。 + +## 安装 + +环境:本人使用CentOS 7.6进行安装 +软件依赖: +![image-20230412132534575](https://cdn.jsdelivr.net/gh/15197580192/imag@main/img/image-20230412132534575.png) +创建用户组dbgroup。 + +``` +groupadd dbgroup +``` + +创建用户组dbgroup下的普通用户omm,并设置普通用户omm的密码,密码建议设置为omm@123。 + +``` +useradd -g dbgroup omm +passwd omm +``` + +使用omm用户登录到openGauss包安装的主机,解压openGauss压缩包到安装目录(假定安装目录为/opt/software/openGauss,请用实际值替换)。 + +``` +tar -jxf openGauss-x.x.x-操作系统-64bit.tar.bz2 -C /opt/software/openGauss +``` + +假定解压包的路径为/opt/software/openGauss,进入解压后目录下的simpleInstall。 + +``` +cd /opt/software/openGauss/simpleInstall +``` + +执行install.sh脚本安装openGauss。 + +``` +sh install.sh -w xxxx +``` + +上述命令中,-w是指初始化数据库密码(gs_initdb指定),安全需要必须设置。 + +安装执行完成后,使用ps和gs_ctl查看进程是否正常。 + +``` +ps ux | grep gaussdb +gs_ctl query -D /opt/software/openGauss/data/single_node +``` + +执行ps命令,显示类似如下信息: + +``` +omm 24209 11.9 1.0 1852000 355816 pts/0 Sl 01:54 0:33 /opt/software/openGauss/bin/gaussdb -D /opt/software/openGauss/single_node +omm 20377 0.0 0.0 119880 1216 pts/0 S+ 15:37 0:00 grep --color=auto gaussdb +``` + +执行gs_ctl命令,显示类似如下信息: + +``` +gs_ctl query ,datadir is /opt/software/openGauss/data/single_node +HA state: + local_role : Normal + static_connections : 0 + db_state : Normal + detail_information : Normal + +Senders info: + No information + + Receiver info: +No information +``` + +## 系统架构 + +openGauss主要包含了openGauss服务器、客户端驱动、OM等模块,它的架构如图1所示,模块说明如表1所示。 +![image-20230412133427269](https://cdn.jsdelivr.net/gh/15197580192/imag@main/img/image-20230412133427269.png) +|图1 openGauss软件架构| +![image-20230412133434722](https://cdn.jsdelivr.net/gh/15197580192/imag@main/img/image-20230412133434722.png) +表1 openGauss模块说明 + +## 代码结构 + +(一)通信管理 +openGauss查询响应是使用简单的“单一用户对应一个服务器线程”的客户端/服务器模型实现的。由于我们无法提前知道需要建立多少个连接,因此必须使用主进程(GaussMaster)主进程在指定的TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/互联网协议)端口上侦听传入的连接,只要检测到连接请求,主进程就会生成一个新的服务器线程。服务器线程之间使用信号量和共享内存相互通信,以确保整个并发数据访问期间的数据完整性。 +客户端进程可以被理解为满足openGauss协议的任何程序。许多客户端都基于C语言库libpq进行通信,但是该协议有几种独立的实现,例如Java JDBC驱动程序。 +建立连接后,客户端进程可以将查询发送到后端服务器。查询是使用纯文本传输的,即在前端(客户端)中没有进行解析。服务器解析查询语句、创建执行计划、执行并通过在已建立的连接上传输检索到的结果集,将其返回给客户端。 +openGauss数据库中处理客户端连接请求的模块叫做postmaster。前端程序发送启动信息给postmaster,postmaster根据信息内容建立后端响应线程。postmaster也管理系统级的操作比如调用启动和关闭程序。postmaster在启动时创建共享内存和信号量池,但它自身不管理内存、信号量和锁操作。 +当客户端发来一个请求信息,postmaster立刻启动一个新会话,新会话对请求进行验证,验证成功后为它匹配后端工作线程。这种模式架构上处理简单,但是高并发下由于线程过多,切换和轻量级锁区域的冲突过大导致性能急剧下降。因此openGauss通过线程资源池化复用的技术来解决该问题。线程池技术的整体设计思想是线程资源池化,并且在不同连接直接复用。 + +1. postmaster源码组织 + postmaster源码目录为: + +/src/gausskernel/process/postmaster。postmaster源码文件如表2所示。 + +表2 postmaster源码文件 +postmaster + +postmaster.cpp + +用户响应主程序 + +aiocompleter.cpp + +完成预取(Prefetch)和后端写(BackWrite)I/O操作 + +alarmchecker.cpp + +闹钟检查线程 + +lwlockmonitor.cpp + +轻量锁的死锁检测 + +pagewriter.cpp + +写页面 + +pgarch.cpp + +日志存档 + +pgaudit.cpp + +审计线程 + +pgstat.cpp + +统计信息收集 + +startup.cpp + +服务初始化和恢复 + +syslogger.cpp + +捕捉并写所有错误日志 + +autovacuum.cpp + +垃圾清理线程 + +bgworker.cpp + +后台工作线程 + +(服务共享内存) + +bgwriter.cpp + +后台写线程(写共享缓存) + +cbmwriter.cpp + +remoteservice.cpp + +postmaster信号处理 + +checkpointer.cpp + +检查点处理 + +fencedudf.cpp + +保护模式下运行用户定义函数 + +gaussdb_version.cpp + +版本特性控制 + +twophasecleaner.cpp + +清理两阶段事务线程 + +walwriter.cpp + +预写式日志写入 + +2. postmaster主流 + +postmaster主流程代码如下。 + +``` +/* postmaster.cpp */ +... +int PostmasterMain(int argc, char* argv[]) +{ +InitializePostmasterGUC(); /* 初始化postmaster配置参数*/ +... +pgaudit_agent_init(); /* 初始化审计模块*/ +... +for (i = 0; i < MAXLISTEN; i++) /* 建立输入socket监听*/ + t_thrd.postmaster_cxt.ListenSocket[i] = PGINVALID_SOCKET; +... +/* 建立共享内存和信号量池*/ +reset_shared(g_instance.attr.attr_network.PostPortNumber); +... +/* 初始化postmaster信号管理*/ +gs_signal_slots_init(GLOBAL_ALL_PROCS + EXTERN_SLOTS_NUM); +... +InitPostmasterDeathWatchHandle(); /* 初始化宕机监听* +... +pgstat_init(); /* 初始化统计数据收集子系统*/ +InitializeWorkloadManager(); /* 初始化工作负载管理器*/ +... +InitUniqueSQL(); /* 初始化unique sql资源*/ +... +autovac_init(); /* 初始化垃圾清理线程子系统*/ +... +status = ServerLoop(); /* 启动postmaster主业务循环*/ +... +} +``` + +(二) SQL引擎 +数据库的SQL引擎是数据库重要的子系统之一,它对上负责承接应用程序发送过来的SQL语句,对下则负责指挥执行器运行执行计划。其中优化器作为SQL引擎中最重要、最复杂的模块,被称为数据库的“大脑”,优化器产生的执行计划的优劣直接决定数据库的性能。 + +本篇从SQL语句发送到数据库服务器开始,对SQL引擎的各个模块进行全面的介绍与源码解析,以实现对SQL语句执行的逻辑与源码更深入的理解。其响应流程如图2所示。 +![image-20230412133657213](https://cdn.jsdelivr.net/gh/15197580192/imag@main/img/image-20230412133657213.png) + +|图2 openGauss数据库SQL查询响应流程| + +1. 查询解析——parser + SQL解析对输入的SQL语句进行词法分析、语法分析、语义分析,获得查询解析树或者逻辑计划。SQL查询语句解析的解析器(parser)阶段包括如下: + +(1)词法分析:从查询语句中识别出系统支持的关键字、标识符、操作符、终结符等,每个词确定自己固有的词性。 +(2)语法分析:根据SQL语言的标准定义语法规则,使用词法分析中产生的词去匹配语法规则,如果一个SQL语句能够匹配一个语法规则,则生成对应的语法树(Abstract Synatax Tree,AST)。 +(3)语义分析:对语法树(AST)进行检查与分析,检查AST中对应的表、列、函数、表达式是否有对应的元数据(指数据库中定义有关数据特征的数据,用来检索数据库信息)描述,基于分析结果对语法树进行扩充,输出查询树。主要检查的内容包括: +①检查关系的使用:FROM子句中出现的关系必须是该查询对应模式中的关系或视图。 +②检查与解析属性的使用:在SELECT句中或者WHERE子句中出现的各个属性必须是FROM子句中某个关系或视图的属性。 +③检查数据类型:所有属性的数据类型必须是匹配的。 +词法和语法分析代码基于gram.y和scan.l中定义的规则,使用Unix工具bison和flex构建产生。其中,词法分析器在文件scan.l中定义,它负责识别标识符、SQL关键字等。对于找到的每个关键字或标识符,都会生成一个标记并将其传递给解析器。语法解析器在文件gram.y中定义,由一组语法规则和每当触发规则时执行的动作组成,基于这些动作代码构架并输出语法树。在解析过程中,如果语法正确,则进入语义分析阶段并建立查询树返回,否则将返回错误,终止解析过程。 + +解析器在词法和语法分析阶段仅使用有关SQL语法结构的固定规则来创建语法树。它不会在系统目录中进行任何查找,因此无法理解所请求操作的详细语义。 + +语法解析完成后,语义分析过程将解析器返回的语法树作为输入,并进行语义分析以了解查询所引用的表、函数和运算符。用来表示此信息的数据结构称为查询树。解析器解析过程分为原始解析与语义分析,分开的原因是,系统目录查找只能在事务内完成,并且我们不希望在收到查询字符串后立即启动事务。原始解析阶段足以识别事务控制命令(BEGIN,ROLLBACK等),然后可以正确执行这些命令而无需任何进一步分析。一旦知道我们正在处理实际查询(例如SELECT或UPDATE),就可以开始事务,这时才调用语义分析过程。 + +1) parser源码组织 +parser源码目录为:/src/common/backend/parser。parser源码文件如表3所示 +表3 parser源码文件 + +parser + +parser.cpp + +解析主程序 + +scan.l + +词法分析,分解查询成token + +scansup.cpp + +处理查询语句转义符 + +kwlookup.cpp + +将关键词转化为具体的token + +keywords.cpp + +标准关键词列表 + +analyze.cpp + +语义分析 + +gram.y + +语法分析,解析查询tokens并产生原始解析树 + +parse_agg.cpp + +处理聚集操作,比如SUM(col1),AVG(col2) + +parse_clause.cpp + +处理子句,比如WHERE,ORDER BY + +parse_compatibility.cpp + +处理数据库兼容语法和特性支持 + +parse_coerce.cpp + +处理表达式数据类型强制转换 + +parse_collate.cpp + +对完成表达式添加校对信息 + +parse_cte.cpp + +处理公共表格表达式(WITH 子句) + +parse_expr.cpp + +处理表达式,比如col, col+3, x = 3 + +parse_func.cpp + +处理函数,table.column和列标识符 + +parse_node.cpp + +对各种结构创建解析节点 + +parse_oper.cpp + +处理表达式中的操作符 + +parse_param.cpp + +处理参数 + +parse_relation.cpp + +支持表和列的关系处理程序 + +parse_target.cpp + +处理查询解析的结果列表 + +parse_type.cpp + +处理数据类型 + +parse_utilcmd.cpp + +处理实用命令的解析分析 + +2) parser主流程 + +parser主流程代码如下。 + +``` +/* parser.cpp */ +... +/* 原始解析器,输入查询字符串,做词法和语法分析,返回原始语法解析树列表*/ +List* raw_parser(const char* str, List** query_string_locationlist) +{ +... + /* 初始化 flex scanner */ + yyscanner = scanner_init(str, &yyextra.core_yy_extra, ScanKeywords, NumScanKeywords); +... + /* 初始化 bison parser */ + parser_init(&yyextra); + + /* 解析! */ + yyresult = base_yyparse(yyscanner); + + /* 清理释放内存*/ + scanner_finish(yyscanner); +... + return yyextra.parsetree; + } +/* analyze.cpp */ +... +/* 分析原始语法解析树,做语义分析并输出查询树 */ +Query* parse_analyze( + Node* parseTree, const char* sourceText, Oid* paramTypes, int numParams, bool isFirstNode, bool isCreateView) +{ + /* 初始化解析状态和查询树 */ + ParseState* pstate = make_parsestate(NULL); + Query* query = NULL; +... + /* 将解析树转化为查询树 */ + query = transformTopLevelStmt(pstate, parseTree, isFirstNode, isCreateView); +... + /* 释放解析状态 */ + free_parsestate(pstate); +... + return query; +} +``` + +2. SQL查询分流——traffic cop + +traffic cop模块负责查询的分流,它负责区分简单和复杂的查询指令。事务控制命令(例如BEGIN和ROLLBACK)非常简单,因此不需要其它处理,而其它命令(例如SELECT和JOIN)则传递给重写器(参考第6章)。这种区分通过对简单命令执行最少的优化,并将更多的时间投入到复杂的命令上,从而减少了处理时间。简单和复杂查询指令也对应如下2类解析: + +(1)软解析(简单,旧查询):当openGauss共享缓冲区中存在已提交SQL语句的已解析表示形式时,可以重复利用缓存内容执行语法和语义检查,避免查询优化的相对昂贵的操作。 + +(2)硬解析(复杂,新查询):如果无缓存语句可重用,或者第一次将SQL语句加载到openGauss共享缓冲区中,则会导致硬解析。同样,当一条语句在共享缓冲区中老化时,再次重新加载该语句时,还会导致另一次硬解析。因此,共享Buffer的大小也会影响解析调用的数量。 + +我们可以查询gs_prepared_statements来查看缓存了什么,以区分软/硬解析(它仅对当前会话可见)。此外,gs_buffercache模块提供了一种实时检查共享缓冲区高速缓存内容的方法,它甚至可以分辨出有多少数据块来自磁盘,有多少数据来自共享缓冲区。 + +1) traffic cop(tcop)源码组织 +traffic cop(tcop)源码目录为: + +/src/common/backend/tcop。traffic cop(tcop)源码文件如下所示。 +tcop + +auditfuncs.cpp + +记录数据库操作审计信息 + +autonomous.cpp + +创建可被用来执行SQL查询 + +的自动会话 + +dest.cpp + +与查询结果被发往的终点通信 + +utility.cpp + +数据库通用指令控制函数 + +fastpath.cpp + +在事务期间缓存操作函数和 + +类型等信息 + +postgres.cpp + +后端服务器主程序 + +pquery.cpp + +查询处理指令 + +stmt_retry.cpp + +SQL语句错误错误码并重试 + +2) traffic cop主流程 +traffic cop主流程代码如下。 + +``` +/*postgres.cpp*/ +... +/*原始解析器,输入查询字符串,做词法和语法分析,返回原始解析树列表*/ +int PostgresMain(int argc, char* argv[], const char* dbname, const char* username) +{ +... + /* 整体初始化*/ + t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(dbname, InvalidOid, username); + ... + /* 自动事务的错误处理 */ + if (sigsetjmp(local_sigjmp_buf, 1) != 0) { ... } +... +/* 错误语句的重新尝试阶段 */ +if (IsStmtRetryEnabled() && StmtRetryController->IsQueryRetrying()) +{ ... } + /* 无错误查询指令循环处理*/ + for (;;) { +... +/* 按命令类型执行处理流程*/ +switch(firstchar){ +... + +case: 'Q': ... /* 简单查询 */ +case: 'P': ... /* 解析 */ +case: 'E': ... /* 执行 */ + } +... + } + ... +} +``` + +3. 查询重写——rewriter + 查询重写利用已有语句特征和关系代数运算来生成更高效的等价语句,在数据库优化器中扮演关键角色;尤其在复杂查询中,能够在性能上带来数量级的提升,可谓是“立竿见影”的“黑科技”。SQL语言是丰富多样的,非常的灵活,不同的开发人员依据经验的不同,手写的SQL语句也是各式各样,另外还可以通过工具自动生成。同时SQL语言是一种描述性语言,数据库的使用者只是描述了想要的结果,而不关心数据的具体获取方式,输入数据库的SQL语言很难做到是以最优形式表示的,往往隐含了一些冗余信息,这些信息可以被挖掘用来生成更加高效的SQL语句。查询重写就是把用户输入的SQL语句转换为更高效的等价SQL,查询重写遵循2个基本原则: + +(1)等价性:原语句和重写后的语句,输出结果相同。 + +(2)高效性:重写后的语句,比原语句在执行时间和资源使用上更高效。 + +介绍openGauss如下几个关键的查询重写技术: + +(1)常量表达式化简:常量表达式即用户输入SQL语句中包含运算结果为常量的表达式,分为算数表达式、逻辑运算表达式、函数表达式。查询重写可以对常量表达式预先计算以提升效率。例如“SELECT * FROM table WHERE a=1+1; ”语句被重写为“SELECT * FROM table WHERE a=2”语句。 + +(2)子查询提升:由于子查询表示的结构更清晰,符合人的阅读理解习惯,用户输入的SQL语句往往包含了大量的子查询,但是相关子查询往往需要使用嵌套循环的方法来实现,执行效率较低,因此将子查询优化为“Semi Join”的形式可以在优化规划时选择其它的执行方法,或能提高执行效率。例如“SELECT * FROM t1 WHERE t1.a in (SELECT t2.a FROM t2); ”语句可重写为“SELECT * FROM t1 LEFT SEMI JOIN t2 ON t1.a=t2.a”语句。 + +(3)谓词下推:谓词(Predicate),通常为SQL语句中的条件,例如“SELECT * FROM t1 WHERE t1.a=1; ”语句中的“t1.a=1”即为谓词。等价类(Equivalent-Class)是指等价的属性、实体等对象的集合,例如“WHERE t1.a=t2.a”语句中,t1.a和t2.a互相等价,组成一个等价类{t1.a,t2.a}。利用等价类推理(又称作传递闭包),我们可以生成新的谓词条件,从而达到减小数据量和最大化利用索引的目的。如图2所示,我们举一个形象的例子来说明谓词下推的威力。假设有两个表t1、t2;它们分别包含[1,2,3,..100]共100行数据,那么查询语句“SELECT * FROM t1 JOIN t2 ON t1.a=t2.a WHERE t1.a=1”的逻辑计划在经过查询重写前后的对比。 + + +![image-20230412133834961](https://cdn.jsdelivr.net/gh/15197580192/imag@main/img/image-20230412133834961.png) +|图3 查询重写前后对比| + +查询重写的主要工作在优化器中实现,除此之外,openGauss还提供了基于规则的rewrite接口,用户可以通过创建替换规则的方法对逻辑执行计划进行改写。例如视图展开功能即通过rewrite模块中的规则进行替换,而视图展开的规则是在创建视图的过程中默认创建的。 + +1) rewriter源码组织 +rewriter源码目录为: +/src/gausskernel/optimizer/rewrite。rewriter源码文件如表5所示。 + +表5 rewriter源码文件 +rewrite + +rewriteDefine.cpp + +定义重写规则 + +rewriteHandler.cpp + +重写主模块 + +rewriteManip.cpp + +重写操作函数 + +rewriteRemove.cpp + +重写规则移除函数 + +rewriteRlsPolicy.cpp + +重写行粒度安全策略 + +rewriteSupport.cpp + +重写辅助函数 + +2) rewriter主流程 +rewriter主流程代码如下。 + +``` +/* rewrite.cpp */ +... +/* 查询重写主函数 */ +List* QueryRewrite(Query* parsetree) +{ +... + /* 应用所有non-SELECT规则获取改写查询列表 */ + querylist = RewriteQuery(parsetree, NIL); + /* 对每个改写查询应用RIR规则 */ + results = NIL; + foreach (l, querylist) { + Query* query = (Query*)lfirst(l); + query = fireRIRrules(query, NIL, false); + query->queryId = input_query_id; + results = lappend(results, query); + } + /* 从重写列表确定一个重写结果 */ + origCmdType = parsetree->commandType; + foundOriginalQuery = false; + lastInstead = NULL; + foreach (l, results) {...} + ... + return results; +} +``` + +4. 查询优化——optimizer + 优化器(optimizer)的任务是创建最佳执行计划。一个给定的SQL查询(以及一个查询树)实际上可以以多种不同的方式执行,每种方式都会产生相同的结果集。如果在计算上可行,则查询优化器将检查这些可能的执行计划中的每一个,最终选择预期运行速度最快的执行计划。 + +在某些情况下,检查执行查询的每种可能方式都会占用大量时间和内存空间,特别是在执行涉及大量连接操作(Join)的查询时。为了在合理的时间内确定合理的(不一定是最佳的)查询计划,当查询连接数超过阈值时,openGauss使用遗传查询优化器(genetic query optimizer),通过遗传算法来做执行计划的枚举。 + +优化器的查询计划(plan)搜索过程实际上与称为路径(path)的数据结构一起使用,该路径只是计划的简化表示,其中仅包含确定计划所需的关键信息。确定代价最低的路径后,将构建完整的计划树以传递给执行器。这足够详细地表示了所需的执行计划,供执行者运行。在本节的其余部分,我们将忽略路径和计划之间的区别。 + +1) 生成查询计划 +首先,优化器会生成查询中使用的每个单独关系(表)的计划。候选计划由每个关系上的可用索引确定。对关系的顺序扫描是最基本的方法,因此总是会创建顺序扫描计划。假设在关系上定义了索引(例如B树索引),并且查询属性恰好与B树索引的键匹配,则使用B树索引创建另一个基于索引的查询计划。如果还存在其它索引并且查询中的限制恰好与索引的关键字匹配,则将考虑其它计划生成更多计划。 + +其次,如果查询需要连接两个或多个关系,则在找到所有可行的扫描单个关系的计划之后,将考虑连接关系的计划。连接关系有3种可用的连接策略: + +(1)嵌套循环连接:对在左关系中找到的每一行,都会扫描一次右关系。此策略易于实施,但非常耗时。(但是,如果可以使用索引扫描来扫描右关系,则这可能是一个不错的策略。可以将左关系的当前行中的值用作右索引扫描的键。) + +(2)合并连接:在开始连接之前,对每个关系对连接属性进行排序。然后,并行扫描这两个关系,并组合匹配的行以形成连接行。这种连接更具吸引力,因为每个关系只需扫描一次。所需的排序可以通过明确的排序步骤来实现,也可以通过使用连接键上的索引以正确的顺序扫描关系来实现。 + +(3)哈希连接:首先将正确的关系扫描并使用其连接属性作为哈希键加载到哈希表中。接下来,扫描左关系,并将找到的每一行的适当值用作哈希键,以在表中找到匹配的行。 + +当查询涉及2个以上的关系时,最终结果必须由构建连接树来确定。优化器检查不同的可能连接顺序以找到代价最低的连接顺序。 + +如果查询所使用的关系数目较少(少于启动启发式搜索阈值),那么将进行近乎穷举的搜索以找到最佳连接顺序。优化器优先考虑存在WHERE限定条件子句中的2个关系之间的连接(即存在诸如rel1.attr1 = rel2.attr2之类的限制),最后才考虑不具有Join子句的连接对。优化器会对每个连接操作生成所有可能的执行计划,然后选择(估计)代价最低的那个。当连接表数目超过geqo_threshold时,所考虑的连接顺序由geqo启发式方法确定。 + +完成的计划树包括对基础关系的顺序或索引扫描,以及根据需要的嵌套循环、合并、哈希连接节点和其它辅助步骤,例如排序节点或聚合函数计算节点。这些计划节点类型中的大多数具有执行选择(丢弃不满足指定布尔条件的行)和投影(基于给定列值计算派生列集,即执行标量表达式的运算)的附加功能。优化器的职责之一是将WHERE子句中的选择条件附加起来,并将所需的输出表达式安排到计划树的最适当节点上。 + +2) 查询计划代价估计 +openGauss的优化器是基于代价的优化器,对每条SQL语句生成的多个候选的计划,优化器会计算一个执行代价,最后选择代价最小的计划。 + +通过统计信息,代价估算系统就可以了解一个表有多少行数据、用了多少个数据页面、某个值出现的频率等,以确定约束条件过滤出的数据占总数据量的比例,即选择率。当一个约束条件确定了选择率之后,就可以确定每个计划路径所需要处理的行数,并根据行数可以推算出所需要处理的页面数。当计划路径处理页面的时候,会产生IO代价。而当计划路径处理元组的时候(例如针对元组做表达式计算),会产生CPU代价。由于openGauss是单机数据库,无服务器节点间传输数据(元组)会产生通信的代价,因此一个计划的总体代价可以表示为: + +总代价 = IO代价 + CPU代价 + +openGauss把所有顺序扫描一个页面的代价定义为单位1,所有其它算子的代价都归一化到这个单位1上。比如把随机扫描一个页面的代价定义为4,即认为随机扫描一个页面所需代价是顺序扫描一个页面所需代价的4倍。又比如,把CPU处理一条元组的代价为0.01,即认为CPU处理一条元组所需代价为顺序扫描一个页面所需代价的百分之一。 + +从另一个角度来看,openGauss将代价又分成了启动代价和执行代价,其中: + +总代价 = 启动代价 + 执行代价 + +(1)启动代价 +从SQL语句开始执行,到此算子输出第一条元组为止,所需要的代价,称为启动代价。有的算子启动代价很小,比如基表上的扫描算子,一旦开始读取数据页,就可以输出元组,因此启动代价为0。有的算子的启动代价相对较大,比如排序算子,它需要把所有下层算子的输出全部读取到,并且把这些元组排序之后,才能输出第一条元组,因此它的启动代价比较大。 + +(2)执行代价 +从输出第一条算子开始,至查询结束,所需要的代价,称为执行代价。这个代价中又可以包含CPU代价、IO代价,执行代价的大小与算子需要处理的数据量有关,也与每个算子完成的功能有关。处理的数据量越大、算子需要完成的任务越重,则执行代价越大。 + +(3)总代价 +如图3所示示例,查询中包含两张表,分别命名为t1、t2。t1与t2进行join,并且对c1列做聚集。 + + +![image-20230412133918433](https://cdn.jsdelivr.net/gh/15197580192/imag@main/img/image-20230412133918433.png) +|图4 代价计算示例| + +示例中涉及的代价包括: + +(1)扫描t1的启动代价为0,总代价为13.13。13.13的意思是“总代价相当于顺序扫描13.13个页面”,t2表的扫描同理。 +(2)此计划的Join方式为Hash Join,使用Hash Join时,必须先对一个子节点的所有数据建立Hash表,再依次使用另一个子节点的每一条元组尝试与Hash Join中的元组进行匹配。因此Hash Join的启动代价包括了建立Hash表的代价。 +此计划中Hash Join的启动代价为13.29,对某个结果集建立Hash表时,必须拿到此结果集的所有数据,因此13.29比下层扫描算子的代价13.13大。 + +此计划中Hash Join的总代价为28.64。 + +(3)Join完毕之后,需要做聚集运算,此计划中的聚集使用了HashAGG算子,此算子需要对Join的结果集上以c1列作为Hash Key建立Hash表,因此它的启动代价又包含了一个建立Hash表的代价。聚集操作的启动代价为28.69,总代价为28.79。 +3) optimizer源码组织 +optimizer源码目录为: +/src/gausskernel/optimizer。optimizer源码文件如表6所示。 + +表6 optimizer源码文件 + +plan + +analyzejoins.cpp + +初始化查询后的连接简化 + +createplan.cpp + +创建查询计划 + +dynsmp_single.cpp + +SMP自适应接口函数 + +planner.cpp + +查询优化外部接口 + +planrecursive_single.cpp + +查询优化外部接口 + +planrewrite.cpp + +基于代价的查询重写 + +setrefs.cpp + +完成的查询计划树的后处理 + +(修复子计划变量引用) + +initsplan.cpp + +目标列表、限定条件和 + +连接信息初始化 + +pgxcplan_single.cpp + +简单查询的旁路执行器 + +planagg.cpp + +聚集查询的特殊计划 + +planmain.cpp + +计划主函数:单个查询的计划 + +streamplan_single.cpp + +流计划相关函数 + +subselect.cpp + +子选择和参数的计划函数 + +path + +allpaths.cpp + +查找所有可能查询执行路径 + +clausesel.cpp + +子句选择性计算 + +costsize.cpp + +计算关系和路径代价 + +pathkeys.cpp + +匹配并建立路径键的公用函数 + +pgxcpath_single.cpp + +查找关系和代价的所有可能远程查询路径 + +streampath_single.cpp + +并行处理的路径生成 + +tidpath.cpp + +确定扫描关系TID条件并创建对应TID路径 + +equivclass.cpp + +管理等价类 + +indxpath.cpp + +确定可使用索引并创建对应路径 + +joinpath.cpp + +查找执行一组join操作的所有路径 + +joinrels.cpp + +确定需要被连接的关系 + +orindxpath.cpp + +查找匹配OR子句集的索引路径 + + 4) optimizer主流程 + +optimizer主流程代码如下。 + +``` +/* planmain.cpp */ +... +/* +*优化器主函数 +*生成基本查询的路径(最简化的查询计划) +*输入参数: +*root:描述需要计划的查询 +*tlist: 查询生成的目标列表 +*tuple_fraction: 被抽取的元组数量比例 +*limit_tuples: 抽取元组数量的数量限制 +*输出参数: +*cheapest_path: 查询整体上代价最低的路径 +*sorted_path: 排好序的代价最低的数个路径 +*num_groups: 估计组的数量(返回1如果查询不使用group运算) +*/ + +void query_planner(PlannerInfo* root, List* tlist, double tuple_fraction, double limit_tuples,query_pathkeys_callback qp_callback, void *qp_extra, Path** cheapest_path, Path** sorted_path, double* num_groups, List* rollup_groupclauses, List* rollup_lists){ +... +/* 空连接树简单query 快速处理 */ +if (parse->jointree->fromlist == NIL) { +... +return; +} +setup_simple_rel_arrays(root); /* 获取线性版的范围表,加速读取 */ +/* 为基础关系建立RelOptInfo节点 */ +add_base_rels_to_query(root, (Node*)parse->jointree); +check_scan_hint_validity(root); +/* 向目标列表添加条目,占位符信息生成,最后形成连接列表 */ + build_base_rel_tlists(root, tlist); +find_placeholders_in_jointree(root); + joinlist = deconstruct_jointree(root); +reconsider_outer_join_clauses(root); /* 基于等价类重新考虑外连接*/ +/* 对等价类生成额外的限制性子句 */ + generate_base_implied_equalities(root); + generate_base_implied_qualities(root); +(*qp_callback) (root, qp_extra); /* 将完整合并的等价类集合转化为标准型 */ +fix_placeholder_input_needed_levels(root); /* 检查占位符表达式 */ +joinlist = remove_useless_joins(root, joinlist); /* 移除无用外连接 */ +add_placeholders_to_base_rels(root); /* 将占位符添加到基础关系 */ +/* 对每个参与查询表的大小进行估计,计算total_table_pages */ +total_pages = 0; + for (rti = 1; rti < (unsigned int)root->simple_rel_array_size; rti++) +{...} +root->total_table_pages = total_pages; +/* 准备开始主查询计划 */ +final_rel = make_one_rel(root, joinlist); + final_rel->consider_parallel = consider_parallel; +... + /* 如果有分组子句,估计结果组数量 */ +if (parse->groupClause) {...} /* 如果有分组子句,估计结果组数量 */ +else if (parse->hasAggs||root->hasHavingQual||parse->groupingSets) +{...} /* 非分组聚集查询读取所有元组 */ +else if (parse->distinctClause) {...} /* 非分组非聚集独立子句估计结果行数 */ +else {...} /* 平凡非分组非聚集查询,计算绝对的元组比例 */ +/* 计算代价整体最小路径和预排序的代价最小路径 */ +cheapestpath = get_cheapest_path(root, final_rel, num_groups, has_groupby); +... +*cheapest_path = cheapestpath; + *sorted_path = sortedpath; +} +``` + +5. 查询执行——executor + 执行器(executor)采用优化器创建的计划,并对其进行递归处理以提取所需的行的集合。这本质上是一种需求驱动的流水线执行机制。即每次调用一个计划节点时,它都必须再传送一行,或者报告已完成传送所有行。 + + +![image-20230412134012928](https://cdn.jsdelivr.net/gh/15197580192/imag@main/img/image-20230412134012928.png) +|图5 执行计划树示例| + +如图5所示的执行计划树示例,顶部节点是Merge Join节点。在进行任何合并操作之前,必须获取2个元组(MergeJoin节点的2个子计划各返回1个元组)。因此,执行器以递归方式调用自身以处理其子计划(如从左子树的子计划开始)。Merge Join由于要做归并操作,因此它要子计划按序返回元组,从图5可以看出,它的子计划是一个Sort节点。Sort的子节点可能是Seq Scan节点,代表对表的实际读取。执行SeqScan节点会使执行程序从表中获取一行并将其返回到调用节点。Sort节点将反复调用其子节点以获得所有要排序的行。当输入完毕时(如子节点返回NULL而不是新行),Sort算子对获取的元组进行排序,它每次返回1个元组,即已排序的第1行。然后不断排序并向父节点传递剩余的排好序的元组。 + +Merge Join节点类似地需要获得其右侧子计划中的第1个元组,看是否可以合并。如果是,它将向其调用方返回1个连接行。在下1次调用时,或者如果它不能连接当前输入对,则立即前进到1个表或另1个表的下1行(取决于比较的结果),然后再次检查是否匹配。最终,1个或另1个子计划用尽,并且Merge Join节点返回NULL,以指示无法再形成更多的连接行。 + +复杂的查询可能涉及多个级别的计划节点,但是一般方法是相同的:每个节点都会在每次调用时计算并返回其下1个输出行。每个节点还负责执行优化器分配给它的任何选择或投影表达式。 + +执行器机制用于执行所有4种基本SQL查询类型:SELECT、INSERT、UPDATE和DELETE。 + +(1)对于SELECT,顶级执行程序代码仅需要将查询计划树返回的每一行发送给客户端。 +(2)对于INSERT,每个返回的行都插入到为INSERT指定的目标表中。这是在称为ModifyTable的特殊顶层计划节点中完成的。(1个简单的“INSERT ... VALUES”命令创建了1个简单的计划树,该树由单个Result节点组成,该节点仅计算一个结果行,并传递给ModifyTable树节点实现插入)。 +(3)对于UPDATE,优化器对每个计算的更新行附着所更新的列值,以及原始目标行的TID(元组ID或行ID);此数据被馈送到ModifyTable节点,并使用该信息来创建新的更新行并标记旧行已删除。 +(4)对于DELETE,计划实际返回的唯一列是TID,而ModifyTable节点仅使用TID访问每个目标行并将其标记为已删除。 +执行器的主要处理控制流程如下: + +(1)创建查询描述。 +(2)查询初始化:创建执行器状态(查询执行上下文)、执行节点初始化(创建表达式与每个元组上下文、执行表达式初始化)。 +(3)查询执行:执行处理节点(递归调用查询上下文、执行表达式,然后释放内存,重复操作)。 +(4)查询完成;执行未完成的表格修改节点。 +(5)查询结束:递归释放资源、释放查询及其子节点上下文。 +(6)释放查询描述。 +1) executor源码组织 +executor源码目录为: +/src/gausskernel/runtime/executor。executor源码文件如表7所示。 + +表7 executor源码文件 + +executor + +execAmi.cpp + +各种执行器访问方法 + +execClusterResize.cpp + +集群大小调整 + +execCurrent.cpp + +支持WHERE CURRENT OF + +execGrouping.cpp + +支持分组、哈希和聚集操作 + +execJunk.cpp + +伪列的支持 + +execMain.cpp + +顶层执行器接口 + +execMerge.cpp + +处理MERGE指令 + +execParallel.cpp + +支持并行执行 + +execProcnode.cpp + +分发函数按节点调用相关 + +初始化等函数 + +execQual.cpp + +评估资质和目标列表表达式 + +execScan.cpp + +通用的关系扫描 + +execTuples.cpp + +元组相关的资源管理 + +execUtils.cpp + +多种执行相关工具函数 + +functions.cpp + +执行SQL语言函数 + +instrument.cpp + +计划执行工具 + +lightProxy.cpp + +轻量级执行代理 + +node*.cpp + +处理*相关节点操作的函数 + +opfusion.cpp + +opfusion_util.cpp + +opfusion_scan.cpp + +旁路执行器:处理简单查询 + +spi.cpp + +服务器编程接口 + +tqueue.cpp + +并行后端之间的元组信息传输 + +tstoreReceiver.cpp + +存储结果元组 + + 2)executor主流程 +executor主流程代码为。 + +``` +/* execMain.cpp */ +... +/* 执行器启动 */ +void ExecutorStart(QueryDesc *queryDesc, int eflags) +{ + gstrace_entry(GS_TRC_ID_ExecutorStart); + if (ExecutorStart_hook) { + (*ExecutorStart_hook)(queryDesc, eflags); + } else { + standard_ExecutorStart(queryDesc, eflags); + } + gstrace_exit(GS_TRC_ID_ExecutorStart); +} +/* 执行器运行 */ +void ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, long count) +{ +... + /* SQL 自调优: 查询执行完毕时,基于运行时信息分析查询计划问题 */ + if (u_sess->exec_cxt.need_track_resource && queryDesc != NULL && has_track_operator &&(IS_PGXC_COORDINATOR || IS_SINGLE_NODE)) { + List *issue_results = PlanAnalyzerOperator(queryDesc, queryDesc->planstate); + /* 如果查询问题找到,存在系统视图 gs_wlm_session_history */ + if (issue_results != NIL) { + RecordQueryPlanIssues(issue_results); + } + } + /* 查询动态特征, 操作符历史统计信息 */ + if (can_operator_history_statistics) { + u_sess->instr_cxt.can_record_to_table = true; + ExplainNodeFinish(queryDesc->planstate, queryDesc->plannedstmt, GetCurrentTimestamp(), false); + ... + } +} +/* 执行器完成 */ +void ExecutorFinish(QueryDesc *queryDesc) +{ + if (ExecutorFinish_hook) { + (*ExecutorFinish_hook)(queryDesc); + } else { + standard_ExecutorFinish(queryDesc); + } +} +/* 执行器结束 */ +void ExecutorEnd(QueryDesc *queryDesc) +{ + if (ExecutorEnd_hook) { + (*ExecutorEnd_hook)(queryDesc); + } else { + standard_ExecutorEnd(queryDesc); + } +} +``` + +(三)存储引擎 +openGauss存储引擎是可插拔、自组装的,支持多个存储引擎来满足不同场景的业务诉求,目前支持行存储引擎、列存储引擎和内存引擎。 + +早期计算机程序通过文件系统管理数据,到了20世纪60年代这种方式就开始不能满足数据管理要求了,用户逐渐对数据并发写入的完整性、高效检索提出更高的要求。由于机械磁盘的随机读写性能问题,从20世纪80年代开始,大多数数据库一直在围绕着减少随机读写磁盘进行设计。主要思路是把对数据页面的随机写盘转化为对WAL(Write Ahead Log,预写式日志)日志的顺序写盘,WAL日志持久化完成,事务就算提交成功,数据页面异步刷盘。但是随着内存容量变大、保电内存、非易失性内存的发展,以及SSD技术逐渐的成熟,IO性能极大提高,经历了几十年发展的存储引擎需要调整架构来发挥SSD的性能和充分利用大内存计算的优势。随着互联网、移动互联网的发展,数据量剧增,业务场景多样化,一套固定不变的存储引擎不可能满足所有应用场景的诉求。因此现在的DBMS需要设计支持多种存储引擎,根据业务场景来选择合适的存储模型。 + +1. 数据库存储引擎要解决的问题 + (1)存储的数据必须要保证ACID:原子性、一致性、隔离性、持久性。 + (2)高并发读写,高性能。 + (3)数据高效存储和检索能力。 + +2. openGauss存储引擎概述 + +openGauss整个系统设计支持多个存储引擎来满足不同场景的业务诉求。当前openGauss存储引擎有以下3种: + +(1)行存储引擎。主要面向OLTP(Online Transaction Processing,在线交易处理)场景设计,例如订货发货,银行交易系统。 +(2)列存储引擎。主要面向OLAP(Online Analytical Processing,联机分析处理)场景设计,例如数据统计报表分析。 +(3)内存引擎。主要面向极致性能场景设计,例如银行风控场景。 + +创建表的时候可以指定行存储引擎、列存引擎的表、内存引擎的表,支持一个事务里包含对3种引擎表的DML(Data Manipulation Language,数据操作语言)操作,可以保证事务ACID性质。 + +1) storage源码组织 +storage源码目录为: +/src/gausskernel/storage。storage源码文件如表1所示。 + +表1 storage源码文件 + +storage + +access + +基础行存储引擎方法 + +cbtree + +hash + +heap + +index + +... + +buffer + +缓冲区 + +freespace + +空闲空间管理 + +ipc + +进程内交互 + +large_object + +大对象处理 + +remote + +远程读 + +replication + +复制备份 + +smgr + +存储管理 + +cmgr + +公共缓存方法 + +cstore + +列存储引擎 + +dfs + +分布式文件系统 + +file + +文件类 + +lmgr + +锁管理 + +mot + +内存引擎 + +page + +数据页 + + 2) storage主流程 + +storage主流程代码如下。 + +``` +/* smgr/smgr.cpp, 存储管理 */.../* 文件管理函数列表,包含磁盘初始化、开关、同步等操作函数 */static const f_smgr g_smgrsw[] = { /* 磁盘*/ {mdinit, NULL, mdclose, mdcreate, mdexists, mdunlink, mdextend, mdprefetch, mdread, mdwrite, mdwriteback, mdnblocks, mdtruncate, mdimmedsync, mdpreckpt, mdsync, mdpostckpt, mdasyncread, mdasyncwrite}};/* * 存储管理初始化 * 当服务器后端启动时调用 */void smgrinit(void){ int i;/* 初始化所有存储相关管理器 */ for (i = 0; i < SMGRSW_LENGTH; i++) { if (g_smgrsw[i].smgr_init) { (*(g_smgrsw[i].smgr_init))(); } } /* 登记存储管理终止程序 */ if (!IS_THREAD_POOL_SESSION) { on_proc_exit(smgrshutdown, 0); }}/* * 当后端服务关闭时,执行存储管理关闭代码 */static void smgrshutdown(int code, Datum arg){ int i;/* 关闭所有存储关联服务 */ for (i = 0; i < SMGRSW_LENGTH; i++) { if (g_smgrsw[i].smgr_shutdown) { (*(g_smgrsw[i].smgr_shutdown))(); } }} +``` + +3. 行存储引擎 + openGauss的行存储引擎设计上支持MVCC(Multi-Version Concurrency Control,多版本并发控制),采用集中式垃圾版本回收机制,可以提供OLTP业务系统的高并发读写要求。支持存储计算分离架构,存储层异步回放日志。如图1所示。 + ![image-20230412134603694](https://cdn.jsdelivr.net/gh/15197580192/imag@main/img/image-20230412134603694.png) + |图1 行存储架构| + +行存储引擎的关键技术有: + +(1)基于CSN(Commit Sequence Number,待提交事务的序列号,它是一个64位递增无符号数)的MVCC并发控制机制,集中式垃圾数据清理。 + +(2)并行刷日志,并行恢复。传统数据库一般都采用串行刷日志的设计,因为日志有顺序依赖关系,例如一个是事务产生的redo/undo log是有前后依赖关系的。openGauss的日志系统采用多个logwriter线程并行写的机制,充分发挥SSD的多通道IO能力。 +(3)基于大内存设计的Buffer manager。 + +行存储buffer主流程代码如下。 + +``` +/* buffer/bufmgr.cpp, 基础行存储管理 */ +... +/* 查找或创建一个缓冲区 */ +Buffer ReadBufferExtended( + Relation reln, ForkNumber fork_num, BlockNumber block_num, ReadBufferMode mode, BufferAccessStrategy strategy) +{ + bool hit = false; + Buffer buf; + + if (block_num == P_NEW) { + STORAGE_SPACE_OPERATION(reln, BLCKSZ); + } + + /* 以smgr(存储管理器)级别 打开一个缓冲区 */ + RelationOpenSmgr(reln); + + /* 拒绝读取非局部临时关系的请求,因为可能会获得监控不到的错误数据 */ + if (RELATION_IS_OTHER_TEMP(reln) && fork_num <= INIT_FORKNUM) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot access temporary tables of other sessions"))); + + /* 读取缓冲区,更新pgstat数量反馈cache命中与否情况 */ + pgstat_count_buffer_read(reln); + pgstatCountBlocksFetched4SessionLevel(); + buf = ReadBuffer_common(reln->rd_smgr, reln->rd_rel->relpersistence, fork_num, block_num, mode, strategy, &hit); + if (hit) { + pgstat_count_buffer_hit(reln); + } + return buf; +} + +/* 释放一个缓冲区 */ +void ReleaseBuffer(Buffer buffer) +{ + BufferDesc* buf_desc = NULL; + PrivateRefCountEntry* ref = NULL; + /* 错误释放处理 */ + if (!BufferIsValid(buffer)) { + ereport(ERROR, (errcode(ERRCODE_INVALID_BUFFER), (errmsg("bad buffer ID: %d", buffer)))); + } + + ResourceOwnerForgetBuffer(t_thrd.utils_cxt.CurrentResourceOwner, buffer); + + if (BufferIsLocal(buffer)) { + Assert(u_sess->storage_cxt.LocalRefCount[-buffer - 1] > 0); + u_sess->storage_cxt.LocalRefCount[-buffer - 1]--; + return; + } + /* 释放当前缓冲区 */ + buf_desc = GetBufferDescriptor(buffer - 1); + + PrivateRefCountEntry *free_entry = NULL; + ref = GetPrivateRefCountEntryFast(buffer, free_entry); + if (ref == NULL) { + ref = GetPrivateRefCountEntrySlow(buffer, false, false, free_entry);} + Assert(ref != NULL); + Assert(ref->refcount > 0); + + if (ref->refcount > 1) { + ref->refcount--; + } else { + UnpinBuffer(buf_desc, false); + } +} +/* 标记写脏缓冲区 */ +void MarkBufferDirty(Buffer buffer) +{ + BufferDesc* buf_desc = NULL; + uint32 buf_state; + uint32 old_buf_state; + + if (!BufferIsValid(buffer)) { + ereport(ERROR, (errcode(ERRCODE_INVALID_BUFFER), (errmsg("bad buffer ID: %d", buffer))));} + + if (BufferIsLocal(buffer)) { + MarkLocalBufferDirty(buffer); + return; + } + + buf_desc = GetBufferDescriptor(buffer - 1); + + Assert(BufferIsPinned(buffer)); + /* unfortunately we can't check if the lock is held exclusively */ + Assert(LWLockHeldByMe(buf_desc->content_lock)); + + old_buf_state = LockBufHdr(buf_desc); + + buf_state = old_buf_state | (BM_DIRTY | BM_JUST_DIRTIED); + + /* 将未入队的脏页入队 */ + if (g_instance.attr.attr_storage.enableIncrementalCheckpoint) { + for (;;) { + buf_state = old_buf_state | (BM_DIRTY | BM_JUST_DIRTIED); + if (!XLogRecPtrIsInvalid(pg_atomic_read_u64(&buf_desc->rec_lsn))) { + break; + } + + if (!is_dirty_page_queue_full(buf_desc) && push_pending_flush_queue(buffer)) { + break; + } + + UnlockBufHdr(buf_desc, old_buf_state); + pg_usleep(TEN_MICROSECOND); + old_buf_state = LockBufHdr(buf_desc); + } + } + + UnlockBufHdr(buf_desc, buf_state); + + /* 如果缓冲区不是“脏”状态,则更新相关计数 */ + if (!(old_buf_state & BM_DIRTY)) { + t_thrd.vacuum_cxt.VacuumPageDirty++; + u_sess->instr_cxt.pg_buffer_usage->shared_blks_dirtied++; + + pgstatCountSharedBlocksDirtied4SessionLevel(); + + if (t_thrd.vacuum_cxt.VacuumCostActive) { + t_thrd.vacuum_cxt.VacuumCostBalance += u_sess->attr.attr_storage.VacuumCostPageDirty; + } + } +} +``` + +4. 列存储引擎 + +传统行存储数据压缩率低,必须按行读取,即使读取一列也必须读取整行。openGauss创建表的时候,可以指定行存储还是列存储。列存储表也支持DML操作,也支持MVCC。列存储架构如图2所示。 + +![image-20230412134714202](https://cdn.jsdelivr.net/gh/15197580192/imag@main/img/image-20230412134714202.png) +|图2 列存储架构| + +列存储引擎有以下优势: +(1)列的数据特征比较相似,适合压缩,压缩比很高。 +(2)当表列的个数比较多,但是访问的列个数比较少时,列存可以按需读取列数据,大大减少不必要的读IO,提高查询性能。 +(3)基于列批量数据Vector(向量)的运算,CPU的cache命中率比较高,性能比较好。列存储引擎更适合OLAP大数据统计分析的场景。 + +1) 列存储源码组织 +列存储源码目录为: +/src/gausskernel/storage/cstore。列存储源码文件如表2所示。 + +表2 列存储源码文件 + +cstore + +compression + +数据压缩与解压 + +cstore_allocspace + +空间分配 + +cstore_am + +列存储公共API + +cstore_***_func + +支持函数 + +cstore_psort + +列内排序 + +cu + +数据压缩单元 + +cucache_mgr + +缓存管理器 + +custorage + +持久化存储 + +cstore_delete + +删除方法 + +cstore_update + +更新方法 + +cstore_vector + +缓冲区实现 + +cstore_rewrite + +SQL重写 + +cstore_insert + +插入方法 + +cstore_mem_alloc + +内存分配 + + 2) 列存储主要API + +``` +/* cstore_am.cpp */ +... +/* 扫描 APIs */ + void InitScan(CStoreScanState *state, Snapshot snapshot = NULL); + void InitReScan(); + void InitPartReScan(Relation rel); + bool IsEndScan() const; + + /* 晚读 APIs */ + bool IsLateRead(int id) const; + void ResetLateRead(); + + /* 更新列存储扫描计时标记*/ + void SetTiming(CStoreScanState *state); + + /* 列存储扫描*/ + void ScanByTids(_in_ CStoreIndexScanState *state, _in_ VectorBatch *idxOut, _out_ VectorBatch *vbout); + void CStoreScanWithCU(_in_ CStoreScanState *state, BatchCUData *tmpCUData, _in_ bool isVerify = false); + + /* 加载数据压缩单元描述信息 */ + bool LoadCUDesc(_in_ int col, __inout LoadCUDescCtl *loadInfoPtr, _in_ bool prefetch_control, _in_ Snapshot snapShot = NULL); + + /* 从描述表中获取数据压缩单元描述*/ + bool GetCUDesc(_in_ int col, _in_ uint32 cuid, _out_ CUDesc *cuDescPtr, _in_ Snapshot snapShot = NULL); + + /* 获取元组删除信息*/ + void GetCUDeleteMaskIfNeed(_in_ uint32 cuid, _in_ Snapshot snapShot); + + bool GetCURowCount(_in_ int col, __inout LoadCUDescCtl *loadCUDescInfoPtr, _in_ Snapshot snapShot); + /* 获取实时行号。*/ + int64 GetLivedRowNumbers(int64 *deadrows); + + /* 获得数据压缩单元*/ + CU *GetCUData(_in_ CUDesc *cuDescPtr, _in_ int colIdx, _in_ int valSize, _out_ int &slotId); + + CU *GetUnCompressCUData(Relation rel, int col, uint32 cuid, _out_ int &slotId, ForkNumber forkNum = MAIN_FORKNUM, + bool enterCache = true) const; + + /* 缓冲向量填充 APIs */ + int FillVecBatch(_out_ VectorBatch *vecBatchOut); + + /* 填充列向量*/ + template + int FillVector(_in_ int colIdx, _in_ CUDesc *cu_desc_ptr, _out_ ScalarVector *vec); + + template + void FillVectorByTids(_in_ int colIdx, _in_ ScalarVector *tids, _out_ ScalarVector *vec); + + template + void FillVectorLateRead(_in_ int seq, _in_ ScalarVector *tids, _in_ CUDesc *cuDescPtr, _out_ ScalarVector *vec); + + void FillVectorByIndex(_in_ int colIdx, _in_ ScalarVector *tids, _in_ ScalarVector *srcVec, _out_ ScalarVector *destVec); + + /* 填充系统列*/ + int FillSysColVector(_in_ int colIdx, _in_ CUDesc *cu_desc_ptr, _out_ ScalarVector *vec); + + template + void FillSysVecByTid(_in_ ScalarVector *tids, _out_ ScalarVector *destVec); + + template + int FillTidForLateRead(_in_ CUDesc *cuDescPtr, _out_ ScalarVector *vec); + + void FillScanBatchLateIfNeed(__inout VectorBatch *vecBatch); + + /* 设置数据压缩单元范围以支持索引扫描 */ + void SetScanRange(); + + /* 判断行是否可用*/ + bool IsDeadRow(uint32 cuid, uint32 row) const; + + void CUListPrefetch(); + void CUPrefetch(CUDesc *cudesc, int col, AioDispatchCUDesc_t **dList, int &count, File *vfdList); + + /* 扫描函数 */ + typedef void (CStore::*ScanFuncPtr)(_in_ CStoreScanState *state, _out_ VectorBatch *vecBatchOut); + void RunScan(_in_ CStoreScanState *state, _out_ VectorBatch *vecBatchOut); + int GetLateReadCtid() const; + void IncLoadCuDescCursor(); +``` + +5. 内存引擎 + +openGauss引入了MOT(Memory-Optimized Table,内存优化表)存储引擎,它是一种事务性行存储,针对多核和大内存服务器进行了优化。MOT是openGauss数据库出色的生产级特性(Beta版本),它为事务性工作负载提供更高的性能。MOT完全支持ACID特性,并包括严格的持久性和高可用性支持。企业可以在关键任务、性能敏感的在线事务处理(OLTP)中使用MOT,以实现高性能、高吞吐、可预测低延迟以及多核服务器的高利用率。MOT尤其适合在多路和多核处理器的现代服务器上运行,例如基于ARM(Advanced RISC Machine,高级精简指令集计算机器)/鲲鹏处理器的华为TaiShan服务器,以及基于x86的戴尔或类似服务器。MOT存储引擎如图3所示。 + + +![image-20230412134749794](https://cdn.jsdelivr.net/gh/15197580192/imag@main/img/image-20230412134749794.png) +|图3 openGauss内存引擎| + +MOT与基于磁盘的普通表并排创建。MOT的有效设计实现了几乎完全的SQL覆盖,并且支持完整的数据库功能集,如存储过程和自定义函数。通过完全存储在内存中的数据和索引、非统一内存访问感知(NUMA-aware)设计、消除锁和锁存争用的算法以及查询原生编译,MOT可提供更快的数据访问和更高效的事务执行。MOT有效的几乎无锁的设计和高度调优的实现,使其在多核服务器上实现了卓越的近线性吞吐量扩展。 + +MOT的高性能(查询和事务延迟)、高可扩展性(吞吐量和并发量)等特点,在某些情况下低成本(高资源利用率)方面拥有显著优势。 + +(1)低延迟(Low Latency):提供快速的查询和事务响应时间。 +(2)高吞吐量(High Throughput):支持峰值和持续高用户并发。 +(3)高资源利用率(High Resource Utilization):充分利用硬件。 +MOT的关键技术如下: + +(1)内存优化数据结构:以实现高并发吞吐量和可预测的低延迟为目标,所有数据和索引都在内存中,不使用中间页缓冲区,并使用持续时间最短的锁。数据结构和所有算法都是专门为内存设计而优化的。 + +(2)免锁事务管理:MOT在保证严格一致性和数据完整性的前提下,采用乐观的策略实现高并发和高吞吐。在事务过程中,MOT不会对正在更新的数据行的任何版本加锁,从而大大降低了一些大内存系统中的争用。 + +(3)免锁索引:由于内存表的数据和索引完全存储在内存中,因此拥有一个高效的索引数据结构和算法非常重要。MOT索引机制基于领域前沿的树结构Masstree,一种用于多核系统的快速和可扩展的键值(Key Value,KV)存储索引,以B+树的Trie实现。通过这种方式,高并发工作负载在多核服务器上可以获得卓越的性能。同时MOT应用了各种先进的技术以优化性能,如优化锁方法、高速缓存感知和内存预取。 + +(4)NUMA-aware的内存管理:MOT内存访问的设计支持非统一内存访问(NUMA,Non-Uniform Memory Access)感知。NUMA-aware算法增强了内存中数据布局的性能,使线程访问物理上连接到线程运行的核心的内存。这是由内存控制器处理的,不需要通过使用互连(如英特尔QPI(Quick Path Interconnect,快速路径互连))进行额外的跳转。MOT的智能内存控制模块,为各种内存对象预先分配了内存池,提高了性能、减少了锁、保证了稳定性。 + +(5)高效持久性:日志和检查点是实现磁盘持久化的关键能力,也是ACID的关键要求之一。目前所有的磁盘(包括SSD和NVMe(Non-Volatile Memory express,非易失性高速传输总线))都明显慢于内存,因此持久化是基于内存数据库引擎的瓶颈。作为一个基于内存的存储引擎,MOT的持久化设计必须实现各种各样的算法优化,以确保持久化的同时还能达到设计时的速度和吞吐量目标。 + +(6)高SQL覆盖率和功能集:MOT通过扩展的openGauss外部数据封装(Foreign Data Wrapper,FDW)以及索引,几乎支持完整的SQL范围,包括存储过程、用户定义函数和系统函数调用。 + +(7)使用PREPARE语句的查询原生编译:通过使用PREPARE客户端命令,可以以交互方式执行查询和事务语句。这些命令已被预编译成原生执行格式,也称为Code-Gen或即时(Just-in-Time,JIT)编译。这样可以实现平均30%的性能提升。 + +(8)MOT和openGauss数据库的无缝集成:MOT是一个高性能的面向内存优化的存储引擎,已集成在openGauss软件包中。MOT的主内存引擎和基于磁盘的存储引擎并存,以支持多种应用场景,同时在内部重用数据库辅助服务,如WAL重做日志、复制、检查点和恢复高可用性等。 + +1) 内存引擎源码组织 +内存引擎源码目录为: +/src/gausskernel/storage/mot。内存引擎源码文件如表3所示。 + +表3 内存引擎源码文件 + +mot + +concurrency_control + +并发控制管理 + +infra + +辅助与配置函数 + +memory + +内存数据管理 + +storage + +持久化存储 + +system + +全局控制API + +utils + +日志等通用方法 + +2) 内存引擎主流程 +内存引擎主流程代码如下。 + +``` +/* system/mot_engine.cpp */ +... +/* 创建内存引擎实例 */ +MOTEngine* MOTEngine::CreateInstance( + const char* configFilePath /* = nullptr */, int argc /* = 0 */, char* argv[] /* = nullptr */) +{ + if (m_engine == nullptr) { + if (CreateInstanceNoInit(configFilePath, argc, argv) != nullptr) { + bool result = m_engine->LoadConfig(); + if (!result) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "System Startup", "Failed to load Engine configuration"); + } else { + result = m_engine->Initialize(); + if (!result) { + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "System Startup", "Engine initialization failed"); + } + } + + if (!result) { + DestroyInstance(); + MOT_ASSERT(m_engine == nullptr); + } + } + } + return m_engine; +} +/* 内存引擎初始化 */ +bool MOTEngine::Initialize() +{ + bool result = false; +/* 初始化应用服务,开始后台任务 */ + do { // instead of goto + m_initStack.push(INIT_CORE_SERVICES_PHASE); + result = InitializeCoreServices(); + CHECK_INIT_STATUS(result, "Failed to Initialize core services"); + + m_initStack.push(INIT_APP_SERVICES_PHASE); + result = InitializeAppServices(); + CHECK_INIT_STATUS(result, "Failed to Initialize applicative services"); + + m_initStack.push(START_BG_TASKS_PHASE); + result = StartBackgroundTasks(); + CHECK_INIT_STATUS(result, "Failed to start background tasks"); + } while (0); + + if (result) { + MOT_LOG_INFO("Startup: MOT Engine initialization finished successfully"); + m_initialized = true; + } else { + MOT_LOG_PANIC("Startup: MOT Engine initialization failed!"); + /* 调用方应在失败后调用DestroyInstance() */ + } + + return result; +} +/* 销毁内存引擎实例 */ +void MOTEngine::Destroy() +{ + MOT_LOG_INFO("Shutdown: Shutting down MOT Engine"); + while (!m_initStack.empty()) { + switch (m_initStack.top()) { + case START_BG_TASKS_PHASE: + StopBackgroundTasks(); + break; + + case INIT_APP_SERVICES_PHASE: + DestroyAppServices(); + break; + + case INIT_CORE_SERVICES_PHASE: + DestroyCoreServices(); + break; + + case LOAD_CFG_PHASE: + break; + + case INIT_CFG_PHASE: + DestroyConfiguration(); + break; + + default: + break; + } + m_initStack.pop(); + } + ClearErrorStack(); + MOT_LOG_INFO("Shutdown: MOT Engine shutdown finished"); +} +``` + +## 价值特性 + +openGauss相比其它开源数据库主要有复合应用场景、高性能和高可用等产品特点。 +(一)高性能 + +1. CBO优化器 + openGauss优化器是典型的基于代价的优化(Cost-Based Optimization,简称CBO)。在这种优化器模型下,数据库根据表的元组数、字段宽度、NULL记录比率、唯一值(Distinct Value)、最常见值(Most Common Value, 简称MCV)等表的特征值以及一定的代价计算模型,计算出每一个执行步骤的不同执行方式的输出元组数和执行代价(cost),进而选出整体执行代价最小/首元组返回代价最小的执行方式进行执行。 + CBO优化器能够在众多计划中依据代价选出最高效的执行计划,最大限度的满足客户业务要求。 +2. 行列混合存储 + openGauss支持行存储和列存储2种存储模型,用户可以根据应用场景,建表的时候选择行存储还是列存储表。 + 一般情况下,如果表的字段比较多(大宽表),查询中涉及到的列不很多的情况下,适合列存储。如果表的字段个数比较少,查询大部分字段,那么选择行存储比较好。 + 在大宽表、数据量比较大的场景中,查询经常关注某些列,行存储引擎查询性能比较差。例如气象局的场景,单表有200~800个列,查询经常访问10个列,在类似这样的场景下,向量化执行技术和列存储引擎可以极大的提升性能和减少存储空间。行存表和列存表各有优劣,建议根据实际情况选择。 + +(1)行存表。默认创建表的类型。数据按行进行存储,即一行数据紧挨着存储。行存表支持完整的增删改查。适用于对数据需要经常更新的场景。 +(2)列存表。数据按列进行存储,即一列所有数据紧挨着存储。单列查询IO小,比行存表占用更少的存储空间。适合数据批量插入、更新较少和以查询为主统计分析类的场景。列存表不适合点查询,insert插入单条记录性能差。 + +行存表和列存表的选择原则如下: +(1)更新频繁程度。数据如果频繁更新,选择行存表。 +(2)插入频繁程度。频繁的少量插入,选择行存表。一次插入大批量数据,选择列存表。 +(3)表的列数。表的列数很多,选择列存表。 +(4)查询的列数。如果每次查询时,只涉及了表的少数(<50%总列数)几个列,选择列存表。 +(5)压缩率。列存表比行存表压缩率高。但高压缩率会消耗更多的CPU资源。 + +3. 自适应压缩 + 当前主流数据库通常都会采用数据压缩技术。数据类型不同,适用于它的压缩算法不同。对于相同类型的数据,其数据特征不同,采用不同的压缩算法达到的效果也不相同。自适用压缩正是从数据类型和数据特征出发,采用相应的压缩算法,实现了良好的压缩比、快速的入库性能以及良好的查询性能。 + 数据入库和频繁的海量数据查询是用户的主要应用场景。在数据入库场景中,自适应压缩可以大幅度地减少数据量,成倍提高IO操作效率,将数据簇集存储,从而获得快速的入库性能。当用户进行数据查询时,少量的IO操作和快速的数据解压可以加快数据获取的速率,从而在更短的时间内得到查询结果。例如,支持类手机号字符串的大整数压缩、支持numeric类型的大整数压缩、支持对压缩算法进行不同压缩水平的调整。 +4. 分区 + 在openGauss系统中,数据分区是将实例内部的数据集按照用户指定的策略做进一步拆分的水平分表,将表按照指定范围划分为多个数据互不重叠的部分。 + 对于大多数用户使用场景,分区表和普通表相比具有以下优点: + (1)改善查询性能:对分区对象的查询可以仅搜索自己关心的分区,提高检索效率。 + (2)增强可用性:如果分区表的某个分区出现故障,表在其它分区的数据仍然可用。 + (3)方便维护:如果分区表的某个分区出现故障,需要修复数据,只修复该分区即可。 + (4)均衡I/O:可以把不同的分区映射到不同的磁盘以平衡I/O,改善整个系统性能。 + 目前openGauss数据库支持的分区表为范围分区表、列表分区表、哈希分区表。 + (1)范围分区表:将数据基于范围映射到每一个分区,这个范围是由创建分区表时指定的分区键决定的。这种分区方式是最为常用的。范围分区功能,即根据表的一列或者多列,将要插入表的记录分为若干个范围(这些范围在不同的分区里没有重叠),然后为每个范围创建一个分区,用来存储相应的数据。 + +(2)列表分区表:将数据基于各个分区内包含的键值映射到每一个分区,分区包含的键值在创建分区时指定。列表分区功能,即根据表的一列,将要插入表的记录中出现的键值分为若干个列表(这些列表在不同的分区里没有重叠),然后为每个列表创建一个分区,用来存储相应的数据。 + +(3)哈希分区表:将数据通过哈希映射到每一个分区,每一个分区中存储了具有相同哈希值的记录。哈希分区功能,即根据表的一列,通过内部哈希算法将要插入表的记录划分到对应的分区中。 + +用户在下发“CREATE TABLE”命令时增加PARTITION参数,即表示针对此表应用数据分区功能。 +用户可以在实际使用中根据需要调整建表时的分区键,使每次查询结果尽可能存储在相同或者最少的分区内(称为“分区剪枝”),通过获取连续I/O大幅度提升查询性能。 +实际业务中,时间经常被作为查询对象的过滤条件。因此,用户可考虑选择时间列为分区键,键值范围可根据总数据量、一次查询数据量调整。 + +5. SQL by pass + 在典型的OLTP场景中,简单查询占了很大一部分比例,这种查询的特征是只涉及单表和简单表达式的查询。为了加速这类查询,提出了SQL by pass框架:在parse层对这类查询做简单的模式判别后,进入到特殊的执行路径里,跳过经典的执行器执行框架,包括算子的初始化与执行、表达式与投影等经典框架,直接重写一套简洁的执行路径,并且直接调用存储接口。这样可以大大加速简单查询的执行速度。 +6. 鲲鹏NUMA架构优化 + 鲲鹏NUMA架构优化图如图4所示。 + +![image-20230412134843942](https://cdn.jsdelivr.net/gh/15197580192/imag@main/img/image-20230412134843942.png) +|图4 鲲鹏NUMA架构优化图| + + +openGauss架构优化要点如下: + +(1)openGauss根据鲲鹏处理器的多核NUMA架构特点,进行针对性一系列NUMA架构相关优化。一方面尽量减少跨核内存访问的时延问题,另一方面充分发挥鲲鹏多核算力优势。所提供的关键技术包括重做日志批量插入、热点数据NUMA分布、CLog分区等,大幅提升OLTP系统的处理性能。 +(2)openGauss基于鲲鹏芯片所使用的ARMv8.1架构,利用大规模系统扩展指令集(Large System Extension,简称LSE)实现高效的原子操作,有效提升CPU利用率,从而提升多线程间同步性能、XLog写入性能等。 +(3)openGauss基于鲲鹏芯片提供的更宽的L3缓存cacheline,针对热点数据访问进行优化,有效提高缓存访问命中率,降低Cache缓存一致性维护开销,大幅提升系统整体的数据访问性能。 + +(二)高扩展 +在OLTP领域中,数据库需要处理大量的客户端连接。因此,高并发场景的处理能力是数据库的重要能力之一。 +对于外部连接最简单的处理模式是per-thread-per-connection模式,即来一个用户连接产生一个线程。这个模式好处是架构上处理简单,但是高并发下,由于线程太多,线程切换和数据库轻量级锁区域的冲突过大导致性能急剧下降,使得系统性能(吞吐量)严重下降,无法满足用户性能的SLA(Service-Level Agreement,服务等级协议)。 +因此,需要通过线程资源池化复用的技术来解决该问题。线程池技术的整体设计思想是线程资源池化、并且在不同连接之间复用。系统在启动之后会根据当前核数或者用户配置启动固定一批数量的工作线程,一个工作线程会服务一到多个连接会话,这样把会话和线程进行了解耦。因为工作线程数是固定的,因此在高并发下不会导致线程的频繁切换,而由数据库层来进行会话的调度管理。 +(三)高可用 + +1. 主备机 + 为了保证故障的可恢复,需要将数据写多份,设置主备多个副本,通过日志进行数据同步;可以实现在节点故障、停止后重启等情况下,保证故障之前的数据无丢失,以满足ACID特性。openGauss可以支持一主多备模式,备机接收主机发送过来的WAL日志并进行回访,保证和主机的数据一致;同时在主机发生故障时,备机可以参照升主机制进行升主。备机过多会消耗过量的资源,而备机太少会降低系统的可用性。 + 主备之间可以通过“switchover”操作进行角色切换,主机故障后可以通过“failover”操作对备机进行升主。 + 初始化安装或者备份恢复等场景中,需要根据主机重建备机的数据,此时需要Build(构建)功能,将主机的数据和WAL日志发送到备机。主机故障后重新以备机的角色加入时,也需要Build功能将其数据和日志与新主拉齐。Build包含全量Build和增量Build;全量Build要全部依赖主机数据进行重建,拷贝的数据量比较大,耗时比较长;而增量Build只拷贝差异文件,拷贝的数据量比较小,耗时比较短。一般情况下,优先选择增量Build来进行故障恢复;如果增量Build失败,再继续执行全量Build,直至故障恢复。 + openGauss除了流复制主备双机外,还支持逻辑复制。在逻辑复制中把主库称为源端库,备库称为目标端数据库。源端数据库根据预先指定好的逻辑解析规则对WAL文件进行解析,把DML操作解析成一定的格式的逻辑日志(例如可以解析成标准SQL语句)。源端数据库把逻辑日志发给目标端数据库,目标端数据库收到后进行回放,从而实现数据同步。逻辑复制只有DML操作。逻辑复制可以实现跨版本复制、异构数据库复制、双写数据库复制、表级别复制等。 +2. 逻辑备份 + openGauss提供逻辑备份能力,可以将用户表的数据以通用的text或者用户自定义格式备份到本地磁盘文件,并在同构/异构数据库中恢复该用户表的数据。 +3. 物理备份 + openGauss提供物理备份能力,可以将整个实例的数据以数据库内部格式备份到本地磁盘文件中,并在同构数据库中恢复整个实例的数据。 + 物理备份主要分为全量备份和增量备份,它们的区别如下:全量备份包含备份时刻点上数据库的全量数据,耗时时间长(和数据库数据总量成正比),自身即可恢复出完整的数据库;增量备份只包含从指定时刻点之后的增量修改数据,耗时时间短(和增量数据成正比,和数据总量无关),但是必须要和全量备份数据一起才能恢复出完整的数据库。当前openGauss同时支持全量备份和增量备份。 + 4.恢复到指定时间点(PITR) + 时间点恢复(Point In Time Recovery,PITR)基本原理是通过基础热备 + WAL预写日志 + WAL归档日志进行备份恢复。重放WAL记录的时候可以在任意点停止重放,这样就有一个在任意时间的数据库一致的快照。即可以把数据库恢复到自开始备份以来的任意时刻的状态。openGauss在恢复时可以指定恢复的停止点位置为LSN(Log Sequence Number,日志序列号)、时间、XID(Transaction ID,事务ID)以及用户创建的还原点。 + (四)可维护性 +4. 支持WDR诊断报告 + WDR(Workload Diagnosis Report)基于两次不同时间点系统的性能快照数据,生成这两个时间点之间的性能表现报表,用于诊断数据库内核的性能故障。 + WDR主要依赖两个组件: + (1)SNAPSHOT性能快照:性能快照可以配置成按一定时间间隔从内核采集一定量的性能数据,持久化在用户表空间。任何一个SNAPSHOT(快照)可以作为一个性能基线,其它SNAPSHOT与之比较的结果,可以分析出与基线的性能表现。 + (2)WDR Reporter:报表生成工具基于两个SNAPSHOT,分析系统总体性能表现,并能计算出更多项具体的性能指标在这两个时间段之间的变化量,生成SUMMARY和DETAIL两个不同级别的性能数据。 + WDR报表是长期性能问题最主要的诊断手段。基于SNAPSHOT的性能基线,从多维度做性能分析,能帮助DBA掌握系统负载繁忙程度,各个组件的性能表现,性能瓶颈。SNAPSHOT也是后续性能问题自诊断和自优化建议的重要数据来源。 +5. 慢SQL诊断 + 慢SQL能根据用户提供的执行时间阈值,记录所有超过阈值的执行完毕的作业信息。 + 历史慢SQL提供表和函数两种维度的查询接口,方便用户统计慢SQL指标,对接第三方平台。用户从接口中能查询到作业的执行计划、开始执行时间、结束执行时间、执行查询的语句、行活动、内核时间、CPU时间、执行时间、解析时间、编译时间、查询重写时间、计划生成时间、网络时间、IO时间、网络开销、锁开销等。所有信息都是脱敏的。 + 慢SQL提供给用户对于慢SQL诊断所需的详细信息,用户无需通过复现就能离线诊断特定慢SQL的性能问题。 +6. 支持一键式收集诊断信息 + 提供多种套件用于捕获、收集、分析诊断数据,使问题可以诊断,加速诊断过程。能根据开发和定位人员的需要,从生产环境中将必要的数据库日志、数据库管理日志、堆栈信息等提取出来,定位人员根据获得信息进行问题的定界定位。 + 一键式收集工具可以根据生产环境中问题的不同,从生产环境中获取不同的信息,从而提高问题定位定界的效率。用户可以通过改写配置文件,收集自己想要的信息: + (1)通过操作系统命令收集操作系统相关的信息。 + (2)通过查询系统表或者视图获得数据库系统相关的信息。 + (3)数据库系统运行日志和数据库管理相关的日志。 + (4)数据库系统的配置信息。 + (5)数据库相关进程产生的core文件。 + (6)数据库相关进程的堆栈信息。 + (7)数据库进程产生的trace信息。 + (8)数据库产生的redo日志文件。 + (9)计划复现信息。 + (五) 数据库安全 +7. 访问控制 + 管理用户对数据库的访问控制权限,涵盖数据库系统权限和对象权限。 + openGauss数据库支持基于角色的访问控制机制,将角色和权限关联起来,通过将权限赋予给对应的角色,再将角色授予给用户,可实现用户访问控制权限管理。其中登录访问控制通过用户标识和认证技术来共同实现,而对象访问控制则基于用户在对象上的权限,通过对象权限检查实现对象访问控制。用户在为相关的数据库用户分配完成任务所需要的最小权限从而将数据库使用风险降到最低。 + openGauss数据库支持三权分立权限访问控制模型,数据库角色可分为系统管理员、安全管理员和审计管理员。其中安全管理员负责创建和管理用户,系统管理员负责授予和撤销用户权限,审计管理员负责审计所有用户的行为。 + 默认情况下,使用基于角色的访问控制模型。客户可通过设置参数来选择是否开启三权分立控制模型。 +8. 控制权和访问权分离 + 针对系统管理员用户,实现表对象的控制权和访问权分离,提高普通用户数据安全性,限制管理员对象访问权限。 + 该特性适用于如下场景,即对于有多个业务部门的企业,各部门间使用不同的数据库用户进行业务操作,同时存在同级别的数据库维护部门使用数据库管理员进行运维操作,业务部门希望在未经授权的情况下,管理员用户只能对各部门的数据进行控制操作(DROP、ALTER、TRUNCATE),但是不能进行访问操作(INSERT、DELETE、UPDATE、SELECT、COPY)。即针对管理员用户,表对象的控制权和访问权分离,提高用户数据的安全性。 + 系统管理员可以在创建用户时指定INDEPENDENT属性,表示该用户为私有用户。针对该用户的对象,数据库管理员(包含初始用户和其它管理员用户)在未经其授权前,只能进行控制操作(DROP、ALTER、TRUNCATE),无权进行INSERT、DELETE、SELECT、UPDATE、COPY、GRANT、REVOKE、ALTER OWNER操作。 +9. 数据库加密认证 + 采用基于RFC5802机制的口令加密认证方法。 + 加密认证过程中采用单向Hash不可逆加密算法PBKDF2,可有效防止彩虹攻击。 + 创建用户所设置的口令被加密存储在系统表中。整个认证过程中口令加密存储和传输,通过计算相应的hash值并与服务端存储的值比较来进行正确性校验。 + 统一加密认证过程中的消息处理流程,可有效防止攻击者通过抓取报文猜解用户名或者口令的正确性。 +10. 数据库审计 + 审计日志记录用户对数据库的启停、连接、DDL(Data Definition Language,数据定义语言)、DML、DCL(Data Control Language,数据控制语言)等操作。审计日志机制主要增强数据库系统对非法操作的追溯及举证能力。 + 用户可以通过参数配置对哪些语句或操作记录审计日志。 + 审计日志记录事件的时间、类型、执行结果、用户名、数据库、连接信息、数据库对象、数据库实例名称和端口号以及详细信息。支持按起止时间段查询审计日志,并根据记录的字段进行筛选。 + 数据库安全管理员可以利用这些日志信息,重现导致数据库现状的一系列事件,找出非法操作的用户、时间和内容等。 +11. 全密态数据库等值查询 + 密态数据库,与流数据库、图数据库一样,就是专门处理密文数据的数据库系统。数据以加密形态存储在数据库服务器中,数据库支持对密文数据的检索与计算,而与查询任务相关的词法解析、语法解析、执行计划生成、事务一致性保证、存储都继承原有数据库能力。 + 密态数据库在客户端进行加密,需要在客户端进行大量的操作,包括管理数据密钥、加密敏感数据、解析并修改实际执行的SQL语句,并且识别返回到客户端的加密的数据信息。OpenGauss将这一系列的复杂操作,自动化的封装在前端解析中,对SQL查询中与敏感信息的加密替换,使得发送至数据库服务器侧的查询任务也不会泄露用户查询意图,减少客户端的复杂安全管理及操作难度,实现用户应用开发无感知。 + 密态数据库通过技术手段实现数据库密文查询和计算,解决数据库云上隐私泄露问题及第三方信任问题。实现云上数据的全生命周期保护,实现数据拥有者与数据管理者读取能力分离。 +12. 网络通信安全特性 + 支持通过SSL(Secure Sockets Layer,安全套接层)加密客户端和服务器之间的通信数据,保证客户的客户端与服务器通信安全。采用TLS 1.2(Transport Layer Security,传输层安全)协议标准,并使用安全强度较高的加密算法套件。 +13. 行级访问控制 + 行级访问控制特性将数据库访问粒度控制到数据表行级别,使数据库达到行级访问控制的能力。不同用户执行相同的SQL查询操作,按照行访问控制策略,读取到的结果可能是不同的。 + 用户可以在数据表创建行访问控制(Row Level Security)策略,该策略是指针对特定数据库用户、特定SQL操作生效的表达式。当数据库用户对数据表访问时,若SQL满足数据表特定的Row Level Security策略,在查询优化阶段将满足条件的表达式,按照属性(PERMISSIVE | RESTRICTIVE)类型,通过AND或OR方式拼接,应用到执行计划上。 + 行级访问控制的目的是控制表中行级数据可见性,通过在数据表上预定义Filter,在查询优化阶段将满足条件的表达式应用到执行计划上,影响最终的执行结果。当前行级访问控制支持的SQL语句包括SELECT、UPDATE、DELETE。 +14. 资源标签 + 资源标签(Resource Label)特性通过将数据库资源按照用户自定义的方式划分,实现资源分类管理的目的。管理员可以通过配置资源标签统一地为一组数据库资源进行安全策略的配置如审计或数据脱敏。 + 资源标签能够将数据库资源按照“特征”、“作用场景”等分组归类,对指定资源标签的管理操作即对标签范围下所有的数据库资源的管理操作,能够大大降低策略配置的复杂度和信息冗余度、提高管理效率。 + 当前资源标签所支持的数据库资源类型包括:SCHEMA、TABLE、COLUMN、VIEW、FUNCTION。 +15. 动态数据脱敏 + 为了在一定程度上限制非授权用户对隐私数据的窥探,可以利用动态数据脱敏(Dynamic Data Masking)特性保护用户隐私数据。在非授权用户访问配置了动态数据脱敏策略的数据时,数据库将返回脱敏后的数据而达到对隐私数据保护的目的。 + 管理员可以在数据列上创建动态数据脱敏策略,该策略指出针对特定用户场景应采取何种数据脱敏方式。在开启动态数据脱敏功能后,当用户访问敏感列数据时,系统将用户身份信息例如:访问IP、客户端工具、用户名来匹配相应的脱敏策略,在匹配成功后将根据脱敏策略对访问列的查询结果实施数据脱敏。 + 动态数据脱敏的目的是在不改变源数据的前提下,通过在脱敏策略上配置针对的用户场景(FILTER)、指定的敏感列标签(LABEL)和对应的脱敏方式(MASKING FUNCTION)来灵活地进行隐私数据保护。 +16. 统一审计 + 统一审计(Unified Auditing)利用策略和条件在数据库内部有选择地进行审计,管理员可以对数据库资源或资源标签统一地配置审计策略,从而达到简化管理、针对性地生成审计日志、减少审计日志冗余、提高管理效率的目的。 + 管理员可以定制化的为操作行为或数据库资源配置审计策略,该策略针对特定的用户场景、用户行为或数据库资源进行审计。在开启了统一审计功能后,当用户访问数据库时,系统将根据用户身份信息如:访问IP、客户端工具、用户名来匹配相应的统一审计策略,之后根据策略信息对用户行为按照访问资源(LABEL)和用户操作类型(DML | DDL)进行统一审计。 + 统一审计的目的是将现有的传统审计行为转变为针对性的跟踪审计行为,将目标之外的行为排除在审计之外,从而简化了管理,提高了数据库生成审计数据的安全性。 +17. 用户口令强度校验机制 + 为了加固客户账户和数据的安全,禁止设置过低强度的口令,当初始化数据库、创建用户、修改用户时需要指定密码。密码必须满足强度校验,否则会提示用户重新输入密码。 + 账户密码复杂度对用户密码大小写字母、数字、特殊字符的最少个数,最大最小长度,不能和用户名、用户名倒写相同,不能是弱口令等进行了限制,从而增强了用户账户的安全性。 + 其中弱口令指的是强度较低,容易被破解的密码,对于不同的用户或群体,弱口令的定义可能会有所区别,用户需要自己添加定制化的弱口令。 + 用户口令强度校验机制是否开启由参数password_policy控制,当该参数设置为1时表示采用密码复杂度校验,默认值为1。 +18. 数据加密存储 + 提供对插入数据的加密存储。为用户提供数据加解密接口,针对用户识别的敏感信息列使用加密函数,使得数据加密后再存储在表内。 + 当用户需要对整张表进行加密存储处理时,则需要为每一列单独书写加密函数,不同的属性列可使用不用的入参。 + 当具有对应权限的用户需要查看具体的数据时,可通过解密函数接口对相应的属性列进行解密处理。 + (六)AI能力 +19. AI4DB + 包括参数智能调优与诊断、慢SQL发现、索引推荐、时序预测、异常检测等,能够为用户提供更便捷的运维操作和性能提升,实现自调优、自监控、自诊断等功能。 +20. DB4AI + 兼容MADlib生态,支持70+算法,性能相比MADlib on PostgreSQL 具有数倍提升。新增XGBoost、prophet、GBDT等高级且常用的算法套件,补充MADlib生态的不足。统一SQL到机器学习的技术栈,实现从数据管理到模型训练的SQL语句“一键驱动”。 + +## 小结 + +本章主要从openGauss功能概述、系统架构设计、代码结构解析和亮点特性阐释多方面介绍了openGauss数据库系统的设计,可以加深对openGauss的设计理念与代码逻辑有认识与理解。 \ No newline at end of file -- Gitee From 35c76468078bb7cfa47f2f464b892f7ad1d5dab2 Mon Sep 17 00:00:00 2001 From: huzheyuan123 <1980452465@qq.com> Date: Wed, 12 Apr 2023 13:59:00 +0800 Subject: [PATCH 2/2] Push huzheyuan123 Test Task --- TestTasks/huzheyuan123/openGauss.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/TestTasks/huzheyuan123/openGauss.md b/TestTasks/huzheyuan123/openGauss.md index c0864f7..f907305 100644 --- a/TestTasks/huzheyuan123/openGauss.md +++ b/TestTasks/huzheyuan123/openGauss.md @@ -1722,4 +1722,4 @@ openGauss架构优化要点如下: ## 小结 -本章主要从openGauss功能概述、系统架构设计、代码结构解析和亮点特性阐释多方面介绍了openGauss数据库系统的设计,可以加深对openGauss的设计理念与代码逻辑有认识与理解。 \ No newline at end of file +本章主要从openGauss功能概述、安装、系统架构设计、代码结构解析和亮点特性阐释多方面介绍了openGauss数据库系统的设计,可以加深对openGauss的设计理念与代码逻辑有认识与理解。 \ No newline at end of file -- Gitee