diff --git "a/TestTasks/wang-luyang/CLOG\345\222\214CSNLOG.md" "b/TestTasks/wang-luyang/CLOG\345\222\214CSNLOG.md" new file mode 100644 index 0000000000000000000000000000000000000000..c237c3167ad054fd115cac6b3df2618a5a0815f1 --- /dev/null +++ "b/TestTasks/wang-luyang/CLOG\345\222\214CSNLOG.md" @@ -0,0 +1,71 @@ + +## 介绍 + +CLOG维护事务ID->CommitLog的映射关系,CSNLOG维护事务ID->CommitSeqNoLog的映射关系。 + +由于内存的资源有限,并且系统中可能会有运行时间较长的事务的存在,内存中可能无法存放所有的映射关系,此时需要将这些映射写入磁盘,所以产生了CLOG(XID->CommitLog Map)、CSNLOG(XID->CommitSeqNoLog Map)文件。 + +CSNLOG以及CLOG均采用了SLRU(simple least recently used,简单最近最少使用)机制来实现文件的读取及刷新磁盘的操作。 + +## CLOG + +作用:**记录事务id的提交状态** + +openGauss中对于每个事务id使用4个bit位来标识它的状态。 + + +CLOG定义代码如下: + +```cpp +#define CLOG_XID_STATUS_IN_PROGRESS 0x00 表示事务未开始或还在运行中(故障场景可能是crash) +#define CLOG_XID_STATUS_COMMITTED 0x01 表示该事务已经提交 +#define CLOG_XID_STATUS_ABORTED 0x02 表示该事务已经回滚 +#define CLOG_XID_STATUS_SUB_COMMITTED 0x03 表示子事务已经提交而父事务状态未知 +``` + +## CSNLOG + +作用:**记录事务提交的序列号** + +openGauss为每个事务id分配8个字节uint64的CSN号,所以一个8kB页面能保存1k个事务的CSN号。CSNLOG达到一定大小后会分块,每个CSNLOG文件块的大小为256kB。同xid号类似,CSN号预留了几个特殊的号。 + +CSNLOG定义代码如下: + +```cpp +#define COMMITSEQNO_INPROGRESS UINT64CONST(0x0) 表示该事务还未提交或回滚 +#define COMMITSEQNO_ABORTED UINT64CONST(0x1) 表示该事务已经回滚 +#define COMMITSEQNO_FROZEN UINT64CONST(0x2) 表示该事务已提交,且对任何快照可见 +#define COMMITSEQNO_FIRST_NORMAL UINT64CONST(0x3) 事务正常的CSN号起始值 +#define COMMITSEQNO_COMMIT_INPROGRESS (UINT64CONST(1) << 62) 事务正在提交中 +``` + +## 关键函数及其作用 + +### `Heap_page_prepare_for_xid` + +在对页面有写入操作时调用,用来调节xid_base。几种常见的情况如下: + +* 新来xid在“xid_base + FirstNormalxid”与“xid_base + MaxShortxid(0xFFFFFFFF)”之间时,当前的xid_base不需要调整。 + +* 新来xid在“xid_base + FirstNormalxid”左侧(xid小于该值)时,需要减小xid_base。 + +* 新来xid在“xid_base + MaxShortxid”右侧(xid大于该值)时,需要增加xid_base。 + +* 特殊情况下,由于页面的xid跨度大于32位能表示的范围时,就需要冻结掉本页面上较小的xid,即将提交的xid设为FrozenTransactionId,该值对所有事务均可见;将回滚的xid设为InvalidTransactionId,该值对所有的事务均不可见。 + +### ` Freeze_single_heap_page` + +* 对该页面上较小的xid进行冻结操作。 +* 计算oldestxid,比该值小的事务已经无任何事务访问更老的版本,此时可以将提交的xid直接标记为FrozenTransactionId,即对所有事务可见;将回滚的xid标记为InvalidTransactionId,即对所有事务不可见。 +* 页面整理,清理hot update链,重定向itemid,整理页面空间。 +* 根据oldestxid处理各个元组。 + + +### `Heap_page_shift_base` + +* 更新xid_base,调整页面中各个元组头中的xmin/xmax。 + + +### GetNewTransactionId + +* 获取最新的事务id。 diff --git "a/TestTasks/wang-luyang/ExecGrant_Relation\345\207\275\346\225\260\350\247\243\346\236\220.md" "b/TestTasks/wang-luyang/ExecGrant_Relation\345\207\275\346\225\260\350\247\243\346\236\220.md" new file mode 100644 index 0000000000000000000000000000000000000000..15bd070da99fae6314355a89dcf66a3afaff1aa9 --- /dev/null +++ "b/TestTasks/wang-luyang/ExecGrant_Relation\345\207\275\346\225\260\350\247\243\346\236\220.md" @@ -0,0 +1,116 @@ +```cpp +static void ExecGrant_Relation(InternalGrant* istmt) +{ + . . . +/* 循环处理每一个表对象 */ + foreach (cell, istmt->objects) { + . . . +/* 判断所要操作的表对象是否存在,若不存在则提示报错 */ + tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relOid)); + if (!HeapTupleIsValid(tuple)) + ereport( + ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relOid))); + pg_class_tuple = (Form_pg_class)GETSTRUCT(tuple); +. . . + /* 系统表pg_class中获取旧ACL。若不存在旧的ACL,则新建一个ACL,若存在旧的ACL,则将旧的ACL存储为一个副本 */ + ownerId = pg_class_tuple->relowner; + aclDatum = SysCacheGetAttr(RELOID, tuple, Anum_pg_class_relacl, &isNull); + if (isNull) { + switch (pg_class_tuple->relkind) { + case RELKIND_SEQUENCE: + old_acl = acldefault(ACL_OBJECT_SEQUENCE, ownerId); + break; + default: + old_acl = acldefault(ACL_OBJECT_RELATION, ownerId); + break; + } + noldmembers = 0; + oldmembers = NULL; + } else { + old_acl = DatumGetAclPCopy(aclDatum); + noldmembers = aclmembers(old_acl, &oldmembers); + } + old_rel_acl = aclcopy(old_acl); + + /* 处理表级别的权限 */ + if (this_privileges != ACL_NO_RIGHTS) { + AclMode avail_goptions; + Acl* new_acl = NULL; + Oid grantorId; + HeapTuple newtuple = NULL; + Datum values[Natts_pg_class]; + bool nulls[Natts_pg_class] = {false}; + bool replaces[Natts_pg_class] = {false}; + int nnewmembers; + Oid* newmembers = NULL; + AclObjectKind aclkind; + + /* 获取授权者grantorId和授权者对该操作对象所拥有的授权权限avail_goptions */ + select_best_grantor(GetUserId(), this_privileges, old_acl, ownerId, &grantorId, &avail_goptions); + + switch (pg_class_tuple->relkind) { + case RELKIND_SEQUENCE: + aclkind = ACL_KIND_SEQUENCE; + break; + default: + aclkind = ACL_KIND_CLASS; + break; + } + + /* 结合参数avail_goptions和SQL命令中给出的操作权限,计算出实际需要授予或回收的权限 */ + this_privileges = restrict_and_check_grant(istmt->is_grant, + avail_goptions, + istmt->all_privs, + this_privileges, + relOid, + grantorId, + aclkind, + NameStr(pg_class_tuple->relname), + 0, + NULL); + + /* 生成新的ACL,并更新到系统表pg_class对应元组的ACL字段 */ + new_acl = merge_acl_with_grant(old_acl, + istmt->is_grant, + istmt->grant_option, + istmt->behavior, + istmt->grantees, + this_privileges, + grantorId, + ownerId); +. . . + replaces[Anum_pg_class_relacl - 1] = true; + values[Anum_pg_class_relacl - 1] = PointerGetDatum(new_acl); + + newtuple = heap_modify_tuple(tuple, RelationGetDescr(relation), values, nulls, replaces); + + simple_heap_update(relation, &newtuple->t_self, newtuple); +. . . + } + + /* 若存在列级授权或回收,则调用ExecGrant_Attribute 函数处理 */ +. . . + if (have_col_privileges) { + AttrNumber i; + + for (i = 0; i < num_col_privileges; i++) { + if (col_privileges[i] == ACL_NO_RIGHTS) + continue; + ExecGrant_Attribute(istmt, + relOid, + NameStr(pg_class_tuple->relname), + i + FirstLowInvalidHeapAttributeNumber, + ownerId, + col_privileges[i], + attRelation, + old_rel_acl); + } + } + . . . + } + + heap_close(attRelation, RowExclusiveLock); + heap_close(relation, RowExclusiveLock); +} +``` + diff --git "a/TestTasks/wang-luyang/Kerberos\350\256\244\350\257\201.md" "b/TestTasks/wang-luyang/Kerberos\350\256\244\350\257\201.md" new file mode 100644 index 0000000000000000000000000000000000000000..35ef1b51ec300ac43b06bb918449e418feb68f97 --- /dev/null +++ "b/TestTasks/wang-luyang/Kerberos\350\256\244\350\257\201.md" @@ -0,0 +1,56 @@ + +## 简介 + +Kerberos是一种基于对称密钥技术的身份认证协议。使用开源组件Kerberos可以解决集群内节点或者进程之间的认证问题,即当开启kerberos之后,恶意用户无法仿冒集群内节点或进程来登录数据库系统,只有内部组件才可以持有用于认证的凭证。 + +## 交互流程 + +![](/api/attachments/397766) + +涉及到的角色说明如下: + +![](/api/attachments/397767) + +## 具体使用 + +在数据库系统部署完毕后即可开启Kerberos模式,Kerberos服务部署在数据库系统的主机上,部署过程中会开启Kerberos相关的服务,并派发凭证给集群内部所有的节点,初始化一系列Kerberos需要用到的环境变量,数据库内核中通过调用GSS-API来实现Kebreros标准协议的通信内容。 + +在Kerberos开启后openGauss内部进程之间认证流程如下: + +![](/api/attachments/397768) + +Kerberos提供用户(数据库管理员)透明的认证机制,数据库管理员无须感知Kerberos进程/部署情况。 + +在上图中,左侧虚线框内的Kerberos协议实现部分由OM工具完成。OM工具在Kerberos初始化的时候将KDC服务拉起,KDC服务内置了两个服务:AS和TGS服务。客户端(openGauss主备等数据库服务进程)在登录对端之前会先和KDC交互拿到TGT(ticket granting ticket,根凭证),这个步骤由OM拉起的定时任务调用Kerebros提供刷新票据工具来实现,默认24小时重新获取1次。该获取TGT的过程对应Kerberos标准协议中的AS-REQ、AS-REP、TGS-REQ和TGS-REP模块。 + +右侧侧虚线框内的数据库内侧认证,主要是右侧虚线框内的AP-REQ流程实现。 + +简化流程如下: + +![](/api/attachments/397769) + +数据库内核封装GSS-API数据结构,实现跟外部API交互认证,关键数据结构源代码文件为“src\include\libpq\auth.h”,相关代码如下: + +```c++ +typedef struct GssConn { + int sock; + gss_ctx_id_t gctx; /* GSS 上下文 */ + gss_name_t gtarg_nam; /* GSS 名称 */ + gss_buffer_desc ginbuf; /* GSS 输入token */ + gss_buffer_desc goutbuf; /* GSS 输出token */ +} GssConn; +/* 客户端、服务端接口,用于封装标准kerberos协议调用,其中客户端接口用于向服务端 */ +/* 发起访问,同时响应服务端接口GssServerAuth发起的票据请求 */ +int GssClientAuth(int socket, char* server_host); +int GssServerAuth(int socket, const char* krb_keyfile); +``` + +## 客户端和服务端的交互流程 + +时序图如下: + +![](/api/attachments/397770) + +1. 服务端通过数据库配置文件决定使用Kerberos协议对客户端连接进行认证。 +2. 发起认证请求,客户端准备需要Kerberos认证的环境和票证,发’P’报文响应请求并发送票证。 +3. 服务端验证通过后会发送响应’R’报文,完成Kerberos认证。 diff --git "a/TestTasks/wang-luyang/LWLock\346\255\273\351\224\201\346\243\200\346\265\213\350\207\252\346\204\210.md" "b/TestTasks/wang-luyang/LWLock\346\255\273\351\224\201\346\243\200\346\265\213\350\207\252\346\204\210.md" new file mode 100644 index 0000000000000000000000000000000000000000..a4c42c47bce7c0f7b492326730b8baf7bd6300d4 --- /dev/null +++ "b/TestTasks/wang-luyang/LWLock\346\255\273\351\224\201\346\243\200\346\265\213\350\207\252\346\204\210.md" @@ -0,0 +1,82 @@ + +## 原理 + +### 工作线程和监控线程 + +openGauss使用一个独立的监控线程来完成轻量级锁的死锁探测、诊断和解除。 + +工作线程在请求轻量级锁成功之前会写入一个时间戳数值,成功获得锁之后置该时间戳为0。 + +监测线程可以通过快速对比时间戳数值来发现长时间获得不到锁资源的线程,这一过程是快速轻量的。只有发现长时间的锁等待,死锁检测的诊断才会触发。这么做的目的是防止频繁诊断影响业务正常执行能。一旦确定了死锁环的存在,监控线程首先会将死锁信息记录到日志中去,然后采取恢复措施使得死锁自愈,即选择死锁环中的一个线程报错退出。 + +![](/api/attachments/398081) + +### 具体过程 + +因为检测死锁是否真正发生是一个重CPU操作,为了不影响数据库性能和运行稳定性,轻量级死锁检测使用了一种轻量式的探测,用来快速判断是否可能发生了死锁。 + +openGauss利用时间戳来探测。工作线程在锁请求进入时会在全局内存上写入开始等待的时间戳;在锁请求成功后,将该时间戳清0。对于一个发生死锁的线程,它的锁请求是wait状态,时间戳也不会清0,且与当前运行时间戳数值的差值越来越大。由GUC参数fault_mon_timeout控制检测间隔时间,默认为5s。LWLock死锁检测每隔fault_mon_timeout去进行检测,如果当前发现有同样线程、同样锁id,且时间戳等待时间超过检测间隔时间值,则触发真正死锁检测。 + +## 时间统计及轻量级检测函数 + +* pgstat_read_light_detect:从统计信息结构体中读取线程及锁id相关的时间戳,并记录到指针队列中。 +* lwm_compare_light_detect:跟几秒检测之前的时间对比,如果找到可能发生死锁的线程及锁id则返回true,否则返回false。 + +**真正的LWLock死锁检测是一个有向无环图的判定过程** + +## 死锁信息记录 + +死锁检测需要的信息: + +* 锁,包括请求和分配的信息; +* 线程,包括等待和持有的信息, + +上面这些信息记录到相应的全局变量中,死锁监控线程可以访问并进行判断。 + +### 相关的函数 + +* lwm_heavy_diagnosis:检测是否有死锁。 +* lwm_deadlock_report:报告死锁详细信息,方便定位诊断。 +* lw_deadlock_auto_healing:治愈死锁,选择环中一个线程退出。 + +## 死锁相关数据结构和函数 + +### lock_entry_id + +作用:记录线程信息,其中的thread_id及sessionid是为了适配线程池框架,可以准确的从统计信息中找到相应的信息。 + +```cpp +typedef struct { + ThreadId thread_id; + uint64 st_sessionid; +} lock_entry_id; +``` + +### lwm_light_detect + +作用:记录可能出现死锁的线程,最后用一个链表的形式将当前所有信息串联起来 + + +```cpp +typedef struct { + /* 线程ID */ + lock_entry_id entry_id; + + /* 轻量级锁检测引用计数 */ + int lw_count; +} lwm_light_detect; +``` + +### lwm_lwlocks + +作用:记录线程相关的锁信息,持有锁数量,以及等锁信息 + +```cpp +typedef struct { + lock_entry_id be_tid; /* 线程ID */ + int be_idx; /* 后台线程的位置 */ + LWLockAddr want_lwlock; /* 预获取锁的信息 */ + int lwlocks_num; /* 线程持有的轻量级锁个数 */ + lwlock_id_mode* held_lwlocks; /* 线程持有的轻量级锁数组 */ +} lwm_lwlocks; +``` \ No newline at end of file diff --git "a/TestTasks/wang-luyang/MVCC\345\217\257\350\247\201\346\200\247\345\210\244\346\226\255\346\234\272\345\210\266\344\271\213\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253\345\222\214CSN.md" "b/TestTasks/wang-luyang/MVCC\345\217\257\350\247\201\346\200\247\345\210\244\346\226\255\346\234\272\345\210\266\344\271\213\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253\345\222\214CSN.md" new file mode 100644 index 0000000000000000000000000000000000000000..4e6724466dbd25c8de66542c4bf756c944ef18f7 --- /dev/null +++ "b/TestTasks/wang-luyang/MVCC\345\217\257\350\247\201\346\200\247\345\210\244\346\226\255\346\234\272\345\210\266\344\271\213\344\272\213\345\212\241\351\232\224\347\246\273\347\272\247\345\210\253\345\222\214CSN.md" @@ -0,0 +1,89 @@ + +## 简介 + +**openGauss利用多版本并发控制来维护数据的一致性。** + +当扫描数据时每个事务看到的只是拿快照那一刻的数据,而不是数据当前的最新状态。这样就可以避免一个事务看到其他并发事务的更新而导致不一致的场景。使用多版本并发控制的主要优点是读取数据的锁请求与写数据的锁请求不冲突,以此来实现读不阻塞写,写也不阻塞读。 + +## 事务隔离级别 + +### 基础 + +![](/api/attachments/397826) + +相关概念如下: + +* 脏写(dirty write):两个事务分别写入,两个事务分别提交或回滚,则事务的结果无法确定,即一个事务可以回滚另一个事务的提交。 +* 脏读(dirty read):一个事务可以读取另一个事务未提交的修改数据。 +* 不可重复读(fuzzy read):一个事务重复读取前面读取过的数据,数据的结果被另外的事务修改。 +* 幻读(phantom):一个事务重复执行范围查询,返回一组符合条件的数据,每次查询的结果集因为其他事务的修改发生改变(条数)。 + + +### 扩展 + +在数据库实现的过程中,并发事务产生了一些新的现象,于是扩展了隔离级别 + +![](/api/attachments/397828) + +* 更新丢失(lost update):一个事务在读取元组并更新该元组的过程中,有另一个事务修改了该元组的值,导致最终这次修改丢失。 +* 读偏斜(read skew):假设数据x,y有隐式的约束x+y=100;事务一读取x=50;事务二写x=25并更新y=75保证约束成立,事务二提交,事务一再读取y=75,导致事务一中读取x+y=125,不满足约束。 +* 写偏斜(write skew):假设数据x,y有隐式的约束x+y<=100;事务一读取x=50,并写入y=50;事务二读取y=30并写入x=70,并提交;事务一再提交;最终导致x=70,y=50不满足x+y<=100的约束。 + +openGauss提供读已提交隔离级别和可重复读隔离级别:在实现上可重复读隔离级别无幻读问题,有A5B写偏斜问题。 + +## CSN + +### 介绍 + +CSN:Commit Sequence Number + +The CSN can be required to position Extract in the transaction log, to reposition Replicat in the trail, or for other purposes. It is returned by some conversion functions and is included in reports and certain GGSCI output. + +A CSN is an identifier that Oracle GoldenGate constructs to identify a transaction for the purpose of maintaining transactional consistency and data integrity. It uniquely identifies a particular point in time in which a transaction commits to the database. +Each kind of database management system generates some kind of unique serial number of its own at the completion of each transaction, which uniquely identifies that transaction. + +A CSN captures this same identifying information and represents it internally as a series of bytes, but the CSN is processed in a platform-independent manner. A comparison of any two CSN numbers, each of which is bound to a transaction-commit record in the same log stream, reliably indicates the order in which the two transactions completed. + +The CSN value is stored as a token in any trail record that identifies the beginning of a transaction. This value can be retrieved with the @GETENV column conversion function and viewed with the Logdump utility. + +Extract writes a normalized form of the CSN to external storage such as the trail files and the checkpoint file. There, the CSN is represented as a hex string of bytes. In normalized form, the first two bytes represent the database platform, and the remainder of the string represents the actual unique identifier. + +每个非只读事务在运行过程中会取得一个xid号,在事务提交时会推进CSN,同时会将当前CSN与事务的xid映射关系保存起来(CSNLOG) + +### MVCC快照可见性判断的流程 + +获取快照时记录当前活跃的最小的xid,记为snapshot.xmin。当前最新提交的“事务id(latestCompleteXid) + 1”,记为snapshot.xmax。当前最新提交的“CSN号 + 1”(NextCommitSeqNo),记为snapshot.csn。 + +可见性判断的简易流程如下: + +![](/api/attachments/397831) + +* xid大于等于snapshot.xmax时,该事务id不可见。 +* xid比snapshot.xmin小时,说明该事务id在本次事务启动以前已经结束,需要去CLOG查询事务的提交状态,并在元组头上设置相应的标记位。 +* xid处于snapshot.xmin和snapshot.xmax之间时,需要从CSN-XID映射中读取事务结束的CSN;如果CSN有值且比snapshot.csn小,表示该事务可见,否则不可见。 + + +### 提交流程 + +#### 流程图 + +![](/api/attachments/397832) + +#### 流程解释 + +1. 设置CSN-XID映射commit-in-progress标记。 +2. 原子更新NextCommitSeqNo值。 +3. 生成redo日志,写CLOG,写CSNLOG。 +4. 更新PGPROC将对应的事务信息从PGPROC中移除,xid设置为InvalidTransactionId、xmin设置为InvalidTransactionId等。 + +### 热备支持 + +在事务的提交流程的第一步与第二步之间,增加commit-in-progress的XLOG日志。 + +备机在读快照时,首先获取轻量锁ProcArrayLock,并计算当前快照。如果使用当前快照中的CSN时,碰到xid对应的CSN号有COMMITSEQNO_COMMIT_INPROGRESS标记,则必须等待相应的事务提交XLOG回放结束后再读取相应的CSN判断是否可见。 + +为了实现上述等待操作,备机在对commit-in-progress的XLOG日志做redo操作时,会调用XactLockTableInsert函数获取相应xid的事务排他锁;其他的读事务如果访问到该xid,会等待在此xid的事务锁上直到相应的事务提交XLOG回放结束后再继续运行。 + + + + diff --git "a/TestTasks/wang-luyang/MVCC\345\217\257\350\247\201\346\200\247\345\210\244\346\226\255\346\234\272\345\210\266\344\271\213\346\225\260\346\215\256\347\273\223\346\236\204\345\217\212\345\207\275\346\225\260.md" "b/TestTasks/wang-luyang/MVCC\345\217\257\350\247\201\346\200\247\345\210\244\346\226\255\346\234\272\345\210\266\344\271\213\346\225\260\346\215\256\347\273\223\346\236\204\345\217\212\345\207\275\346\225\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..ef951d5a7f80c67413a603cdac65501a969f7f41 --- /dev/null +++ "b/TestTasks/wang-luyang/MVCC\345\217\257\350\247\201\346\200\247\345\210\244\346\226\255\346\234\272\345\210\266\344\271\213\346\225\260\346\215\256\347\273\223\346\236\204\345\217\212\345\207\275\346\225\260.md" @@ -0,0 +1,170 @@ + +### 快照 + +```cpp +typedef struct SnapshotData { + SnapshotSatisfiesFunc satisfies; /* 判断可见性的函数;通常使用MVCC,即HeapTupleSatisfiesMVCC */ + TransactionId xmin; /*当前活跃事务最小值,小于该值的事务说明已结束 */ + TransactionId xmax; /*最新提交事务id(latestCompeleteXid)+1,大于等于改值说明事务还未开始,该事务id不可见 */ + TransactionId* xip; /*记录当前活跃事务链表,在CSN版本中该值无用 */ + TransactionId* subxip; /* 记录缓存子事务活跃链表,在CSN版本中该值无用 */ + uint32 xcnt; /* 记录活跃事务的个数(xip中元组数)在CSN版本中该值无用 */ + GTM_Timeline timeline; /* openGauss单机中无用 */ + uint32 max_xcnt; /* xip的最大个数,CSN版本中该值无用 */ + int32 subxcnt; /* 缓存子事务活跃链表的个数,在CSN版本中该值无用 */ + int32 maxsubxcnt; /* 缓存子事务活跃链表最大个数,在CSN版本中该值无用 */ + bool suboverflowed; /* 子事务活跃链表是否已超过共享内存中预分配的上限,在CSN版本中无用。 */ + + CommitSeqNo snapshotcsn; /* 快照的CSN号,一般为最新提交事务的CSN号+1(NextCommitSeqNo),CSN号严格小于该值的事务可见。 */ + + int prepared_array_capacity; /* 单机openGauss无用 */ + int prepared_count; /* 单机openGauss无用 */ + TransactionId* prepared_array; /* 单机openGauss无用 */ + + bool takenDuringRecovery; /* 是否Recovery过程中产生的快照 */ + bool copied; /* 该快照是会话级别静态的,还是新分配内存拷贝的 */ + + CommandId curcid; /*事务块中的命令序列号,即同一事务中,前面插入的数据,后面可见。 */ + uint32 active_count; /* ActiveSnapshot stack的refcount */ + uint32 regd_count; /* RegisteredSnapshotList 的refcount*/ +``` + +```cpp +void* user_data; /* 本地多版本快照使用,标记该快照还有线程使用,不能直接释放 */ + SnapshotType snapshot_type; /* openGauss单机无用 */ +} SnapshotData; +``` + +### HeapTupleSatisfiesMVCC + +作用:**用于一般读事务的快照扫描** + +```cpp +bool HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer) +{ + …… /* 初始化变量 */ + + if (!HeapTupleHeaderXminCommitted(tuple)) { /* 此处先判断用一个bit位记录的hint bit(提示比特位:openGauss判断可见性时,通常需要知道元组xmin和xmax对应的clog的提交状态;为了避免重复访问clog,openGauss内部对可见性判断进行了优化。hint bit是把事务状态直接记录在元组头中,用一个bit位来表示提交和回滚状态。openGauss并不会在事务提交或者回滚时主动更新元组上的 hint bit,而是等到访问该元组并进行可见性判断时,如果发现hint bit没有设置,则从 CLOG 中读取并设置,否则直接读取hint bit的值),防止同一条tuple反复获取事务最终提交状态。如果一次扫描发现该元组的xmin/xmax已经提交,就会打上相应的标记,加速扫描;如果没有标记则继续判断。 */ + if (HeapTupleHeaderXminInvalid(tuple)) /* 同样判断hint bit。如果xmin已经标记为invalid说明插入该元组的事务已经回滚,直接返回不可见 */ + return false; + + if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmin(page, tuple))) { /* 如果是一个事务内部,需要去判断该元组的CID,也即是同一个事务内,后面的查询可以查到当前事务之前插入的扫描结果 */ + ……. + } else { /* 如果扫描别的事务,需要根据快照判断事务是否可见 */ + visible = XidVisibleInSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot, &hintstatus); /* 通过csnlog判断事务是否可见,并且返回该事务的最终提交状态 */ + if (hintstatus == XID_COMMITTED) /* 如果该事务提交,则打上提交的hint bit用于加速判断 */ +``` + +```cpp + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleHeaderGetXmin(page, tuple)); + + if (hintstatus == XID_ABORTED) { + … /* 如果事务回滚,则打上回滚标记 */ + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId); + } + if (!visible) { /* 如果xmin不可见,则该元组不可见,否则表示插入该元组的事务对于该次快照已经提交,继续判断删除该元组的事务是否对该次快照提交 */ + return false; + } + } + } + } else { /* 如果该条元组的xmin已经被打上提交的hint bit,则通过函数接口CommittedXidVisibleInSnapshot判断是否对本次快照可见 */ + /* xmin is committed, but maybe not according to our snapshot */ + if (!HeapTupleHeaderXminFrozen(tuple) && + !CommittedXidVisibleInSnapshot(HeapTupleHeaderGetXmin(page, tuple), snapshot)) { + return false; + } + } + …… /* 后续xmax的判断同xmin类似,如果xmax对于本次快照可见,则说明删除该条元组的事务已经提交,则不可见,否则可见,此处不再赘述 */ + if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) { + if (TransactionIdIsCurrentTransactionId(HeapTupleHeaderGetXmax(page, tuple))) { + if (HeapTupleHeaderGetCmax(tuple, page) >= snapshot->curcid) + return true; /* 在扫面前该删除事务已经提交 */ + else + return false; /* 扫描开始后删除操作的事务才提交 */ + } + + visible = XidVisibleInSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot, &hintstatus); + if (hintstatus == XID_COMMITTED) { + /* 设置xmax的hint bit */ + SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleHeaderGetXmax(page, tuple)); + } + if (hintstatus == XID_ABORTED) { + /* 回滚或者故障 */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); + } + if (!visible) { + return true; /* 快照中xmax对应的事务不可见,则认为该元组仍然活跃 */ + } + } else { + /* xmax对应的事务已经提交,但是快照中该事务不可见,认为删除该元组的操作未完成,仍然认为该元组可见 */ + if (!CommittedXidVisibleInSnapshot(HeapTupleHeaderGetXmax(page, tuple), snapshot)) { + return true; /* 认为元组可见 */ + } + } + return false; +} + +``` + +### HeapTupleSatisfiesVacuum + +根据传入的OldestXmin的值返回相应的状态。死亡元组(openGauss多版本机制中不可见的旧版本元组)且没有任何其他未结束的事务可能访问该元组(`xmaxt_infomask & HEAP_XMAX_INVALID) /* 如果xmax还没有,说明没有人删除,此时判断该元组正在插入过程中,否则在删除过程中 */ + return HEAPTUPLE_INSERT_IN_PROGRESS; + return HEAPTUPLE_DELETE_IN_PROGRESS; /* 返回正在删除的过程中 */ + } else if (xidstatus == XID_COMMITTED) { /* 如果xmin提交了,打上hint bit,后面继续看xmax是否提交。 */ + SetHintBits(tuple, buffer, HEAP_XMIN_COMMITTED, HeapTupleGetRawXmin(htup)); + } else { + …. /* 事务结束了且未提交,可能是abort或者是crash的事务,一般返回死亡,可删除;单机情形 t_thrd.xact_cxt.useLocalSnapshot没有作用,恒为false。 */ + SetHintBits(tuple, buffer, HEAP_XMIN_INVALID, InvalidTransactionId); + return ((!t_thrd.xact_cxt.useLocalSnapshot || IsInitdb) ? HEAPTUPLE_DEAD : HEAPTUPLE_LIVE); + } + } + /* 接着判断xmax。如果还没有设置xmax说明没有人删除该元组,返回元组存活,不可删除。 */ + if (tuple->t_infomask & HEAP_XMAX_INVALID) + return HEAPTUPLE_LIVE; +…… + if (!(tuple->t_infomask & HEAP_XMAX_COMMITTED)) { /*如果xmax提交,则看xmax是否比oldesxmin小。小的话说明没有未结束的事务会访问该元组,可以删除。 */ + xidstatus = TransactionIdGetStatus(HeapTupleGetRawXmax(htup), false); + if (xidstatus == XID_INPROGRESS) + return HEAPTUPLE_DELETE_IN_PROGRESS; + else if (xidstatus == XID_COMMITTED) + SetHintBits(tuple, buffer, HEAP_XMAX_COMMITTED, HeapTupleGetRawXmax(htup)); + else { +… /* xmax对应的事务abort或者crash */ + SetHintBits(tuple, buffer, HEAP_XMAX_INVALID, InvalidTransactionId); + return HEAPTUPLE_LIVE; + } + } + + /*判断该元组是否可以删除,xmax%s", backend_exec, backend_options, DEVNULL); ”,其中“static const char* backend_options = "–single "”表示openGauss进程以单用户模式运行。 + +下面以setup_schema函数为例详细介绍这个过程。相关代码如下: +``` +static void setup_schema(void) +{ + PG_CMD_DECL; + char** line; + char** lines; + int nRet = 0; + char* buf_features = NULL; + fputs(_("creating information schema ... "), stdout); + (void)fflush(stdout); + lines = readfile(info_schema_file); + /* + * 使用-j 避免在information_schema.sql反斜杠处理 + */ + nRet = snprintf_s( + cmd, sizeof(cmd), sizeof(cmd) - 1, "\"%s\" %s -j template1 >%s", backend_exec, backend_options, DEVNULL); + securec_check_ss_c(nRet, "\0", "\0"); + PG_CMD_OPEN; + for (line = lines; *line != NULL; line++) { + PG_CMD_PUTS(*line); + FREE_AND_RESET(*line); + } + FREE_AND_RESET(lines); + PG_CMD_CLOSE; + nRet = snprintf_s( + cmd, sizeof(cmd), sizeof(cmd) - 1, "\"%s\" %s template1 >%s", backend_exec, backend_options, DEVNULL); + securec_check_ss_c(nRet, "\0", "\0"); + PG_CMD_OPEN; + PG_CMD_PRINTF1("UPDATE information_schema.sql_implementation_info " + " SET character_value = '%s' " + " WHERE implementation_info_name = 'DBMS VERSION';\n", + infoversion); + + buf_features = escape_quotes(features_file); + PG_CMD_PRINTF1("COPY information_schema.sql_features " + " (feature_id, feature_name, sub_feature_id, " + " sub_feature_name, is_supported, comments) " + " FROM E'%s';\n", + buf_features); + FREE_AND_RESET(buf_features); + PG_CMD_CLOSE; + check_ok(); +} +``` +在这个函数中,PG_CMD_DECL是一个变量定义宏,它有两个变量,由语句"char cmd[MAXPGPATH]"和"FILE* cmdfd = NULL"定义。这样的效果是代码格式统一,便于阅读。 + +语句“readfile(info_schema_file)”读取info_schema_file文件,该文件包含用于初始化系统表的SQL命令。 + +语句"snprintf_s(cmd, sizeof(cmd), sizeof(cmd) -1," "%s" %s -j template1 >%s", backend_exec, backend_options, DEVNULL "是格式化openguss后台进程的命令。PG_CMD_OPEN以popen模式运行cmd命令,启动opengaus进程。 + +语句"for (line = lines;*线!=零;行+ +)意味着遍历info_schema_file文件中的每个SQL命令,宏PG_CMD_PUTS将每个SQL命令发送到openguss进程执行。 + +在整个文件执行完之后,调用宏PG_CMD_CLOSE来停止进程并关闭管道。setup_schema函数的其余部分处理方式类似,除了SQL命令是在函数中生成的,并使用宏PG_CMD_PRINTF1将其写入管道到openguss进程。 + +其他系统对象(如setup_sysviews、setup_dictionary、setup_privileges)的初始化过程类似,不会重复。 + +initdb的整个初始化过程如下所示。 + +1.解析命令行参数。 + +2.找到openguss程序,设置PGDATA、PGDATA、PGDATA、PGPATH等环境变量。设置原始数据库初始化文件postgres。bki postgres.description、postgres。shdescription postgresql.conf。样本,mot.conf。样本,pg_hba.conf。样本,pg_ident。相依样本。shell命令中的这些文件使install在安装后执行,默认为“opengaus - server/dest/share/postgresql”目录。 + +3.当数据库在本地初始化时,locale初始化为en_US。默认为UTF-8,数据库编码初始化为UTF8,文本搜索默认化为英文。 + +4.检查数据库数据目录pg_data是否为空,是否需要创建,权限是否正确。 + +5.创建subdirs变量指定的子目录。 + +6.初始化配置文件postgresql.conf。 + +7.创建template1数据库bootstrap_template1在此步骤中,需要启动后台opengaus进程,以便在数据库中执行SQL语句并创建系统表。bootstrap_template1函数从postgres中读取SQL语句。bki文件,并将它们发送到opengaus进程执行。bootstrap_template1函数用于创建系统表。下面是一个SQL语句的示例。create pg_type表示创建pg_type类型的系统表,INSERT OID表示要插入到系统表中的默认数据。这里的语法是为initdb定制的引导解析语法,而不是正式的SQL语法,语法文件也是独立的。有关详细信息,请参见bootscanner。l和bootparse。在openguss -server\src\gausskernel\bootstrap. y文件。initdb初始化过程中pg_type系统对象的引导语法代码如下。在初始化过程中,通过解析以下语法格式创建pg_type系统对象: +``` +create pg_type 1247 bootstrap rowtype_oid 71 + ( + typname = name , + typnamespace = oid , + typowner = oid , + typlen = int2 , + typbyval = bool , + typtype = char , + typcategory = char , + typispreferred = bool , + typisdefined = bool , + typdelim = char , + typrelid = oid , + typelem = oid , + typarray = oid , + typinput = regproc , + typoutput = regproc , + typreceive = regproc , + typsend = regproc , + typmodin = regproc , + typmodout = regproc , + typanalyze = regproc , + typalign = char , + typstorage = char , + typnotnull = bool , + typbasetype = oid , + typtypmod = int4 , + typndims = int4 , + typcollation = oid , + typdefaultbin = pg_node_tree , + typdefault = text , + typacl = aclitem[] + ) +INSERT OID = 16 ( bool 11 10 1 t b B t t \054 0 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 0 _null_ _null_ _null_ ) +INSERT OID = 17 ( bytea 11 10 -1 f b U f t \054 0 0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 0 _null_ _null_ _null_ ) +........... +close pg_type +``` +8.使用setup_auth函数初始化pg_authid权限。此函数执行一条SQL语句,该语句在函数内静态定义了“static const char* pg_authid_setup[]”。 + +9.使用setup_depend函数创建系统表依赖关系。此函数执行一条SQL语句,该语句在函数内静态定义了“static const char* pg_depend_setup[]”。 + +10.使用load_plpgsql函数加载plpgsql扩展组件。这个函数只执行一条SQL语句:"CREATE EXTENSION plpgsql;". + +11.使用setup_sysviews创建系统视图。这个函数将读取system_views中的sql语句。SQL文件,并将它们发送到opengaus执行。它的主要功能是创建系统视图。 + +12.使用setup_perfviews函数创建性能视图。这个函数读取performance_views中的sql语句。SQL并将它们发送到opengaus执行。该功能用于创建性能视图。 + +13.使用setup_conversion函数创建编码转换。该函数将读取conversion_create中的sql语句。SQL文件,并将它们发送到opengaus执行。该函数主要用于创建编码转换函数。 + +14.使用setup_dictionary函数创建词干数据字典。这个函数从snowball_create。SQL文件,并将其发送到opengaus执行。主要功能是创建一个文本搜索功能。 + +15.使用setup_privileges函数设置权限。setup_privileges函数通过xstrdu将SQL常量字符串复制到一个动态数组中,然后遍历指定的SQL语句。 + +16.使用load_supported_extension函数加载曲面。这个函数执行对应扩展组件的CREATE EXTENSION语句。 + +17.使用setup_update函数更新系统表。COPY pg_cast_oid.txt函数用于创建类型强制处理函数。 + +18.如果要清除template1的垃圾数据,请执行三个SQL语句ANALYZE;”和“真空完整;”和“真空冻结;. + +19.创建template0数据库,即将template1拷贝到template0中。 + +20.通过将template1复制到postgres来创建postgres数据库。 + +21.清理垃圾数据,冻结template0、template1和postgres的事务id。 + +#多线程架构 + +##为什么要使用多线程架构 +随着计算机领域多核技术的发展,如何充分有效的利用多核的并行处理能力,是每个服务器端应用程序都必须考虑的问题。由于数据库服务器的服务进程或线程间存在着大量数据共享和同步,而多线程可以充分利用多CPU来并行执行多个强相关任务,例如执行引擎可以充分的利用线程的并发执行以提供性能。在多线程的架构下,数据共享的效率更高,能提高服务器访问的效率和性能,同时维护开销和复杂度更低,这对于提高数据库系统的并行处理能力非常重要。 + +##openGauss主要线程 +openGauss的后台线程是不对等的,其中Postmaster是主线程,其他线程都是它创建出来的。openGauss后台线程的功能介绍如表所示。 +![](/api/attachments/397888) + +##线程间通信 +penGauss后台线程之间紧密配合,共同完成了数据库的数据处理任务。这些后台线程之间需要交换信息来协调彼此的行为。openGauss多线程通信使用了原来的PostgreSQL的多进程通信方式。具体如表所示。 +![](/api/attachments/397889) + +##线程初始化 +openGauss进程的主函数入口在“\openGauss-server\src\gausskernel\process\main\main.cpp”文件中。在main.cpp文件中,主要完成实例Context(上下文)的初始化、本地化设置,根据main.cpp文件的入口参数调用BootStrapProcessMain函数、GucInfoMain函数、PostgresMain函数和PostmasterMain函数。BootStrapProcessMain函数和PostgresMain函数是在initdb场景下初始化数据库使用的。GucInfoMain函数作用是显示GUC(grand unified configuration,大统一配置,在数据库中指的是运行参数)参数信息。正常的数据库启动会进入PostmasterMain函数。 + +###PostmasterMain函数。 +1.进行Postmaster的Context初始化,初始化GUC参数,解析命令行参数。 +2.调用StreamServerPort函数启动服务器监听和双机监听(如果配置了双机),调用reset_shared函数初始化共享内存和LWLock锁,调用gs_signal_monitor_startup函数注册信号处理线程,调用InitPostmasterDeathWatchHandle函数注册Postmaster死亡监控管道,把openGauss进程信息写入pid_file文件中,调用gspqsignal函数注册Postmaster的信号处理函数。 +3. 根据配置初始化黑匣子,调用pgstat_init函数初始化统计信息传递使用的UDP套接字通信,调用InitializeWorkloadManager函数初始化负载管理器,调用InitUniqueSQL函数初始化UniqueSQL,调用SysLogger_Start函数初始化运行日志的通信管道和SYSLOGGER线程,调用load_hba函数加载hba鉴权文件。 +4.调用initialize_util_thread函数启动STARTUP线程,调用ServerLoop函数进入一个周期循环。在ServerLoop函数的周期循环中,进行客户端请求监听,如果有客户端连接请求,在非线程池模式下,则调用BackendStartup函数创建一个后台线程worker处理客户请求。在线程池模式下,把新的链接加入一个线程池组中。在ServerLoop函数的周期循环中,检查其他线程的运行状态。如果数据库是第一次启动,则调用initialize_util_thread函数启动其他后台线程。如果有后台线程FATAL级别错误退出,则调用initialize_util_thread函数重新启动该线程。如果是PANIC级别错误退出,则整个实例进行重新初始化。 + +PostmasterMain完成了线程之间的通信初始化和线程的启动,无论是后台线程的启动函数initialize_util_thread,还是工作线程的启动函数initialize_worker_thread,最后都是调用initialize_thread函数完成线程的启动。 + +###initialize_thread函数 +initialize_thread函数调用gs_thread_create函数创建线程,调用InternalThreadFunc函数处理线程。它的相关代码如下所示: +``` +ThreadId initialize_thread(ThreadArg* thr_argv) +{ + gs_thread_t thread; + if (0 != gs_thread_create(&thread, InternalThreadFunc, 1, (void*)thr_argv)) { + gs_thread_release_args_slot(thr_argv); + return InvalidTid; + } + return gs_thread_id(thread); +} +``` +nternalThreadFunc函数的代码如下。该函数根据角色调用GetThreadEntry函数,GetThreadEntry函数直接以角色为下标,返回对应GaussdbThreadEntryGate数组对应的元素。数组的元素是处理具体任务的回调函数指针,指针指向的函数为GaussDbThreadMain。相关代码如下所示: +``` +static void* InternalThreadFunc(void* args) +{ + knl_thread_arg* thr_argv = (knl_thread_arg*)args; + gs_thread_exit((GetThreadEntry(thr_argv->role))(thr_argv)); + return (void*)NULL; +} +GaussdbThreadEntry GetThreadEntry(knl_thread_role role) +{ + Assert(role > MASTER && role < THREAD_ENTRY_BOUND); + return GaussdbThreadEntryGate[role]; +} +static GaussdbThreadEntry GaussdbThreadEntryGate[] = {GaussDbThreadMain, + GaussDbThreadMain, + GaussDbThreadMain, + GaussDbThreadMain, + ......}; +``` +在GaussDbThreadMain函数中,首先初始化线程基本信息,Context和信号处理函数,接着就是根据thread_role角色的不同调用不同角色的处理函数,进入各个线程的main函数,比如GaussDbAuxiliaryThreadMain函数、AutoVacLauncherMain函数、WLMProcessThreadMain函数等。其中GaussDbAuxiliaryThreadMain函数是后台辅助线程处理函数。该函数的处理也类似GaussDbThreadMain函数,根据thread_role角色的不同调用不同角色的处理函数,进入各个线程的main函数,比如StartupProcessMain函数、CheckpointerMain函数、WalWriterMain函数、walrcvWriterMain函数等。 + +##总结 +openGauss多线程架构主要包括3个方面: +1.多线程之间的通信,由主线程在初始化阶段完成。 +2.多线程的启动,由主线程创建各个角色线程,调用不同角色的处理函数完成。 +3. 主线程负责监控各个线程的运行,异常退出和重新拉起。 + +#线程池技术 + +##为什么要使用线程池 +在OLTP领域中,数据库需要处理大量的客户端连接。因此,高并发场景的处理能力是数据库的重要能力之一。 +对于外部连接最简单的处理模式是per-thread-per-connection模式,即来一个用户连接产生一个线程。这个模式好处是架构上处理简单,但是高并发下,由于线程太多,线程切换和数据库轻量级锁区域的冲突过大导致性能急剧下降,使得系统性能(吞吐量)严重下降,无法满足用户性能的SLA。 +因此,需要通过线程资源池化复用的技术来解决该问题。线程池技术的整体设计思想是线程资源池化、并且在不同连接之间复用。系统在启动之后会根据当前核数或者用户配置启动固定一批数量的工作线程,一个工作线程会服务一到多个连接session,这样把session和thread进行了解耦。因为工作线程数是固定的,因此在高并发下不会导致线程的频繁切换,而由数据库层来进行session的调度管理。 + +##线程池原理 + +### 线程池工作流程 +![](/api/attachments/397897) +### 线程池对象 +![](/api/attachments/397892) +些对象相互配合实现了线程池机制,它们的主要交互过程如下。 + +1.客户端向数据库发起连接请求,Postmaster线程接收到连接请求并被唤醒。Postmaster线程创建该连接对应的socket,调用ThreadPoolControler函数创建会话(session)数据结构。ThreadPoolControler函数遍历当前所有的Thread Group(线程组),找到当前活跃会话数量最少的Thread Group,并把最新的会话分发给该Thread Group,加入该Thread Group的epoll列表之中。 +2.Thread Group的listener线程负责监听epoll列表中所有的客户连接。 +3.客户端发起任务请求,listener线程被唤醒。listener线程检查当前的Thread Group是否有空闲worker线程;如果有,则把当前会话分配给该worker线程,并唤醒该worker线程;如果没有,则把该会话放在等待队列之中。 +4.worker线程被唤醒后,读取客户端连接上的请求,执行相应请求,并返回请求结果。在一次事务结束(提交、回滚)或者事务超时退出的时候,worker线程的一次任务完成。worker线程将session返回给listener线程,listener线程继续等待该会话的下一次请求。worker线程返还会话后,检查会话等待队列;如果存在等待响应请求的会话,则直接从该队列中取出新的会话并继续工作;如果没有等待响应的session,则将自身标记为free(空闲)状态,等待listener线程唤醒。 +5.客户端断开连接时,worker线程被唤醒,关闭连接,同时清理会话相关结构,释放内存和fd(文件句柄)等资源。 +6.如果worker线程FATAL级别错误退出,退出时worker线程会从worker队列中注销掉。此时listener线程会重新启动一个新的worker线程,直到达到指定数量的worker线程。 + +### 线程池实现 +线程池函数由GUC参数enable_thread_pool控制。当该参数为true时,线程池功能才可用。 +Postmaster线程在ServerLoop中判断如果你启用了线程池的函数,就会在线程池初始化时调用" threadpoolcontroller:: Init"函数。在线程池的初始化过程中,通过确定NUMA节点的数量来处理NUMA结构。相关代码如下: +``` + if (threadPoolActivated) { + bool enableNumaDistribute = (g_instance.shmem_cxt.numaNodeNum > 1); + g_threadPoolControler->Init(enableNumaDistribute); +} +``` +" threadpoolcontroller:: Init"函数的主要作用是创建m_sessCtrl成员和成员m_groups对象,根据核策略分配线程数,调用"ThreadPoolGroup:: Init"函数初始化线程组,调用"ThreadPoolGroup:: WaitReady"函数等待每个线程组初始化结束。创建m_scheduler成员对象,并为“ThreadPoolScheduler:: StartUp”线程池线程调度调用函数。在ThreadPoolGroup::init函数中,创建m_listener对象并启动listener线程。threadworkersentinel函数分配内存并初始化每个worker的互斥锁和条件变量。调用“ThreadPoolGroup:: AddWorker”创建工作对象函数,启动工作线程。 + +Postmaster线程在ServerLoop中如果有客户端链接请求,监听判断线程池启用函数时,会调用“threadpoolcontroller:: DispatchSession”函数进行会话分发。相关代码如下: +``` +if (threadPoolActivated &&!(i < MAXLISTEN && t_thrd.postmaster_cxt.listen_sock_type[i] == HA_LISTEN_SOCKET)) +result = g_threadPoolControler->DispatchSession(port); +/* ThreadPoolControler::DispatchSession的代码实现如下,找到一个会话数最少的线程组,创建会话,把会话添加到线程组的监听线程中 */ +int ThreadPoolControler::DispatchSession(Port* port) +{ + ThreadPoolGroup* grp = NULL; + knl_session_context* sc = NULL; + grp = FindThreadGroupWithLeastSession(); + if (grp == NULL) { + Assert(false); + return STATUS_ERROR; + } + sc = m_sessCtrl->CreateSession(port); + if (sc == NULL) + return STATUS_ERROR; + grp->GetListener()->AddNewSession(sc); + return STATUS_OK; +} +``` +线程的主要功能是TpoolListenerMain(ThreadPoolListener* listener)。在此函数中设置线程名称和信号处理程序,创建一个epoll等待事件,通知Postmaster线程已经准备好,调用t_pool_listener_loop函数(称为"ThreadPoolListener:: WaitTask()"函数已达到等待事件状态)。如果有一个即将到来的事件,调用“ThreadPoolListener:: HandleConnEvent”函数来查找与会话对应的事件。调用"ThreadPoolListener:: DispatchSession"函数,如果有空闲的工作线程,通知工作线程进行处理;如果没有空闲的工作线程,会话就挂在等待队列上。 + +工作线程的主要功能是普通的SQL处理程序PostgresMain。与非线程模式相比,工作线程有三个主要的处理功能: + +(1)通知工作线程准备好了。 + +(2)等待会话通知。 + +(3)连接退出处理。 + +工作线程的相关代码如下: +``` + if (IS_THREAD_POOL_WORKER) { + u_sess->proc_cxt.MyProcPort->sock = PGINVALID_SOCKET; + t_thrd.threadpool_cxt.worker->NotifyReady(); +} + if (IS_THREAD_POOL_WORKER) { + t_thrd.threadpool_cxt.worker->WaitMission(); + Assert(u_sess->status != KNL_SESS_FAKE); + } + case 'X': + case EOF: + RemoveTempNamespace(); + InitThreadLocalWhenSessionExit(); + if (IS_THREAD_POOL_WORKER) { + t_thrd.threadpool_cxt.worker->CleanUpSession(false); + break; + } +``` +“ThreadPoolWorker::WaitMission”函数的主要作用是阻塞所有系统信号,避免系统信号比如SIGHUP等中断当前的处理。清除线程上的会话信息,保证没有上一个会话的内容,等待会话上新的请求,把会话给线程进行处理,允许系统信号中断。 + +“ThreadPoolWorker::CleanUpSession”函数的主要作用是清除会话,从Listener中去除会话,释放会话资源。 + +##总结 +线程池主要是解决大并发的用户连接,在一定程度上可以起到流量控制的作用,即使用户的连接数很多,后端也不需要分配太多的线程。 +线程是OS的一种资源,如果线程太多,OS资源占用很多,并且大量线程的调度和切换会带来昂贵的开销。如果没有线程池,随着连接数的增多,系统的吞吐量会逐渐降低。另外一方面,把线程池划分为线程组,可以很好地匹配NUMA CPU架构的节点,提升多核情况下的访问性能。 + +# 内存管理 +数据库在运行过程中涉及许多对象,这些对象具有不同的生命周期,有些处理需要频繁分配内存。 + +##openGauss内存管理的功能 +1.引入jemalloc开源库,替换glibc的内存分配和释放,减少内存碎片 +2.引入逻辑内存管理机制,控制进程内存使用,避免出现OOM问题 +3.引入多种内存上下文(共享内存上下文、栈式内存上下文、对齐内存上下文),满足不同场景代码开发诉求。 +4.引入ASAN(Address Sanitizer)开源库,在Debug版本下定位内存泄漏和内存越界问题 。 +5.引入丰富的内存查询视图,方便观察内存使用情况,定位潜在内存问题。 + +## 内存管理实现 +openGauss在内存管理上采用了上下文的概念,即具有同样生命周期或者属于同一个上下文语义的内存放到一个MemoryContext管理,MemoryContext的结构代码如下: +``` +typedef struct MemoryContextData* MemoryContext; +typedef struct MemoryContextData { + NodeTag type; /* 上下文类别*/ + MemoryContextMethods* methods; /* 虛函数表*/ + MemoryContext parent; /* 父上下文。顶级上下文为 NULL*/ + MemoryContext firstchild; /* 子上下文的链表头*/ + MemoryContext prevchild; /* 前向子上下文 */ + MemoryContext nextchild; /* 后向子上下文 */ + char* name; /* 上下文名称,方便调试 */ + pthread_rwlock_t lock; /*上下文共享时的并发控制锁 */ + bool is_shared; /* 上下文是否在多个线程共享 */ + bool isReset; /* isReset为true时,表示复位后没有内存空间用于分配*/ + int level; /* 上下文层次级别*/ + uint64 session_id; /* 上下文属于的会话ID */ + ThreadId thread_id; /* 上下文属于的线程ID */ +} MemoryContextData; +``` +虛函数表就是具体的内存管理操作函数指针,具体定义代码如下(函数功能参照注释): +``` +typedef struct MemoryContextMethods { +/*在上下文中分配内存*/ + void* (*alloc)(MemoryContext context, Size align, Size size, const char* file, int line); + /* 释放pointer 内存到上下文中*/ +void (*free_p)(MemoryContext context, void* pointer); +/*在上下文中重新分配内存*/ +void* (*realloc)(MemoryContext context, void* pointer, Size align, Size size, const char* file, int line); + void (*init)(MemoryContext context); /*上下文初始化*/ + void (*reset)(MemoryContext context); /*上下文复位*/ + void (*delete_context)(MemoryContext context); /*删除上下文 */ + Size (*get_chunk_space)(MemoryContext context, void* pointer); /*获取上下文块大小 */ + bool (*is_empty)(MemoryContext context); /*上下文是否为空*/ + void (*stats)(MemoryContext context, int level); /*上下文信息统计*/ +#ifdef MEMORY_CONTEXT_CHECKING + void (*check)(MemoryContext context); /*上下文异常检查*/ +#endif +} MemoryContextMethods; +``` +这些回调函数指针初始化是在AllocSetContextSetMethods函数中调用AllocSetMethodDefinition函数完成的。 +``` +AllocSetMethodDefinition函数的实现代码如下: + +template +void AlignMemoryAllocator::AllocSetMethodDefinition(MemoryContextMethods* method) +{ + method->alloc = &AlignMemoryAllocator::AllocSetAlloc; + method->free_p = &AlignMemoryAllocator::AllocSetFree; + method->realloc = &AlignMemoryAllocator::AllocSetRealloc; + method->init = &AlignMemoryAllocator::AllocSetInit; + method->reset = &AlignMemoryAllocator::AllocSetReset; + method->delete_context = &AlignMemoryAllocator::AllocSetDelete; + method->get_chunk_space = &AlignMemoryAllocator::AllocSetGetChunkSpace; + method->is_empty = &AlignMemoryAllocator::AllocSetIsEmpty; + method->stats = &AlignMemoryAllocator::AllocSetStats; +#ifdef MEMORY_CONTEXT_CHECKING + method->check = &AlignMemoryAllocator::AllocSetCheck; +#endif +} +``` +这些实际操作内存管理的函数为AlignMemoryAllocator类中的AllocSetAlloc函数、AllocSetFree函数、AllocSetRealloc函数、AllocSetInit函数、AllocSetReset函数、AllocSetDelete函数、AllocSetGetChunkSpace函数、AllocSetIsEmpty函数、AllocSetStats函数和AllocSetCheck函数。 +代码如下: +``` +typedef AllocSetContext* AllocSet; +typedef struct AllocSetContext { + MemoryContextData header; /*内存上下文,存储空间是在这个内存上下文中分配的 */ + AllocBlock blocks; /* AllocSetContext所管理内存块的块链表头 */ + AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; /* 空闲块链表*/ + /*这个上下文的分配参数 */ + Size initBlockSize; /* 初始块大小*/ + Size maxBlockSize; /* 最大块大小 */ + Size nextBlockSize; /* 下一个分配的块大小 */ + Size allocChunkLimit; /* 块大小上限*/ + AllocBlock keeper; /* 在复位时,保存的块 */ + Size totalSpace; /* 这个上下文分配的总空间 */ + Size freeSpace; /* 这个上下文总的空闲空间 */ + Size maxSpaceSize; /* 最大内存空间 */ + MemoryTrack track; /* 跟踪内存分配信息 */ +} AllocSetContext; +AllocBlock定义如下: +typedef struct AllocBlockData* AllocBlock; +typedef struct AllocBlockData { + AllocSet aset; /* 哪个AllocSetContext 拥有此块,AllocBlockData 归属AllocSetContext管理*/ + AllocBlock prev; /* 在块链表中的前向指针 */ + AllocBlock next; /* 在块链表中的后向指针 */ + char* freeptr; /* 这个块空闲空间的起始地址 */ + char* endptr; /* 这个块空间的结束地址*/ + Size allocSize; /* 分配的大小*/ +#ifdef MEMORY_CONTEXT_CHECKING + uint64 magicNum; /* 魔鬼数字值,用于内存校验。当前代码固定填写为DADA */ +#endif +} AllocBlockData; +typedef struct AllocChunkData* AllocChunk; /* AllocChunk 内存前面部分是一个AllocBlock结构*/ +typedef struct AllocChunkData { + void* aset; /* 拥有这个chunk的AllocSetContext,如果空闲,则为空闲列表链接*/ + Size size; /* chunk中的使用空间 */ +#ifdef MEMORY_CONTEXT_CHECKING + Size requested_size; /* 实际请求大小,在空闲块中时为0 */ + const char* file; /* palloc/palloc0调用时的文件名称 */ + int line; /* palloc/palloc0 调用时的行号*/ + uint32 prenum; /* 前向魔鬼数字*/ +#endif +} AllocChunkData; +``` +从前面的数据结构可以看出,核心数据结构为AllocSetContext,这个数据结构有3个成员“MemoryContextData header;”、“AllocBlock blocks;”和“AllocChunk freelist[ALLOCSET_NUM_FREELISTS];”。这3个成员把内存管理分为3个层次。 + +(1) MemoryContext管理上下文之间的父子关系,设置MemoryContext的内存管理函数。 +(2) AllocBlock blocks把所有内存块通过双链表链接起来。 +(3) 指定内存单元块。内存块从内存块AllocBlock中分配。内存块和内存块之间的转换关系为:AllocChunk = (AllocChunk)(((char*)*block) + ALLOC_BLOCKHDRSZ;And" AllocBlock block = (AllocBlock)(((char*)*chunk) - ALLOC_BLOCKHDRSZ);". +内存单元块被转换为获得最终的用户指针。内存单元块和用户指针之间的转换关系是:((AllocPointer)(((char*)(*chk)) + ALLOC_CHUNKHDRSZ))和((AllocChunk)(((char*)*(ptr)) -ALLOC_CHUNKHDRSZ))。 +数据结构的基本关系如图所示。 +![](/api/attachments/398168) + +### MemoryContext的实现函数 +主要实现在mcxt.cpp文件中 +![](/api/attachments/398169) + +### AllocSet的实现函数 +主要实现在aset.cpp文件中 +![](/api/attachments/398170) + +#多维监控 +数据库是企业的关键组件,数据库的性能直接决定了很多业务的吞吐量。为了简化数据库维护人员的调优,openGauss对数据库运行进行了多维度的监控,并且开发了一些维护特性,比如WDR(wordload dignostic report,工作负荷诊断报告)性能诊断报告、慢SQL诊断、session性能诊断、系统KPI(key performance indicator,关键性能指标)辅助诊断等,帮助维护人员对数据库的性能进行诊断。 + +这些监视项显示在视图模式中,并集中在DBE_PERF模式中。除了WDR Snapshot的快照元数据外,其他数据表源也是DBE_PERF模式中的视图。WDR快照表命名原则:snap_{*源表},根据这种关系可以找到原表对应的快照表。在opengaus网站(https://opengauss.org) 上的开发人员指南手册的“DBE_PERF模式”一节中解释了这些视图。 +性能视图的源代码在openguss -server\src\common\backend\catalog\performance_views中。sql文件。https://gitee.com/opengauss/openGauss-server/blob/master/src/common/backend/catalog/performance_views.sql, 安装后将复制到安装路径“/ share/postgresql/performance_views SQL”)。initdb在数据库初始化阶段读取该文件,以在数据库系统中创建相应的视图。这些视图遵循openguss通用视图的实现逻辑,即视图来自函数的封装,这些函数可以是内置的,也可以是存储的。下面是dbe_perf的代码: +``` +CREATE OR REPLACE FUNCTION dbe_perf.get_global_os_runtime + (OUT node_name name, OUT id integer, OUT name text, OUT value numeric, OUT comments text, OUT cumulative boolean) +RETURNS setof record +AS $$ +DECLARE + row_data dbe_perf.os_runtime%rowtype; +query_str := 'SELECT * FROM dbe_perf.os_runtime'; + FOR row_data IN EXECUTE(query_str) LOOP + ...... + END LOOP; + return; + END; $$ +LANGUAGE 'plpgsql' NOT FENCED; +CREATE VIEW dbe_perf.global_os_runtime AS + SELECT DISTINCT * FROM dbe_perf.get_global_os_runtime(); +``` +global_os_runtime视图来自存储函数get_global_os_runtime的封装,访问dbe_perf。os_runtime VIEW FROM存储函数。os_runtime视图的SQL语句是“CREATE view dbe_perf”。os_runtime AS SELECT * FROM pv_os_run_info();"pv_os_run_info函数是一个内置函数,用于读取数据库系统的监视指标。pv_os_run_info函数的代码如下: +``` +Datum pv_os_run_info(PG_FUNCTION_ARGS) +{ + FuncCallContext* func_ctx = NULL; + /* 判断是不是第一次调用 */ + if (SRF_IS_FIRSTCALL()) { + MemoryContext old_context; + TupleDesc tup_desc; + /* 创建函数上下文 */ + func_ctx = SRF_FIRSTCALL_INIT(); + /* + * 切换内存上下文到多次调用上下文 + */ + old_context = MemoryContextSwitchTo(func_ctx->multi_call_memory_ctx); + /* 创建一个包含5列的元组描述模板*/ + tup_desc = CreateTemplateTupleDesc(5, false); + TupleDescInitEntry(tup_desc, (AttrNumber)1, "id", INT4OID, -1, 0); + TupleDescInitEntry(tup_desc, (AttrNumber)2, "name", TEXTOID, -1, 0); + TupleDescInitEntry(tup_desc, (AttrNumber)3, "value", NUMERICOID, -1, 0); + TupleDescInitEntry(tup_desc, (AttrNumber)4, "comments", TEXTOID, -1, 0); + TupleDescInitEntry(tup_desc, (AttrNumber)5, "cumulative", BOOLOID, -1, 0); + + /* 填充元组描述模板 */ + func_ctx->tuple_desc = BlessTupleDesc(tup_desc); + /* 收集系统信息 */ + getCpuNums(); + getCpuTimes(); + getVmStat(); + getTotalMem(); + getOSRunLoad(); + (void)MemoryContextSwitchTo(old_context); + } + + /*设置函数的上下文,每次函数调用都需要*/ + func_ctx = SRF_PERCALL_SETUP(); + while (func_ctx->call_cntr < TOTAL_OS_RUN_INFO_TYPES) { + /* 填充所有元组每个字段的值 */ + Datum values[5]; + bool nulls[5] = {false}; + HeapTuple tuple = NULL; + errno_t rc = 0; + rc = memset_s(values, sizeof(values), 0, sizeof(values)); + securec_check(rc, "\0", "\0"); + rc = memset_s(nulls, sizeof(nulls), 0, sizeof(nulls)); + securec_check(rc, "\0", "\0"); + if (!u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].got) { + ereport(DEBUG3, + (errmsg("the %s stat has not got on this plate.", + u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].name))); + func_ctx->call_cntr++; + continue; + } + values[0] = Int32GetDatum(func_ctx->call_cntr); + values[1] = CStringGetTextDatum(u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].name); + values[2] = u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].getDatum( + u_sess->stat_cxt.osStatDataArray[func_ctx->call_cntr]); + values[3] = CStringGetTextDatum(u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].comments); + values[4] = BoolGetDatum(u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].cumulative); + + tuple = heap_form_tuple(func_ctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(func_ctx, HeapTupleGetDatum(tuple)); + } + /* 填充结束,返回结果 */ + SRF_RETURN_DONE(func_ctx); +} +``` +pv_os_run_info函数可以分为三个部分: + +(1)调用CreateTemplateTupleDesc函数和TupleDescInitEntry函数定义元组描述。 +(2)调用getCpuNums、getCpuTimes、getVmStat、getTotalMem、getOSRunLoad命令收集系统信息。 +(3)将收集到的u_sess信息填充到元组数据中,最后返回给调用者。openguss为实现返回结果的通用SQL函数提供了实现步骤和方法:SRF_IS_FIRSTCALL、SRF_PERCALL_SETUP、SRF_RETURN_NEXT和SRF_RETURN_DONE。从代码中可以看出,pv_os_run_info的实现过程也遵循了openguss常用的SQL函数实现方法。 +系统指标的采集来自于对系统信息的读取和对数据库系统中某些模块的打分(打分是指按照规范收集指定的数据,记录系统运行的一些关键点)。许多要点集中在两个方面:执行的事务数量和执行时间。可以推断出最大时间,最小时间,平均时间。这些是分散的,并且代码逻辑相对简单,这里不再讨论。只需查看内置函数读取的变量并查看这些变量的赋值位置,就可以跟踪实现的位置。 + +openguss数据库的主要维护特性的实现代码在openguss -server \src\gausskernel\cbb\instruments目录中,例如WDR和SQL百分位计算,这里不进行描述。 + +性能统计也会给openguss的正常运行带来一定的性能损失,所以这些特性都是切换控制的。具体情况如下。 + +(1)实时收集事件信息的功能为enable_instr_track_wait。 +(2)启用enable_instr_unique_sql和enable_inst_rt_percentile,实时收集Unique SQL信息。 +(3)数据库监控快照开关为enable_wdr_snapshot。 + +#模拟信号机制 +信号是Linux进程/线程之间的通信机制。向进程发送信号的系统函数是kill,向线程发送信号的系统函数是pthread_kill。在openguss中,既有gs_ctl发送给openguss进程的进程间信号,也有openguss进程中的线程间信号。 + +信号是有限的资源。操作系统提供SIGINT、SIGQUIT、SIGTERM、SIGALRM、SIGPIPE、SIGFPE、SIGUSR1、SIGUSR2、SIGCHLD、SIGTTIN、SIGTTOU、SIGXFSZ等信号。这些信号通常与系统相关。每个信号都有特定的用途。例如,SIGALRM是系统定时器的通知信号。剩下的主要信号是SIGUSR1和SIGUSR2。 + +为了在系统信号有限的情况下,在openGauss中表达不同的丰富的通信语义,openGauss添加了新的变量来表达特定的语义。opengaus是一个多线程体系结构。如果同一进程中的不同线程注册了不同的处理程序,则后者将覆盖前者的信号处理。为了让不同的线程注册不同的处理程序,您需要自己管理与信号对应的注册函数。为了解决这些问题,opengaus实现了信号仿真机制。信号仿真的基本原理是,每个线程注册和管理自己的信号处理函数,信号枚举值仍然使用系统信号值,线程使用自己的变量记录信号和回调函数的对应关系。在线程之间发送信号时,首先将变量设置为特定的信号值,然后使用系统调用pthread_kill发送信号。在收到通知后,线程将根据附加变量所表示的特定信号值回调相应的信号处理函数。 + +信号处理所涉及的数据结构代码如下。每个线程都有一个GsSignalSlot结构,它存储线程ID、线程名称和GsSignal结构。GsSignal结构存储了与每个信号对应的处理程序数组以及与每个线程关联的信号池。struct SignalPool包含已使用信号列表和空闲信号列表。当模拟信号到达时,找到一个空闲信号GsNode,并将其放入已使用信号列表中。GsNode存储GsSndSignal sig_data结构。发送的信号的具体值和发送的线程ID存储在GsSndSignal结构中。如果需要设置其他检查信息,可以设置“GsSignalCheck”的内容。相关代码如下: +``` +typedef struct GsSignalSlot { + ThreadId thread_id; + char* thread_name; + GsSignal* gssignal; +} GsSignalSlot; +typedef struct GsSignal { + gs_sigfunc handlerList[GS_SIGNAL_COUNT]; + sigset_t masksignal; + SignalPool sig_pool; + volatile unsigned int bitmapSigProtectFun; +} GsSignal; +typedef struct SignalPool { + GsNode* free_head; /* 空闲信号列表头部 */ + GsNode* free_tail; /* 空闲信号列表尾部 */ + GsNode* used_head; /* 使用信号列表头部*/ + GsNode* used_tail; /*使用信号列表尾部*/ + int pool_size; /* 数组列表大小*/ + pthread_mutex_t sigpool_lock; +} SignalPool; +typedef struct GsNode { + GsSndSignal sig_data; + struct GsNode* next; +} GsNode; +typedef struct GsSndSignal { + unsigned int signo; /* 需要处理的信号*/ + gs_thread_t thread; /* 发送信号的线程ID */ + GsSignalCheck check; /* 信号发送线程需要检查的信息 */ +} GsSndSignal; +typedef struct GsSignalCheck { + GsSignalCheckType check_type; + uint64 debug_query_id; + uint64 session_id; +} GsSignalCheck; +``` +信号处理的主要过程是模拟信号机制的初始化、信号处理函数的配准、信号发送和信号处理。具体处理逻辑如下。 + +(1)初始化模拟信号机制函数gs_signal_slots_init。以下函数在gs_signal_slots_init处理函数中执行。 +1.根据传入插槽的数量分配内存。遍历和初始化每个槽。初始化过程中,调用gs_signal_init函数初始化每个槽位的GsSignal (GsSignal是openguss封装的模拟信号结构,包含信号掩码和信号处理函数)。 + +2.在gs_signal_init函数中,为GsSignal分配并初始化内存。初始化过程中,调用gs_signal_sigpool_init函数初始化信号池。 + +3.在gs_signal_sigpool_init函数中分配内存并初始化信号池。 + + +(2)寄存器信号处理函数gspqsignal。在gspqsignal handler函数中执行以下操作。 +1.叫gs_signal_register_handler (t_thrd。signal_slot - > gssignal signo, func);GsSignal函数用GsSignal注册与该信号对应的处理程序。在注册之前,需要给线程分配一个signal_slot,这在gs_signal_startup_siginfo函数中完成。 + +2.在gs_signal_startup_siginfo函数中,调用gs_signal_alloc_slot_for_new_thread函数为线程分配一个signal_slot。函数是遍历“g_instance”。signal_base->slots”,找到一个未使用的槽位(thread_id 0表示未使用),然后设置本地线程ID和线程名。 + +(3)发送信号函数gs_signal_send。在gs_signal_send处理函数中执行以下函数。 +1.调用gs_signal_find_slot函数定位要发送的线程所在的GsSignalSlot。 + +2.调用gs_signal_set_signal_by_threadid设置模拟信号。函数首先检查信号是否已经存在于使用列表中,如果存在则返回。如果它不存在,它会在空闲列表中找到一个空闲的GsNode,设置信号值,发送线程ID、check_type到sig_data,最后将空闲的GsNode移动到使用列表中。 + +3.调用gs_signal_thread_kill命令发送信号通知。这个函数遍历GsSignalSlot,找到一个匹配的线程ID,然后调用"gs_signal_thread_kill(thread_id, RES_SIGNAL);"。函数向特定线程发送信号通知。“#define RES_SIGNAL SIGUSR2”表示使用SIGUSR2发送通知。 + +(4)处理信号函数gs_signal_handle。以下函数在gs_signal_handle中执行。 +1.遍历信号池的使用列表,找到需要处理的信号。 + +2.找到该信号对应的信号处理函数。将GsNode移动到空闲列表中。 + +3.调用gs_signal_handle_check函数检查当前条件是否满足。如果它仍然工作,则回调处理程序。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\345\210\233\345\273\272CEK\345\210\227\345\212\240\345\257\206\345\257\206\351\222\245.md" "b/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\345\210\233\345\273\272CEK\345\210\227\345\212\240\345\257\206\345\257\206\351\222\245.md" new file mode 100644 index 0000000000000000000000000000000000000000..7d328aae56010281e782e08e45a415d89b6b4625 --- /dev/null +++ "b/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\345\210\233\345\273\272CEK\345\210\227\345\212\240\345\257\206\345\257\206\351\222\245.md" @@ -0,0 +1,50 @@ +## 创建前提 + +先创建好主密钥CMK + +## 创建语句 + +```cpp +CREATE COLUMN ENCRYPTION KEY cek_1 WITH VALUES (CLIENT_MASTER_KEY = cmk_1, ALGORITHM = AEAD_AES_256_CBC_HMAC_SHA256); +``` + +## 数据结构 + +```cpp +/* 保存创建列加密密钥的语法信息 */ +typedef struct CreateClientLogicColumn { + NodeTag type; + List *column_key_name; /* 列加密密钥名称 */ + List *column_setting_params; /* 列加密密钥参数 */ +} CreateClientLogicColumn; + +/* 保存列加密密钥参数,保存在CreateClientLogicColumn的column_setting_params中 */ +typedef struct ClientLogicColumnParam { + NodeTag type; + ClientLogicColumnProperty key; + char *value; + unsigned int len; + List *qualname; + int location; +} ClientLogicColumnParam; + +/* 保存列加密密钥参数的key的枚举类型 */ +typedef enum class ClientLogicColumnProperty { + CLIENT_GLOBAL_SETTING, /* 加密CEK的CMK */ + CEK_ALGORITHM, /* 加密用户数据的算法 */ + CEK_EXPECTED_VALUE, /* CEK密钥明文,可选参数 */ + COLUMN_COLUMN_FUNCTION, /* 默认为encryption */ +} ClientLogicColumnProperty; +``` + +## 参数说明 + +* `CLIENT_MASTER_KEY`:用于加密CEK的CMK对象。 +* `ALGORITHM`:指定加密用户数据的算法,即指定CEK的密钥类型。 +* `ENCRYPTED_VALUE`:列加密密钥的明文,默认随机生成。也可由用户指定,用户指定时密钥长度范围为28~256位。 + +## 创建示例 + +```cpp +CREATE COLUMN ENCRYPTION KEY cek_1 WITH VALUES (CLIENT_MASTER_KEY = cmk_1, ALGORITHM = AEAD_AES_256_CBC_HMAC_SHA256); +``` diff --git "a/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\345\210\233\345\273\272CMK\345\256\242\346\210\267\347\253\257\344\270\273\345\257\206\351\222\245.md" "b/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\345\210\233\345\273\272CMK\345\256\242\346\210\267\347\253\257\344\270\273\345\257\206\351\222\245.md" new file mode 100644 index 0000000000000000000000000000000000000000..ec7c1fe11de64b80545cc26b73a4398bf28a813b --- /dev/null +++ "b/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\345\210\233\345\273\272CMK\345\256\242\346\210\267\347\253\257\344\270\273\345\257\206\351\222\245.md" @@ -0,0 +1,54 @@ + +## 创建语句 + +```cpp +CREATE CLIENT MASTER KEY cmk_1 WITH (KEY_STORE = LOCALKMS , KEY_PATH = "kms_1" , ALGORITHM = RSA_2048); +``` + +## 数据结构 + +```cpp +/* 保存创建客户端主密钥的语法信息 */ +typedef struct CreateClientLogicGlobal { + NodeTag type; + List *global_key_name; /* 全密态数据库主密钥名称 */ + List *global_setting_params; /* 全密态数据库主密钥参数,每一个元素是一个ClientLogicGlobalparam */ +} CreateClientLogicGlobal; + +/* 保存客户端主密钥参数信息 */ +typedef struct ClientLogicGlobalParam { + NodeTag type; + ClientLogicGlobalProperty key; /* 键 */ + char *value; /* 值 */ + unsigned int len; /* 值长度 */ + int location; /* 位置标记 */ +} ClientLogicGlobalParam; + +/* 保存客户端主密钥参数的key的枚举类型 */ +typedef enum class ClientLogicGlobalProperty { + CLIENT_GLOBAL_FUNCTION, /* 默认为encryption */ + CMK_KEY_STORE, /* 目前仅支持localkms */ + CMK_KEY_PATH, /* 密钥存储路径 */ + CMK_ALGORITHM /* 指定加密CEK的算法 */ +} ClientLogicGlobalProperty; +``` + +## 参数说明 + +* KEY_STORE:指定管理CMK的组件 +* KEY_PATH:唯一标识CMK。 +* ALGORITHM:指定加密CEK的算法,即指定CMK的密钥类型。 + +创建主密钥本质上是将CMK的元信息解析并保存在CreateClientLogicGlobal。其中global_key_name是密钥名称,global_setting_params是一个List,每个节点是ClientLogicGlobalParam结构。客户端先通过解析器`fe_raw_parser()`解析为CreateClientLogicGlobal结构体,对其参数进行校验并发送查询语句到服务端;服务端解析为CreateClientLogicGlobal结构体并检查用户namespace等权限,CMK元信息保存在系统表中。 + + +## 示例 + +```cpp +CREATE CLIENT MASTER KEY cmk_1 WITH (KEY_STORE = LOCALKMS , KEY_PATH = "kms_1" , ALGORITHM = RSA_2048); +``` + +## 流程图 + +![](/api/attachments/392179) + diff --git "a/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\345\210\233\345\273\272\345\212\240\345\257\206\350\241\250.md" "b/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\345\210\233\345\273\272\345\212\240\345\257\206\350\241\250.md" new file mode 100644 index 0000000000000000000000000000000000000000..aa59fcbcbed7a6ab05947e72cd82ed164337484a --- /dev/null +++ "b/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\345\210\233\345\273\272\345\212\240\345\257\206\350\241\250.md" @@ -0,0 +1,114 @@ +## 创建语句 + +```cpp +CREATE TABLE creditcard_info (id_number int, +name text encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), gender varchar(10) encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), +salary float4 encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), +credit_card varchar(19) encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC)); +``` + +## 处理逻辑 + +先对sql语句进行语句解析,后使用`CreateStmt`函数处理逻辑,在`run_pre_create_statement`函数中,对`CreateStmt->tableElts`中每个`ListCell`进行判断,加密表列定义及约束处理函数段代码如下: + +```cpp +bool createStmtProcessor::run_pre_create_statement(const CreateStmt * const stmt, StatementData *statement_data) +{ + … + /* 加密表列定义及约束处理 */ + foreach (elements, stmt->tableElts) { + Node *element = (Node *)lfirst(elements); + switch (nodeTag(element)) { + case T_ColumnDef: { + … + /* 校验distribute by是否符合规格 */ + if (column->colname != NULL && + !check_distributeby(stmt->distributeby, column->colname)) { + return false; + } + /* 列定义处理,存储加密类型,加密密钥等信息 */ + if (!process_column_defintion(column, element, &expr_vec, &cached_columns, + &cached_columns_for_defaults, statement_data)) { + return false; + } + break; + } + /* 处理check, unique 或其他约束 */ + case T_Constraint: { + Constraint *constraint = (Constraint*)element; + if (constraint->keys != NULL) { + ListCell *ixcell = NULL; + foreach (ixcell, constraint->keys) { + char *ikname = strVal(lfirst(ixcell)); + for (size_t i = 0; i < cached_columns.size(); i++) { + if (strcmp((cached_columns.at(i))->get_col_name(), ikname) == 0 && !check_constraint( + constraint, cached_columns.at(i)->get_data_type(), ikname, &cached_columns)) { + return false; + } + } + } + } else if (constraint->raw_expr != NULL) { + if (!transform_expr(constraint->raw_expr, "", &cached_columns)) { + return false; + } + } + break; + } + default: + break; + } + } + … + /* 加密约束中需要加密的明文数据 */ + if (!RawValues::get_raw_values_from_consts_vec(&expr_vec, statement_data, 0, &raw_values_list)) { + return false; + } + return ValuesProcessor::process_values(statement_data, &cached_columns_for_defaults, 1, + &raw_values_list); +} +``` + +在数据发送到数据库之前,数据加密驱动程序进行数据加密,数据在整个语句的处理过程中以密文形式存在,在返回结果时,解密返回的数据集。 + +定义了完整的加密表后,用户就可以用正常的方式将数据插入到表中。 + +`encrypt_data`函数用于加密,其核心逻辑代码如下所示: + +```cpp +int encrypt_data(const unsigned char *plain_text, int plain_text_length, const AeadAesHamcEncKey &column_encryption_key, + EncryptionType encryption_type, unsigned char *result, ColumnEncryptionAlgorithm column_encryption_algorithm) +{ + …… + /* 得到16位的iv值 */ + unsigned char _iv [g_key_size + 1] = {0}; +unsigned char iv_truncated[g_iv_size + 1] = {0}; +/* 确定性加密,则通过hmac_sha256生成iv */ + if (encryption_type == EncryptionType::DETERMINISTIC_TYPE) { + hmac_sha256(column_encryption_key.get_iv_key(), g_auth_tag_size, plain_text, plain_text_length, _iv); + …… +} else { +/* 随机加密,则随机生成iv */ + if (encryption_type != EncryptionType::RANDOMIZED_TYPE) { + return 0; + } + int res = RAND_priv_bytes(iv_truncated, g_block_size); + if (res != 1) { + return 0; + } + } + int cipherStart = g_algo_version_size + g_auth_tag_size + g_iv_size; + /* 调用encrypt计算密文 */ +int cipherTextSize = encrypt(plain_text, plain_text_length, column_encryption_key.get_encyption_key(), iv_truncated, + result + cipherStart, column_encryption_algorithm); + …… + int ivStartIndex = g_auth_tag_size + g_algo_version_size; + res = memcpy_s(result + ivStartIndex, g_iv_size, iv_truncated, g_iv_size); + securec_check_c(res, "\0", "\0"); + /* 计算 HMAC */ + int hmacDataSize = g_algo_version_size + g_iv_size + cipherTextSize; + hmac_sha256(column_encryption_key.get_mac_key(), g_auth_tag_size, + result + g_auth_tag_size, hmacDataSize, result); + return (g_auth_tag_size + hmacDataSize); +} +``` + diff --git "a/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\346\237\245\350\257\242.md" "b/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\346\237\245\350\257\242.md" new file mode 100644 index 0000000000000000000000000000000000000000..7a15a8779576b7563b842931e0b05461cae24001 --- /dev/null +++ "b/TestTasks/wang-luyang/openGauss\345\257\206\346\200\201\346\225\260\346\215\256\345\272\223\344\271\213\346\237\245\350\257\242.md" @@ -0,0 +1,106 @@ + +## 简要介绍 + +### 特点 + +* 对用户无感知 +* 展示数据时将密文数据进行解密 + +### 过程 + +客户端解析`SELECT`查询语句中的列属性信息,如果在缓存中已存在则直接从缓存中提取列属性信息;如果缓存中尚不存在,需要先从服务端查询该信息,并缓存。列加密密钥`CEK`是以密文形式存储在服务端,客户端需要先解密`CEK`,然后用其加密`SELECT`查询语句中的条件参数。加密后的SELECT查询语句发送给数据库服务端执行完成后,返回加密的查询结果集。客户端用解密后的列加密密钥`CEK`解密`SELECT`查询结果集,并返回解密后的明文结果集给应用端。 + +## 相关代码 + +`run_pre_insert_statement`函数用于等值查询处理,其核心逻辑代码如下所示: + +```cpp +bool Processor::run_pre_select_statement(const SelectStmt * const select_stmt, const SetOperation &parent_set_operation, + const bool &parent_all, StatementData *statement_data, ICachedColumns *cached_columns, ICachedColumns *cached_columns_parents) +{ +bool select_res = false; +/* 处理SELECT语句中的集合操作 */ + if (select_stmt->op != SETOP_NONE) { + select_res = process_select_set_operation(select_stmt, statement_data, cached_columns); + RETURN_IF(!select_res); + } + /* 处理WHERE子句 */ + ExprPartsList where_expr_parts_list; + select_res = exprProcessor::expand_expr(select_stmt->whereClause, statement_data, &where_expr_parts_list); + RETURN_IF(!select_res); +…… + /* 从FROM子句中获取缓存加密列 */ +CachedColumns cached_columns_from(false, true); + select_res = run_pre_from_list_statement(select_stmt->fromClause, statement_data, &cached_columns_from, + cached_columns_parents); +…… +/* 将查询的加密列放在cached_columns结构中 */ + for (size_t i = 0; i < cached_columns_from.size(); i++) { + if (find_in_name_map(target_list, cached_columns_from.at(i)->get_col_name())) { + CachedColumn *target = new (std::nothrow) CachedColumn(cached_columns_from.at(i)); + if (target == NULL) { + fprintf(stderr, "failed to new CachedColumn object\n"); + return false; + } + cached_columns->push(target); + } + } + if (cached_columns_from.is_empty()) { + return true; + } + /* 加密列不支持ORDER BY(排序)操作 */ + if (!deal_order_by_statement(select_stmt, cached_columns)) { + return false; + } + +/* 将WHERE子句中加密的值进行加密处理 */ + if (!WhereClauseProcessor::process(&cached_columns_from, &where_expr_parts_list, statement_data)) { + return false; +} +…… + return true; +} +``` + +客户端密文解密函数代码如下 + +```cpp +int decrypt_data(const unsigned char *cipher_text, int cipher_text_length, + const AeadAesHamcEncKey &column_encryption_key, unsigned char *decryptedtext, + ColumnEncryptionAlgorithm column_encryption_algorithm) +{ + if (cipher_text == NULL || cipher_text_length <= 0 || decryptedtext == NULL) { + return 0; + } + /* 校验密文长度 */ + if (cipher_text_length < min_ciph_len_in_bytes_with_authen_tag) { + printf("ERROR(CLIENT): The length of cipher_text is invalid, cannot decrypt.\n"); + return 0; + } + /* 校验密文中的版本号 */ + if (cipher_text[g_auth_tag_size] != '1') { + printf("ERROR(CLIENT): Version byte of cipher_text is invalid, cannot decrypt.\n"); + return 0; + } + …… + /* 计算MAC标签 */ + unsigned char authenticationTag [g_auth_tag_size] = {0}; + int HMAC_length = cipher_text_length - g_auth_tag_size; + int res = hmac_sha256(column_encryption_key.get_mac_key(), g_auth_tag_size, + cipher_text + g_auth_tag_size, HMAC_length, authenticationTag); + if (res != 1) { + printf("ERROR(CLIENT): Fail to compute a keyed hash of a given text.\n"); + return 0; + } + /* 校验密文是否被篡改 */ + int cmp_result = my_memcmp(authenticationTag, cipher_text, g_auth_tag_size); + /* 解密数据 */ + int decryptedtext_len = decrypt(cipher_text + cipher_start_index, cipher_value_length, + column_encryption_key.get_encyption_key(), iv, decryptedtext, column_encryption_algorithm); + if (decryptedtext_len < 0) { + return 0; + } + decryptedtext[decryptedtext_len] = '\0'; + return decryptedtext_len; +} +``` diff --git "a/TestTasks/wang-luyang/openGauss\347\232\204\345\205\250\345\257\206\346\200\201\346\212\200\346\234\257\347\256\200\344\273\213.md" "b/TestTasks/wang-luyang/openGauss\347\232\204\345\205\250\345\257\206\346\200\201\346\212\200\346\234\257\347\256\200\344\273\213.md" new file mode 100644 index 0000000000000000000000000000000000000000..142ad3ebd3d1d40b0f771bd119facfb50b2954a0 --- /dev/null +++ "b/TestTasks/wang-luyang/openGauss\347\232\204\345\205\250\345\257\206\346\200\201\346\212\200\346\234\257\347\256\200\344\273\213.md" @@ -0,0 +1,36 @@ +## 简介 + +openGauss支持的数据全生命周期保护方案:全密态数据库机制。在这种机制下数据在客户端即被加密,从客户端传输到数据库内核,再到在内核中完成查询运算,到返回结果给客户端,数据始终处于加密状态,仅用户数据持有加解密所需的密钥。实现了数据拥有者和数据处理者的数据权属分离,有效规避第三方等威胁造成的数据泄漏风险。 + +## 密态等值查询的能力 + +* 数据加密:openGauss通过客户端驱动加密敏感数据,将密钥分为数据加密密钥和密钥加密密钥,客户端驱动保管密钥加密密钥,由此保证只有客户端才拥有解密数据密文的能力。 +* 数据检索:openGauss支持在用户无感知的情况下,为其提供对数据库密文进行等值检索的能力。在加密阶段,openGauss会将与加密相关的元数据存储在系统表中,当处理敏感数据时,客户端会自动检索加密相关元数据并对数据进行加解密。 +openGauss新增数据加解密表语法,通过采用驱动层过滤技术,在客户端的加密驱动中集成了SQL语法解析、密钥管理和敏感数据加解密等模块来处理相关语法。加密驱动源码流程如下。 + +![](/api/attachments/392177) + +用户执行SQL查询语句时,通过Pqexec函数执行SQL语句,SQL语句在发送之前首先进入run_pre_query函数,通过前端解析器解析涉及密态的语法。然后在run_pre_statement函数中通过分类器对语法标签进行识别,进入对应语法的处理逻辑。在不同的处理逻辑函数中,查找出要替换的数据参数,并存储在结构体StatementData中。最后通过replace_raw_values函数重构SQL语句,将其发送给服务端。在PqgetResult函数接收到从服务端返回的数据时,若是加密数据类型,则用deprocess_value函数对加密数据进行解密。接收完数据后还需要在run_post_query函数中刷新相应的缓存。 + +![](/api/attachments/392178) + +## 加密步骤 + +### 创建CMK客户端主密钥 + +```cpp +CREATE CLIENT MASTER KEY cmk_1 WITH (KEY_STORE = LOCALKMS , KEY_PATH = "kms_1" , ALGORITHM = RSA_2048); +``` + + +### 创建CEK列加密密钥 + +```cpp +CREATE COLUMN ENCRYPTION KEY cek_1 WITH VALUES (CLIENT_MASTER_KEY = cmk_1, ALGORITHM = AEAD_AES_256_CBC_HMAC_SHA256); +``` + +### 创建加密表 + +```cpp +CREATE TABLE creditcard_info (id_number int, name text encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), gender varchar(10) encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), salary float4 encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC),credit_card varchar(19) encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC)); +``` \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\344\272\213\345\212\241ID\345\210\206\351\205\215.md" "b/TestTasks/wang-luyang/\344\272\213\345\212\241ID\345\210\206\351\205\215.md" new file mode 100644 index 0000000000000000000000000000000000000000..5cd57d1838a6671f7a6c399029cc35ca53c66d5c --- /dev/null +++ "b/TestTasks/wang-luyang/\344\272\213\345\212\241ID\345\210\206\351\205\215.md" @@ -0,0 +1,30 @@ + +## 简介 + +为了在数据库内部区别不同的事务,openGauss会为每一个事务分配唯一的标识符,即事务id(transaction id,缩写xid),xid是uint64单调递增的序列。 + +## 64位xid及其分配 + +* 当事务插入时,会将事务信息写到元组头部的xmin,代表插入该元组的xid; +* 当事务进行更新和删除时,会将当前事务信息写到元组头部的xmax,代表删除该元组的xid。 + +当前事务id的分配采用的是uint64单调递增序列,为了节省空间以及兼容老的版本,当前设计是将元组头部的xmin/xmax分成两部分存储,元组头部的xmin/xmax均为uint32的数字;页面的头部存储64位的xid_base,为当前页面的xid_base。 + +元组结构如下, + +![](/api/attachments/397819) + +页面头结构如下 + +![](/api/attachments/397820) + +则对于每一条元组真正的xmin、xmax计算公式即为:元组头中xmin/xmax + 页面xid_base。 + +## 备注 + +当页面不断有更大的xid插入进来时,可能超过“xid_base + 232”,此时需要通过调节xid_base来满足所有元组的xmin/xmax都可以通过该值及元组头部的值计算出来。 + +为了使xid不消耗过快,openGauss当前只对写事务进行xid的分配,只读事务不会额外分配xid,也就是说并不是任何事务一开始都会分配xid,只有真正使用xid时才会去分配。 + +在分配子事务xid时,如果父事务还未分配xid,则会先给父事务分配xid,再给子事务分配xid,确保子事务的xid比父事务大。 + diff --git "a/TestTasks/wang-luyang/\344\272\213\345\212\241\345\271\266\345\217\221\346\216\247\345\210\266\346\246\202\350\277\260.md" "b/TestTasks/wang-luyang/\344\272\213\345\212\241\345\271\266\345\217\221\346\216\247\345\210\266\346\246\202\350\277\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..e3fd2e5b639f7b6d771032bd0a115319f1bd876e --- /dev/null +++ "b/TestTasks/wang-luyang/\344\272\213\345\212\241\345\271\266\345\217\221\346\216\247\345\210\266\346\246\202\350\277\260.md" @@ -0,0 +1,17 @@ + +## 概述 + +为保证并发执行事务的情况下数据库的ACID特性,opengauss使用了事务并发控制机制。 + +## 组成部分 + +* 事务状态机 + +* 事务ID分配及CLOG/CSNLOG + +* MVCC可见性判断机制 + +* 进程内多线程管理机制 + + + diff --git "a/TestTasks/wang-luyang/\344\272\213\345\212\241\346\234\272\345\210\266\346\246\202\350\277\260.md" "b/TestTasks/wang-luyang/\344\272\213\345\212\241\346\234\272\345\210\266\346\246\202\350\277\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..ac0c9d97473ce7ca3b47489a4f3f3e02b0ce3bdd --- /dev/null +++ "b/TestTasks/wang-luyang/\344\272\213\345\212\241\346\234\272\345\210\266\346\246\202\350\277\260.md" @@ -0,0 +1,33 @@ + +## 事务简介 + +事务是数据库操作的执行单位,需要满足最基本的ACID(原子性、一致性、隔离性、持久性)属性。 + +* 原子性:一个事务提交之后要么全部执行,要么全部不执行。 +* 一致性:事务的执行不能破坏数据库的完整性和一致性。 +* 隔离性:事务的隔离性是指在并发中,一个事务的执行不能被其他事务干扰。 +* 持久性:一旦事务完成提交,那么它对数据库的状态变更就会永久保存在数据库中。 + +## 架构分析 + +![](/api/attachments/397786) + +主要组成部分如下: + +* 事务管理器:事务系统的中枢,是一个有限循环状态机,通过接受外部系统的命令并根据当前事务所处的状态决定事务的下一步执行过程。 + +* 日志管理器:用来记录事务执行的状态以及数据变化的过程,包括事务提交日志(CLOG)、事务提交序列日志(CSNLOG)以及事务日志(XLOG)。其中CLOG日志只用来记录事务执行的结果状态,CSNLOG记录日志提交的顺序,用于可见性判断;XLOG是数据的redo日志,用于恢复及持久化。 + +* 线程管理机制:系统开辟一片内存区域记录所有线程的事务信息,任何一个线程可以通过访问该区域获取其他事务的状态信息。 + +* MVCC机制:openGauss系统中,事务执行读流程结合各事务提交的CSN序列号,采用了多版本并发控制机制,实现了元组的读和写互不阻塞。 + +* 锁管理器:实现系统的写并发控制,通过锁机制来保证事务写流程的隔离性。 + + + + + + + + diff --git "a/TestTasks/wang-luyang/\344\272\213\345\212\241\347\212\266\346\200\201\346\234\272\344\271\213\344\270\212\345\261\202\347\212\266\346\200\201\346\234\272.md" "b/TestTasks/wang-luyang/\344\272\213\345\212\241\347\212\266\346\200\201\346\234\272\344\271\213\344\270\212\345\261\202\347\212\266\346\200\201\346\234\272.md" new file mode 100644 index 0000000000000000000000000000000000000000..df4adfcb90523ae855e5f43b7b3311bf5c975adb --- /dev/null +++ "b/TestTasks/wang-luyang/\344\272\213\345\212\241\347\212\266\346\200\201\346\234\272\344\271\213\344\270\212\345\261\202\347\212\266\346\200\201\346\234\272.md" @@ -0,0 +1,68 @@ + +## 简介 + +openGauss将事务系统分为上层(事务块TBlockState)以及底层(TransState)两个层次。 + +通过分层的设计,在处理上层业务时可以屏蔽具体细节,实现灵活支持客户端各类事务执行语句(`BEGIN/START TRANSACTION/COMMIT/ROLLBACK/END`)。 + +* 事务块TBlockState:客户端query的状态,用于提高用户操作数据的灵活性,用事务块的形式支持在一个事务中执行多条query语句。 + +* 底层事务TransState:内核端视角,记录了整个事务当前处于的具体状态。 + + +## 事务上层状态机 + +状态的数据结构如下: + +```cpp +typeset enum TBlockState +{ +/* 不在事务块中的状态:单条SQL语句 */ +TBLOCK_DEFAULT,/* 事务块缺省状态 */ +TBLOCK_STARTED,/*执行单条query 语句*/ + +/* 处于事务块中的状态:一个事务包含多条语句 */ +TBLOCK_BEGIN,/* 遇到事务开始命令BEGIN/START TRANSACTION */ +TBLOCK_INPROGRESS,/* 表明正在事务块处理过程中*/ +TBLOCK_END,/ *遇到事务结束命令END/COMMIT */ +TBLOCK_ABORT,/* 事务块内执行报错,等待客户端执行ROLLBACK */ +TBLOCK_ABORT_END,/ *在事务块内执行报错后,接收客户端执行ROLLBACK */ +TBLOCK_ABORT_PENDING,/* 事务块内执行成功,接收客户端执行ROLLBACK(期望事务回滚)*/ +TBLOCK_PREPARE,/ *两阶段提交事务,收到PREPARE TRANSACTION命令*/ +/* 子事务块状态,与上述事务块状态类似 */ +TBLOCK_SUBBEGIN,/* 遇到子事务开始命令SAVEPOINT */ +TBLOCK_SUBINPROGRESS,/* 表明正在子事务块处理过程中*/ +TBLOCK_SUBRELEASE,/* 遇到子事务结束命令RELEASE SAVEPOINT */ +TBLOCK_SUBCOMMIT,/* 遇到事务结束命令END/COMMIT 从最底层的子事务递归的提交到最顶层事务*/ +TBLOCK_SUBABORT,/* 子事务块内执行报错,等待客户端ROLLBACK TO/ROLLBACK */ +TBLOCK_SUBABORT_END,/* 在子事务块内执行报错后,接收到客户端ROLLBACK TO上层子事务/ROLLBACK */ +TBLOCK_SUBABORT_PENDING,/* 子事务块内执行成功,接收客户端执行的ROLLBACK TO上层子事务/ROLLBACK */ +TBLOCK_SUBRESTART,/* 子事务块内执行成功,收到ROLLBACK TO当前子事务*/ +TBLOCK_SUBABORT_RESTART/* 子事务块内执行报错后,接收到ROLLBACK TO当前子事务*/ +} TBlockState; + +``` + +显式事务块的状态机及相应的转换函数如下: + +![](/api/attachments/397810) + +上图中事务状态相对应的事务状态机结构体中的值如下表所示 + +![](/api/attachments/397811) + +在无异常情形下,一个事务块的状态机按照`默认(TBLOCK_DEFAULT)->已开始(TBLOCK_STARTED)->事务块开启(TBLOCK_BEGIN)->事务块运行中(TBLOCK_INPROGRESS)->事务块结束(TBLOCK_END)->默认(TBLOCK_DEFAULT)`循环。 + +剩余的状态机是在上述正常场景下的各个状态点的异常处理分支。 + +常见的几种出错情况如下: + +1. 在进入事务块运行中(TBLOCK_INPROGRESS)之前出错,因为事务还没有开启,直接报错并回滚,清理资源回到默认(TBLOCK_DEFAULT)状态。 + +2. 在事务块运行中(TBLOCK_INPROGRESS)出错分为2种情形。事务执行失败:事务块运行中(TBLOCK_INPROGRESS)->回滚(TBLOCK_ABORT)->回滚结束(TBLOCK_ABORT_END)->默认(TBLOCK_DEFAULT);用户手动回滚执行成功的事务:事务块运行中(TBLOCK_INPROGRESS)->回滚等待(TBLOCK_ABORT_PENDING)->默认(TBLOCK_DEFAULT)。 + +3. 在用户执行COMMIT语句时出错:事务块结束(TBLOCK_END)->默认(TBLOCK_DEFAULT)。事务开始后离开默认(TBLOCK_DEFAULT)状态,事务完全结束后回到默认(TBLOCK_DEFAULT)状态。 + +另外,openGauss同时还支持隐式事务块,当客户端执行单条SQL语句时可以自动提交,其状态机相对比较简单:按照默认(TBLOCK_DEFAULT)->已开始(TBLOCK_STARTED)->默认(TBLOCK_DEFAULT)循环。 + + diff --git "a/TestTasks/wang-luyang/\344\272\213\345\212\241\347\212\266\346\200\201\346\234\272\344\271\213\344\272\213\345\212\241\345\272\225\345\261\202\347\212\266\346\200\201.md" "b/TestTasks/wang-luyang/\344\272\213\345\212\241\347\212\266\346\200\201\346\234\272\344\271\213\344\272\213\345\212\241\345\272\225\345\261\202\347\212\266\346\200\201.md" new file mode 100644 index 0000000000000000000000000000000000000000..90fa0cac8f85a49dd23da088f1bdbcdd4a257b3e --- /dev/null +++ "b/TestTasks/wang-luyang/\344\272\213\345\212\241\347\212\266\346\200\201\346\234\272\344\271\213\344\272\213\345\212\241\345\272\225\345\261\202\347\212\266\346\200\201.md" @@ -0,0 +1,29 @@ + +## 数据结构 + +事务状态使用TransState结构体表示,其代码如下: + +```cpp +typedef enum TransState +{ +TRANS_DEFAULT,/* 当前为空闲缺省状态,无事务开启*/ +TRANS_START,/* 事务正在开启*/ +TRANS_INPROGRESS,/* 事务开始完毕,进入事务运行中*/ +TRANS_COMMIT,/* 事务正在提交*/ +TRANS_ABORT,/* 事务正在回滚*/ +TRANS_PREPARE/* 两阶段提交事务进入PREPARE TRANSACTION阶段*/ +} TransState; +``` + +## 示意图 + +![](/api/attachments/397815) + +## 事务处理过程中的状态 + +1. 在事务开启前事务状态为TRANS_DEFAULT。 +2. 事务开启过程中事务状态为TRANS_START。 +3. 事务成功开启后一直处于TRANS_INPROGRESS。 +4. 事务结束/回滚的过程中为TARNS_COMMIT/ TRANS_ABORT。 +5. 事务结束后事务状态回到TRANS_DEFAULT。 + diff --git "a/TestTasks/wang-luyang/\344\272\213\345\212\241\347\212\266\346\200\201\350\275\254\346\215\242\347\233\270\345\205\263\345\207\275\346\225\260\344\273\213\347\273\215.md" "b/TestTasks/wang-luyang/\344\272\213\345\212\241\347\212\266\346\200\201\350\275\254\346\215\242\347\233\270\345\205\263\345\207\275\346\225\260\344\273\213\347\273\215.md" new file mode 100644 index 0000000000000000000000000000000000000000..40407ee1c0573df1d61899f2b6940beae4343acc --- /dev/null +++ "b/TestTasks/wang-luyang/\344\272\213\345\212\241\347\212\266\346\200\201\350\275\254\346\215\242\347\233\270\345\205\263\345\207\275\346\225\260\344\273\213\347\273\215.md" @@ -0,0 +1,18 @@ + + +## 事务处理子函数 + +作用:**根据当前事务上层状态机,对事务的资源进行相应的申请、回收及清理。** + +![](/api/attachments/397816) + +## 处理函数 + +作用:**根据相应的状态机调用子函数** + +![](/api/attachments/397817) + +## 上层事务状态机控制函数 + +![](/api/attachments/397818) + diff --git "a/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\351\233\206\346\200\273\347\273\223.md" "b/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\351\233\206\346\200\273\347\273\223.md" new file mode 100644 index 0000000000000000000000000000000000000000..1cad16374018435b801fbf708a3563cbd1f3239e --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\351\233\206\346\200\273\347\273\223.md" @@ -0,0 +1,649 @@ +# 元命令集-总结篇 +``` +--gsql +gsql是openGauss提供在命令行下运行的数据库连接工具,可以通过此工具连接服务器并对其进行操作和维护, +除了具备操作数据库的基本功能,gsql还提供了若干高级特性,便于用户使用。 +``` +## 什么是元命令? +所谓元命令就是在gsql里输入的任何以不带引号的反斜杠开头的命令。 + +> - 一个gsql元命令的格式是反斜杠后面紧跟一个动词,然后是任意参数。参数命令动词和其他参数以任意个空白字符间隔。 +> - 要在参数里面包含空白,必须用单引号把它引起来。要在这样的参数里包含单引号,可以在前面加一个反斜杠。任何包含在单引号里的内容都会被进一步进行类似C语言的替换:\n(新行)、\t(制表符)、\b(退格)、\r(回车)、\f(换页)、\digits(八进制表示的字符)、\xdigits(十六进制表示的字符)。 +> - 用”“包围的内容被当做一个命令行传入shell。该命令的输出(删除了结尾的新行)被当做参数值。 +> - 如果不带引号的参数以冒号(:)开头,它会被当做一个gsql变量,并且该变量的值最终会成为真正的参数值。 +> - 有些命令以一个SQL标识的名称(比如一个表)为参数。这些参数遵循SQL语法关于双引号的规则:不带双引号的标识强制转换成小写,而双引号保护字母不进行大小写转换,并且允许在标识符中使用空白。在双引号中,成对的双引号在结果名称中分析成一个双引号。比如,FOO”BAR”BAZ解析成fooBARbaz;而”Aweird”“name”解析成A +> weird”name。 +> - 对参数的分析在遇到另一个不带引号的反斜杠时停止。这里会认为是一个新的元命令的开始。特殊的双反斜杠序列(\\)标识参数的结尾并将继续分析后面的SQL语句(如果存在)。这样SQL和gsql命令可以自由的在一行里面混合。但是在任何情况下,一条元命令的参数不能延续超过行尾。 + +### 1.一般的元命令 + +``` +--显示openGauss的版本和版权信息。 +\copyright + +--执行查询(并将结果发送到文件或管道)。 +\g [FILE] or + +--给出指定SQL语句的语法帮助。 +\h(\help) [NAME] +注:如果没有给出NAME,gsql将列出可获得帮助的所有命令。如果NAME是一个星号(*),则显示所有SQL语句的语法帮助。 + +--控制并发执行开关。 +\parallel [on [num]|off] +注: +on:打开控制并发执行开关,且最大并发数为num。 +off:关闭控制并发执行开关。 +num的默认值:1024。 + +--退出gsql程序。在一个脚本文件里,只在脚本终止的时候执行。 +\q +``` +### 2.查询缓存区元命令 + +``` +--使用外部编辑器编辑查询缓冲区(或者文件)。 +\e [FILE] [LINE] + +--使用外部编辑器编辑函数定义。如果指定了LINE(即行号),则光标会指到函数体的指定行。 +\ef [FUNCNAME [LINE]] + +--打印当前查询缓冲区到标准输出。 +\p + +--重置(或清空)查询缓冲区。 +\r + +--将当前查询缓冲区输出到文件。 +\w FILE +``` +### 3.输入/输出元命令 + +``` +--SQL COPY命令,读取或写入文件,并在服务器和本地文件系统之间路由数据。 +\copy { table [ ( column_list ) ] | ( query ) } { from | to } { filename | stdin | stdout | pstdin | pstdout } [ with ] [ binary ] [ oids ] [ delimiter [ as ] ‘character’ ] [ null [ as ] ‘string’ ] [ csv [ header ] [ quote [ as ] ‘character’ ] [ escape [ as ] ‘character’ ] [ force quote column_list | * ] [ force not null column_list ] ] + +注:\COPY只适合小批量,格式良好的数据导入,不会对非法字符进行预处理,也无容错能力。导入数据应优先选择COPY。在任何psql客户端登录数据库成功后可以执行导入导出数据, 这是一个运行SQL COPY命令的操作,但不是读取或写入指定文件的服务器。 这意味着文件的可访问性和权限是本地用户的权限,而不是服务器的权限,并且不需要数据库初始化用户权限。 + +--把字符串写到标准输出。 +\echo [STRING] + +--从文件FILE中读取内容,并将其当作输入,执行查询。 +\i FILE + +--执行加密文件中的命令。 +\i+ FILE KEY + +--和\i类似,只是相对于存放当前脚本的路径。 +\ir FILE + +--和\i+类似,只是相对于存放当前脚本的路径。 +\ir+ FILE KEY + +--把所有的查询结果发送到文件里。 +\o [FILE] + +-- +把字符串写到查询结果输出流里。 +\qecho [STRING] +``` +### 4.显示信息元命令 +指令中:选项S表示显示系统对象,+表示显示对象附加的描述信息。PATTERN用来指定要被显示的对象名称。 + +``` +--列出当前search_path中模式下所有的表、视图和序列。 +\d[S+] +注:当search_path中不同模式存在同名对象时,只显示search_path中位置靠前模式下的同名对象。 +eg:列出当前search_path中模式下所有的表、视图和序列: +postgres=# \d + +--列出指定表、视图和索引的结构。 +\d[S+] NAME +eg:假设存在表a,列出指定表a的结构。 +postgres=# \dtable+ a + +--列出所有表、视图和索引。 +\d+ [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的表、视图和索引。 +eg:列出所有名称以f开头的表、视图和索引。 +postgres=# \d+ f + +--列出所有可用的聚集函数,以及它们操作的数据类型和返回值类型。 +\da[S] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的聚集函数。 +eg:列出所有名称以f开头可用的聚集函数,以及它们操作的数据类型和返回值类型。 +postgres=# \da f + +--列出所有可用的表空间。 +\db[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的表空间。 +eg:列出所有名称以p开头的可用表空间。 +postgres=# \db p + +--列出所有字符集之间的可用转换。 +\dc[S+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的转换。 +eg:列出所有字符集之间的可用转换。 +postgres=# \dc + +--列出所有类型转换。 +\dC[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的转换。 +eg:列出所有名称以c开头的类型转换。 +postgres=# \dC c + +--显示所有匹配PATTERN的描述。 +\dd[S] [PATTERN] +注:如果没有给出参数,则显示所有可视对象。“对象”包括:聚集、函数、操作符、类型、 +关系(表、视图、索引、序列、大对象)、规则。 +eg:列出所有可视对象。 +postgres=# \dd + +--显示所有默认的使用权限。 +\ddp [PATTERN] +注:如果指定了PATTERN,只显示名称匹配PATTERN的使用权限。 +eg:列出所有默认的使用权限。 +postgres=# \ddp + +--列出所有可用域。 +\dD[S+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的域。 +eg:列出所有可用域。 +postgres=# \dD + +--列出所有的Data Source对象。 +\ded[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的对象。 +eg:列出所有的Data Source对象。 +postgres=# \ded + +--列出所有的外部表。 +\det[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的表。 +eg:列出所有的外部表。 +postgres=# \det + +--列出所有的外部服务器。 +\des[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的服务器。 +eg:列出所有的外部服务器。 +postgres=# \des + +--列出用户映射信息。 +\deu[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的信息。 +eg:列出用户映射信息。 +postgres=# \deu + +--列出封装的外部数据。 +\dew[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的数据。 +eg:列出封装的外部数据。 +postgres=# \dew + +--列出所有可用函数,以及它们的参数和返回的数据类型。 +\df[antw][S+] [PATTERN] +注:a代表聚集函数,n代表普通函数,t代表触发器,w代表窗口函数。如果声明了PATTERN,只显示 +名称匹配PATTERN的函数。 +eg:列出所有可用函数,以及它们的参数和返回的数据类型。 +postgres=# \df + +--列出所有的文本搜索配置信息。 +\dF[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的配置信息。 +列出所有的文本搜索配置信息 +postgres=# \dF+ + +--列出所有的文本搜索字典。 +\dFd[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的字典。 +eg:列出所有的文本搜索字典。 +postgres=# \dFd + +--列出所有的文本搜索分析器。 +\dFp[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的分析器。 +eg:列出所有的文本搜索分析器。 +postgres=# \dFp + +--列出所有的文本搜索模板。 +\dFt[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的模板。 +eg:列出所有的文本搜索模板。 +postgres=# \dFt + +--列出所有数据库角色。 +\dg[+] [PATTERN] +注:因为用户和群组的概念被统一为角色,所以这个命令等价于\du。为了和以前兼容,所以保留两个 +命令。如果指定了PATTERN,只显示名称匹配PATTERN的角色。 +eg:列出名称为‘j_e’所有数据库角色。 +postgres=# \dg j?e + +--显示一个大对象的列表。 +\dl +注:\lo_list的别名, +eg:列出所有的大对象。 +postgres=# \dl + +列出可用的程序语言。 +\dL[S+] [PATTERN] +注:如果指定了PATTERN,只列出名称匹配PATTERN的语言。 +eg:列出可用的程序语言。 +postgres=# \dL + +--列出所有的模式(名称空间)。 +\dn[S+] [PATTERN] +注:如果声明了PATTERN,只列出名称匹配PATTERN的模式名。缺省时,只列出用户创建的模式。 +eg:列出所有名称以d开头的模式以及相关信息。 +postgres=# \dn+ d + +--列出所有可用的操作符,以及它们的操作数和返回的数据类型。 +\do[S] [PATTERN] +注:如果声明了PATTERN,只列出名称匹配PATTERN的操作符。缺省时,只列出用户创建的操作符。 +eg:列出所有可用的操作符,以及它们的操作数和返回的数据类型。 +postgres=# \do + + +--列出排序规则。 +\dO[S+] [PATTERN] +注:如果声明了PATTERN,只列出名称匹配PATTERN的规则。缺省时,只列出用户创建的规则。 +eg:列出排序规则。 +postgres=# \dO + +--列出一列可用的表、视图以及相关的权限信息。 +\dp [PATTERN] +注:如果指定了PATTERN,只列出名称匹配PATTERN的表、视图。 +eg:列出一列可用的表、视图以及相关的权限信息。 +postgres=# \dp + +--列出所有修改过的配置参数。 +\drds [PATTERN1 [PATTERN2]] + +注:这些设置可以是针对角色的、针对数据库的或者同时针对两者的。PATTERN1和PATTERN2表示 +要列出的角色PATTERN和数据库PATTERN。如果声明了PATTERN,只列出名称匹配PATTERN的规 +则。缺省或指定时,则会列出所有设置。 +eg:列出postgres数据库所有修改过的配置参数。 +postgres=# \drds postgres + +--列出所有的数据类型。 +\dT[S+] [PATTERN] +注:如果指定了PATTERN,只列出名称匹配PATTERN的类型。 +eg:列出所有的数据类型。 +postgres=# \dT + +--列出所有数据库角色。 +\du[+] [PATTERN] +注:因为用户和群组的概念被统一为角色,所以这个命令等价于\dg。为了和以前兼容,所以保留两个命令。如果指定了PATTERN,则只列出名称匹配PATTERN的角色。 +eg:列出所有数据库角色。 +postgres=# \du + +--列出对象 +\dE[S+] [PATTERN] +\di[S+] [PATTERN] +\ds[S+] [PATTERN] +\dt[S+] [PATTERN] +\dv[S+] [PATTERN] +注:这一组命令,字母E,i,s,t和v分别代表着外部表,索引,序列,表和视图。可以以任意顺序指 +定其中一个或者它们的组合来列出这些对象。例如:\dit列出所有的索引和表。在命令名称后面追加 ++,则每一个对象的物理尺寸以及相关的描述也会被列出。如果指定了PATTERN,只列出名称匹配该 +PATTERN的对象。默认情况下只会显示用户创建的对象。通过PATTERN或者S修饰符可以把系统对 +象包括在内。 +eg:列出所有的索引和视图。 +postgres=# \div + +--列出安装数据库的扩展信息。 +\dx[+] [PATTERN] +注:如果指定了PATTERN,则只列出名称匹配PATTERN的扩展信息。 +eg:列出安装数据库的扩展信息。 +postgres=# \dx + +--列出服务器上所有数据库的名称、所有者、字符集编码以及使用权限。 +\l[+] +eg:列出服务器上所有数据库的名称、所有者、字符集编码以及使用权限。 +postgres=# \l + +--显示函数的定义。 +\sf[+] FUNCNAME +注:对于带圆括号的函数名,需要在函数名两端添加双引号,并且在双引号后面加上参数类型列表。 +参数类型列表两端添加圆括号。 +eg:假设存在函数function_a和函数名带圆括号的函数func()name,列出函数的定义。 +postgres=# \sf function_a +postgres=# \sf +“func()name”(argtype1, argtype2) + +--列出数据库中所有表、视图和序列,以及它们相关的访问特权。 +\z [PATTERN] +注:如果给出任何pattern ,则被当成一个正则表达式,只显示匹配的表、视图、序列。 +eg:列出数据库中所有表、视图和序列,以及它们相关的访问特权。 +postgres=# \z +``` +### 5.格式化元命令 + +``` +参数 参数说明 + +--对齐模式和非对齐模式之间的切换。 +\a + +--把正在打印的表的标题设置为一个查询的结果或者取消这样的设置。 +\C [STRING] + +--对于不对齐的查询输出,显示或者设置域分隔符。 +\f [STRING] + +--若当前模式为文本格式,则切换为HTML输出格式。若当前模式为HTML格式,则切换回文本格式。 +\H + +设置影响查询结果表输出的选项。NAME的取值见表7。 +\pset NAME [VALUE] + +切换输出的字段名的信息和行计数脚注。 +\t [on|off] + +指定在使用HTML输出格式时放在table标签里的属性。如果参数为空,不设置。 +\T [STRING] + +切换扩展行格式。 +\x [on|off|auto] +``` +### 6.连接元命令 + +``` +参数 参数说明 取值范围 + +--连接到一个新的数据库(当前数据库为postgres)。 +\c[onnect] [DBNAME|- USER|- HOST|- PORT|-] +注:当数据库名称长度超过63个字节时,默认前63个字节有效,连接到前63个字节对应的数据库,但是gsql的命令提示 +符中显示的数据库对象名仍为截断前的名称。重新建立连接时,如果切换数据库登录用户,将可能会出现交互式输入, +要求输入新用户的连接密码。该密码最长长度为999字节,受限于GUC参数password_max_length的最大值。 + +--设置客户端字符编码格式。 +\encoding [ENCODING] +注:不带参数时,显示当前的编码格式。 + +--输出当前连接的数据库的信息。 +\conninfo +``` +### 7.操作系统元命令 + +参数 参数说明 取值范围 +``` +-- +切换当前的工作目录。 +\cd [DIR] +注:绝对路径或相对路径,且满足操作系统路径命名规则。 + +--设置环境变量NAME为VALUE,如果没有给出VALUE值,则不设置环境变量。 +\setenv NAME [VALUE] + +--以毫秒为单位显示每条SQL语句的执行时间。 +\timing [on|off] +注:on表示打开显示。off表示关闭显示。 + +--返回到一个单独的Unix shell或者执行Unix命令COMMAND。 +! [COMMAND] +``` +### 8.变量元命令 + +``` +参数 参数说明 + +--提示用户用文本格式来指定变量名称。 +\prompt [TEXT] NAME + +--设置内部变量NAME为VALUE或者如果给出了多于一个值,设置为所有这些值的连接结果。如果没有给出第二个参 +数,只设变量不设值。 +\set [NAME [VALUE]] +注:有一些常用变量被gsql特殊对待,它们是一些选项设置,通常所有特殊对待的变量都是由大写字母组成(可能还有数字和下划线)。 + +--不设置(或删除)gsql变量名。 +\unset NAME +``` +### 9.大对象元命令 + +``` +--显示一个目前存储在该数据库里的所有openGauss大对象和提供给他们的注释。 +\lo_list +``` +### 附1:Linux常见信息显示命令: +#### 1、常用的系统信息显示命令 + +``` +--显示主机名称 +hostname + +--显示操作系统信息 +uname + +--显示系统启动信息 +dmesg + +--显示系统加载的内核模块 +lsmod + +--显示系统时间(cal 可以显示系统时间的日历) +date + +--显示系统环境变量 +env + +--显示当前语言环境(cat /etc/sysconfig/i18n) +locale + +--显示操作系统版本(head -1 /etc/issue) +cat /etc/redhat-release : + +--显示CPU信息 +cat /proc/cpuinfo + +--显示PCI/USB接口信息 +lspci/lsusb + +--显示系统已安装的所有软件包 +rpm -qa +``` + +#### 2、常用的资源显示命令 + +``` +--显示当前系统中耗费资源最多的进程 +top + +--显示当前内存的使用情况(cat /proc/meminfo) +free + +--显示指定的文件(目录)已使用的磁盘空间的总量 +du -h + +--显示文件系统磁盘空间的使用情况 +df -h + +--显示系统运行时间、用户数、负载 +uptime + +--查看所有分区 +fdisk -l + +--查看已经挂装的分区 +mount + +--查看所有交换分区 +swapon -s + +--查看所有进程 +ps -ef + +--显示进程树 +pstree + +--列出所有系统服务 +chkconfig --list +``` + +#### 3、常用的用户相关显示命令 + +``` +--显示在线登录用户 +who、w + +--显示用户自己的身份 +whoami + +--显示用户当前使用的终端 +tty + +--显示当前用户的id信息 +id + +--显示当前用户属于哪些组 +groups + +--查看用户登录日志 +last + +--查看当前用户的计划任务 +crontab -l +``` +### 附2:权限的参数说明 + +``` +参数 参数说明 + +r +SELECT:允许对指定的表、视图读取数据。 + +w +UPDATE:允许对指定表更新字段。 + +a +INSERT:允许对指定表插入数据。 + +d +DELETE:允许删除指定表中的数据。 + +D +TRUNCATE:允许清理指定表中的数据。 + +x +REFERENCES:允许创建外键约束。由于当前不支持外键,所以该参数暂不生效。 + +t +TRIGGER:允许在指定表上创建触发器。 + +X +EXECUTE:允许使用指定的函数,以及利用这些函数实现的操作符。 + +U +USAGE: + - 对于过程语言,允许用户在创建函数时,指定过程语言。 + - 对于模式,允许访问包含在指定模式中的对象。 + - 对于序列,允许使用nextval函数。 + +C +CREATE: + - 对于数据库,允许在该数据库里创建新的模式。 + - 对于模式,允许在该模式中创建新的对象。 + - 对于表空间,允许在其中创建表,以及允许创建数据库和模式的时候把该表空间指定为其缺省表空间。 + +c +CONNECT:允许用户连接到指定的数据库。 + +T +TEMPORARY:允许创建临时表。 + +arwdDxt +ALL PRIVILEGES:一次性给指定用户/角色赋予所有可赋予的权限。 + +* +给前面权限的授权选项。 +``` +### 附3:可调节的打印选项 + + - border + +value必须是一个数字。通常这个数字越大,表的边界就越宽线就越多,但是这个取决于特定的格式。 +在HTML格式下,取值范围为大于0的整数。 +在其他格式下,取值范围: +0:无边框 +1:内部分隔线 +2:台架 + + - expanded (或x) + +在正常和扩展格式之间切换。 +当打开扩展格式时,查询结果用两列显示,字段名称在左、数据在右。这个模式在数据无法放进通常的”水平”模式的屏幕时很有用。 +在正常格式下,当查询输出的格式比屏幕宽时,用扩展格式。正常格式只对aligned和wrapped格式有用。 + + - fieldsep + +声明域分隔符来实现非对齐输出。这样就可以创建其他程序希望的制表符或逗号分隔的输出。要设置制表符域分隔符,键入\pset fieldsep ‘\t’。缺省域分隔符是 ‘|’ (竖条符)。 + + - fieldsep_zero + +声明域分隔符来实现非对齐输出到零字节。 + + - footer + +用来切换脚注。 + + - format + +设置输出格式。允许使用唯一缩写(这意味着一个字母就够了)。 +取值范围: + +``` +unaligned:写一行的所有列在一条直线上中,当前活动字段分隔符分隔。 +aligned:此格式是标准的,可读性最好的文本输出。 +wrapped:类似aligned,但是包装跨行的宽数据值,使其适应目标字段的宽度输出。 +html:把表输出为可用于文档里的对应标记语言。输出不是完整的文档。 +latex:把表输出为可用于文档里的对应标记语言。输出不是完整的文档。 +troff-ms:把表输出为可用于文档里的对应标记语言。输出不是完整的文档。 +``` + + - null + +打印一个字符串,用来代替一个null值。 +缺省是什么都不打印,这样很容易和空字符串混淆。 + + - numericlocale + +切换分隔小数点左边的数值的区域相关的分组符号。 + +``` +on:显示指定的分隔符。 +off:不显示分隔符。 +忽略此参数,显示默认的分隔符。 +``` + + - pager + +控制查询和gsql帮助输出的分页器。如果设置了环境变量PAGER,输出将被指向到指定程序,否则使用系统缺省。 + +``` +on:当输出到终端且不适合屏幕显示时,使用分页器。 +off:不使用分页器。 +always:当输出到终端无论是否符合屏幕显示时,都使用分页器。 +``` + + - recordsep + +声明在非对齐输出格式时的记录分隔符。 + + - recordsep_zero + +声明在非对齐输出到零字节时的记录分隔符。 + + - tableattr(或T) + +声明放在html输出格式中HTML table标签的属性(例如:cellpadding或bgcolor)。 +注意:这里可能不需要声明border,因为已经在\pset border里用过了。如果没有给出value,则不设置表的属性。 + + - title + +为随后打印的表设置标题。这个可以用于给输出一个描述性标签。如果没有给出value,不设置标题。 + + - tuples_only (或者t) + +在完全显示和只显示实际的表数据之间切换。完全显示将输出像列头、标题、各种脚注等信息。在tuples_only模式下,只显示实际的表数据。 + + + diff --git "a/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\344\270\200\357\274\211.md" "b/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\344\270\200\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..0e3ebe254bff83dc067ba5b100d75f55fd24728d --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\344\270\200\357\274\211.md" @@ -0,0 +1,83 @@ +#### 什么是元命令? +所谓元命令就是在gsql里输入的任何以不带引号的反斜杠开头的命令。 + +> - 一个gsql元命令的格式是反斜杠后面紧跟一个动词,然后是任意参数。参数命令动词和其他参数以任意个空白字符间隔。 +> - 要在参数里面包含空白,必须用单引号把它引起来。要在这样的参数里包含单引号,可以在前面加一个反斜杠。任何包含在单引号里的内容都会被进一步进行类似C语言的替换:\n(新行)、\t(制表符)、\b(退格)、\r(回车)、\f(换页)、\digits(八进制表示的字符)、\xdigits(十六进制表示的字符)。 +> - 用”“包围的内容被当做一个命令行传入shell。该命令的输出(删除了结尾的新行)被当做参数值。 +> - 如果不带引号的参数以冒号(:)开头,它会被当做一个gsql变量,并且该变量的值最终会成为真正的参数值。 +> - 有些命令以一个SQL标识的名称(比如一个表)为参数。这些参数遵循SQL语法关于双引号的规则:不带双引号的标识强制转换成小写,而双引号保护字母不进行大小写转换,并且允许在标识符中使用空白。在双引号中,成对的双引号在结果名称中分析成一个双引号。比如,FOO”BAR”BAZ解析成fooBARbaz;而”Aweird”“name”解析成A +> weird”name。 +> - 对参数的分析在遇到另一个不带引号的反斜杠时停止。这里会认为是一个新的元命令的开始。特殊的双反斜杠序列(\\)标识参数的结尾并将继续分析后面的SQL语句(如果存在)。这样SQL和gsql命令可以自由的在一行里面混合。但是在任何情况下,一条元命令的参数不能延续超过行尾。 + +### 1.一般的元命令 + +``` +--显示openGauss的版本和版权信息。 +\copyright + +--执行查询(并将结果发送到文件或管道)。 +\g [FILE] or + +--给出指定SQL语句的语法帮助。 +\h(\help) [NAME] +注:如果没有给出NAME,gsql将列出可获得帮助的所有命令。如果NAME是一个星号(*),则显示所有SQL语句的语法帮助。 + +--控制并发执行开关。 +\parallel [on [num]|off] +注: +on:打开控制并发执行开关,且最大并发数为num。 +off:关闭控制并发执行开关。 +num的默认值:1024。 + +--退出gsql程序。在一个脚本文件里,只在脚本终止的时候执行。 +\q +``` +### 2.查询缓存区元命令 + +``` +--使用外部编辑器编辑查询缓冲区(或者文件)。 +\e [FILE] [LINE] + +--使用外部编辑器编辑函数定义。如果指定了LINE(即行号),则光标会指到函数体的指定行。 +\ef [FUNCNAME [LINE]] + +--打印当前查询缓冲区到标准输出。 +\p + +--重置(或清空)查询缓冲区。 +\r + +--将当前查询缓冲区输出到文件。 +\w FILE +``` +### 3.输入/输出元命令 + +``` +--SQL COPY命令,读取或写入文件,并在服务器和本地文件系统之间路由数据。 +\copy { table [ ( column_list ) ] | ( query ) } { from | to } { filename | stdin | stdout | pstdin | pstdout } [ with ] [ binary ] [ oids ] [ delimiter [ as ] ‘character’ ] [ null [ as ] ‘string’ ] [ csv [ header ] [ quote [ as ] ‘character’ ] [ escape [ as ] ‘character’ ] [ force quote column_list | * ] [ force not null column_list ] ] + +注:\COPY只适合小批量,格式良好的数据导入,不会对非法字符进行预处理,也无容错能力。导入数据应优先选择COPY。在任何psql客户端登录数据库成功后可以执行导入导出数据, 这是一个运行SQL COPY命令的操作,但不是读取或写入指定文件的服务器。 这意味着文件的可访问性和权限是本地用户的权限,而不是服务器的权限,并且不需要数据库初始化用户权限。 + +--把字符串写到标准输出。 +\echo [STRING] + +--从文件FILE中读取内容,并将其当作输入,执行查询。 +\i FILE + +--执行加密文件中的命令。 +\i+ FILE KEY + +--和\i类似,只是相对于存放当前脚本的路径。 +\ir FILE + +--和\i+类似,只是相对于存放当前脚本的路径。 +\ir+ FILE KEY + +--把所有的查询结果发送到文件里。 +\o [FILE] + +-- +把字符串写到查询结果输出流里。 +\qecho [STRING] +``` + diff --git "a/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\344\270\211\357\274\211.md" "b/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\344\270\211\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..57354d4239644f6ea070850e263f327ce7bb5030 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\344\270\211\357\274\211.md" @@ -0,0 +1,119 @@ +### 5.格式化元命令 + +``` +参数 参数说明 + +--对齐模式和非对齐模式之间的切换。 +\a + +--把正在打印的表的标题设置为一个查询的结果或者取消这样的设置。 +\C [STRING] + +--对于不对齐的查询输出,显示或者设置域分隔符。 +\f [STRING] + +--若当前模式为文本格式,则切换为HTML输出格式。若当前模式为HTML格式,则切换回文本格式。 +\H + +设置影响查询结果表输出的选项。NAME的取值见表7。 +\pset NAME [VALUE] + +切换输出的字段名的信息和行计数脚注。 +\t [on|off] + +指定在使用HTML输出格式时放在table标签里的属性。如果参数为空,不设置。 +\T [STRING] + +切换扩展行格式。 +\x [on|off|auto] +``` +### 6.连接元命令 + +``` +参数 参数说明 取值范围 + +--连接到一个新的数据库(当前数据库为postgres)。 +\c[onnect] [DBNAME|- USER|- HOST|- PORT|-] +注:当数据库名称长度超过63个字节时,默认前63个字节有效,连接到前63个字节对应的数据库,但是gsql的命令提示 +符中显示的数据库对象名仍为截断前的名称。重新建立连接时,如果切换数据库登录用户,将可能会出现交互式输入, +要求输入新用户的连接密码。该密码最长长度为999字节,受限于GUC参数password_max_length的最大值。 + +--设置客户端字符编码格式。 +\encoding [ENCODING] +注:不带参数时,显示当前的编码格式。 + +--输出当前连接的数据库的信息。 +\conninfo +``` +### 7.操作系统元命令 + +参数 参数说明 取值范围 +``` +-- +切换当前的工作目录。 +\cd [DIR] +注:绝对路径或相对路径,且满足操作系统路径命名规则。 + +--设置环境变量NAME为VALUE,如果没有给出VALUE值,则不设置环境变量。 +\setenv NAME [VALUE] + +--以毫秒为单位显示每条SQL语句的执行时间。 +\timing [on|off] +注:on表示打开显示。off表示关闭显示。 + +--返回到一个单独的Unix shell或者执行Unix命令COMMAND。 +! [COMMAND] +``` +### 附:权限的参数说明 + +``` +参数 参数说明 + +r +SELECT:允许对指定的表、视图读取数据。 + +w +UPDATE:允许对指定表更新字段。 + +a +INSERT:允许对指定表插入数据。 + +d +DELETE:允许删除指定表中的数据。 + +D +TRUNCATE:允许清理指定表中的数据。 + +x +REFERENCES:允许创建外键约束。由于当前不支持外键,所以该参数暂不生效。 + +t +TRIGGER:允许在指定表上创建触发器。 + +X +EXECUTE:允许使用指定的函数,以及利用这些函数实现的操作符。 + +U +USAGE: + - 对于过程语言,允许用户在创建函数时,指定过程语言。 + - 对于模式,允许访问包含在指定模式中的对象。 + - 对于序列,允许使用nextval函数。 + +C +CREATE: + - 对于数据库,允许在该数据库里创建新的模式。 + - 对于模式,允许在该模式中创建新的对象。 + - 对于表空间,允许在其中创建表,以及允许创建数据库和模式的时候把该表空间指定为其缺省表空间。 + +c +CONNECT:允许用户连接到指定的数据库。 + +T +TEMPORARY:允许创建临时表。 + +arwdDxt +ALL PRIVILEGES:一次性给指定用户/角色赋予所有可赋予的权限。 + +* +给前面权限的授权选项。 +``` diff --git "a/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\344\272\214\357\274\211.md" "b/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\344\272\214\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..2f0c91f1babcf76ee91e5fca38cfa0f043cbc025 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\344\272\214\357\274\211.md" @@ -0,0 +1,329 @@ +### 4.显示信息元命令 +指令中:选项S表示显示系统对象,+表示显示对象附加的描述信息。PATTERN用来指定要被显示的对象名称。 + +``` +--列出当前search_path中模式下所有的表、视图和序列。 +\d[S+] +注:当search_path中不同模式存在同名对象时,只显示search_path中位置靠前模式下的同名对象。 +eg:列出当前search_path中模式下所有的表、视图和序列: +postgres=# \d + +--列出指定表、视图和索引的结构。 +\d[S+] NAME +eg:假设存在表a,列出指定表a的结构。 +postgres=# \dtable+ a + +--列出所有表、视图和索引。 +\d+ [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的表、视图和索引。 +eg:列出所有名称以f开头的表、视图和索引。 +postgres=# \d+ f + +--列出所有可用的聚集函数,以及它们操作的数据类型和返回值类型。 +\da[S] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的聚集函数。 +eg:列出所有名称以f开头可用的聚集函数,以及它们操作的数据类型和返回值类型。 +postgres=# \da f + +--列出所有可用的表空间。 +\db[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的表空间。 +eg:列出所有名称以p开头的可用表空间。 +postgres=# \db p + +--列出所有字符集之间的可用转换。 +\dc[S+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的转换。 +eg:列出所有字符集之间的可用转换。 +postgres=# \dc + +--列出所有类型转换。 +\dC[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的转换。 +eg:列出所有名称以c开头的类型转换。 +postgres=# \dC c + +--显示所有匹配PATTERN的描述。 +\dd[S] [PATTERN] +注:如果没有给出参数,则显示所有可视对象。“对象”包括:聚集、函数、操作符、类型、 +关系(表、视图、索引、序列、大对象)、规则。 +eg:列出所有可视对象。 +postgres=# \dd + +--显示所有默认的使用权限。 +\ddp [PATTERN] +注:如果指定了PATTERN,只显示名称匹配PATTERN的使用权限。 +eg:列出所有默认的使用权限。 +postgres=# \ddp + +--列出所有可用域。 +\dD[S+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的域。 +eg:列出所有可用域。 +postgres=# \dD + +--列出所有的Data Source对象。 +\ded[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的对象。 +eg:列出所有的Data Source对象。 +postgres=# \ded + +--列出所有的外部表。 +\det[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的表。 +eg:列出所有的外部表。 +postgres=# \det + +--列出所有的外部服务器。 +\des[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的服务器。 +eg:列出所有的外部服务器。 +postgres=# \des + +--列出用户映射信息。 +\deu[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的信息。 +eg:列出用户映射信息。 +postgres=# \deu + +--列出封装的外部数据。 +\dew[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的数据。 +eg:列出封装的外部数据。 +postgres=# \dew + +--列出所有可用函数,以及它们的参数和返回的数据类型。 +\df[antw][S+] [PATTERN] +注:a代表聚集函数,n代表普通函数,t代表触发器,w代表窗口函数。如果声明了PATTERN,只显示 +名称匹配PATTERN的函数。 +eg:列出所有可用函数,以及它们的参数和返回的数据类型。 +postgres=# \df + +--列出所有的文本搜索配置信息。 +\dF[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的配置信息。 +列出所有的文本搜索配置信息 +postgres=# \dF+ + +--列出所有的文本搜索字典。 +\dFd[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的字典。 +eg:列出所有的文本搜索字典。 +postgres=# \dFd + +--列出所有的文本搜索分析器。 +\dFp[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的分析器。 +eg:列出所有的文本搜索分析器。 +postgres=# \dFp + +--列出所有的文本搜索模板。 +\dFt[+] [PATTERN] +注:如果声明了PATTERN,只显示名称匹配PATTERN的模板。 +eg:列出所有的文本搜索模板。 +postgres=# \dFt + +--列出所有数据库角色。 +\dg[+] [PATTERN] +注:因为用户和群组的概念被统一为角色,所以这个命令等价于\du。为了和以前兼容,所以保留两个 +命令。如果指定了PATTERN,只显示名称匹配PATTERN的角色。 +eg:列出名称为‘j_e’所有数据库角色。 +postgres=# \dg j?e + +--显示一个大对象的列表。 +\dl +注:\lo_list的别名, +eg:列出所有的大对象。 +postgres=# \dl + +列出可用的程序语言。 +\dL[S+] [PATTERN] +注:如果指定了PATTERN,只列出名称匹配PATTERN的语言。 +eg:列出可用的程序语言。 +postgres=# \dL + +--列出所有的模式(名称空间)。 +\dn[S+] [PATTERN] +注:如果声明了PATTERN,只列出名称匹配PATTERN的模式名。缺省时,只列出用户创建的模式。 +eg:列出所有名称以d开头的模式以及相关信息。 +postgres=# \dn+ d + +--列出所有可用的操作符,以及它们的操作数和返回的数据类型。 +\do[S] [PATTERN] +注:如果声明了PATTERN,只列出名称匹配PATTERN的操作符。缺省时,只列出用户创建的操作符。 +eg:列出所有可用的操作符,以及它们的操作数和返回的数据类型。 +postgres=# \do + + +--列出排序规则。 +\dO[S+] [PATTERN] +注:如果声明了PATTERN,只列出名称匹配PATTERN的规则。缺省时,只列出用户创建的规则。 +eg:列出排序规则。 +postgres=# \dO + +--列出一列可用的表、视图以及相关的权限信息。 +\dp [PATTERN] +注:如果指定了PATTERN,只列出名称匹配PATTERN的表、视图。 +eg:列出一列可用的表、视图以及相关的权限信息。 +postgres=# \dp + +--列出所有修改过的配置参数。 +\drds [PATTERN1 [PATTERN2]] + +注:这些设置可以是针对角色的、针对数据库的或者同时针对两者的。PATTERN1和PATTERN2表示 +要列出的角色PATTERN和数据库PATTERN。如果声明了PATTERN,只列出名称匹配PATTERN的规 +则。缺省或指定时,则会列出所有设置。 +eg:列出postgres数据库所有修改过的配置参数。 +postgres=# \drds postgres + +--列出所有的数据类型。 +\dT[S+] [PATTERN] +注:如果指定了PATTERN,只列出名称匹配PATTERN的类型。 +eg:列出所有的数据类型。 +postgres=# \dT + +--列出所有数据库角色。 +\du[+] [PATTERN] +注:因为用户和群组的概念被统一为角色,所以这个命令等价于\dg。为了和以前兼容,所以保留两个命令。如果指定了PATTERN,则只列出名称匹配PATTERN的角色。 +eg:列出所有数据库角色。 +postgres=# \du + +--列出对象 +\dE[S+] [PATTERN] +\di[S+] [PATTERN] +\ds[S+] [PATTERN] +\dt[S+] [PATTERN] +\dv[S+] [PATTERN] +注:这一组命令,字母E,i,s,t和v分别代表着外部表,索引,序列,表和视图。可以以任意顺序指 +定其中一个或者它们的组合来列出这些对象。例如:\dit列出所有的索引和表。在命令名称后面追加 ++,则每一个对象的物理尺寸以及相关的描述也会被列出。如果指定了PATTERN,只列出名称匹配该 +PATTERN的对象。默认情况下只会显示用户创建的对象。通过PATTERN或者S修饰符可以把系统对 +象包括在内。 +eg:列出所有的索引和视图。 +postgres=# \div + +--列出安装数据库的扩展信息。 +\dx[+] [PATTERN] +注:如果指定了PATTERN,则只列出名称匹配PATTERN的扩展信息。 +eg:列出安装数据库的扩展信息。 +postgres=# \dx + +--列出服务器上所有数据库的名称、所有者、字符集编码以及使用权限。 +\l[+] +eg:列出服务器上所有数据库的名称、所有者、字符集编码以及使用权限。 +postgres=# \l + +--显示函数的定义。 +\sf[+] FUNCNAME +注:对于带圆括号的函数名,需要在函数名两端添加双引号,并且在双引号后面加上参数类型列表。 +参数类型列表两端添加圆括号。 +eg:假设存在函数function_a和函数名带圆括号的函数func()name,列出函数的定义。 +postgres=# \sf function_a +postgres=# \sf +“func()name”(argtype1, argtype2) + +--列出数据库中所有表、视图和序列,以及它们相关的访问特权。 +\z [PATTERN] +注:如果给出任何pattern ,则被当成一个正则表达式,只显示匹配的表、视图、序列。 +eg:列出数据库中所有表、视图和序列,以及它们相关的访问特权。 +postgres=# \z +``` +附:Linux常见信息显示命令: +#### 1、常用的系统信息显示命令 + +``` +--显示主机名称 +hostname + +--显示操作系统信息 +uname + +--显示系统启动信息 +dmesg + +--显示系统加载的内核模块 +lsmod + +--显示系统时间(cal 可以显示系统时间的日历) +date + +--显示系统环境变量 +env + +--显示当前语言环境(cat /etc/sysconfig/i18n) +locale + +--显示操作系统版本(head -1 /etc/issue) +cat /etc/redhat-release : + +--显示CPU信息 +cat /proc/cpuinfo + +--显示PCI/USB接口信息 +lspci/lsusb + +--显示系统已安装的所有软件包 +rpm -qa +``` + +#### 2、常用的资源显示命令 + +``` +--显示当前系统中耗费资源最多的进程 +top + +--显示当前内存的使用情况(cat /proc/meminfo) +free + +--显示指定的文件(目录)已使用的磁盘空间的总量 +du -h + +--显示文件系统磁盘空间的使用情况 +df -h + +--显示系统运行时间、用户数、负载 +uptime + +--查看所有分区 +fdisk -l + +--查看已经挂装的分区 +mount + +--查看所有交换分区 +swapon -s + +--查看所有进程 +ps -ef + +--显示进程树 +pstree + +--列出所有系统服务 +chkconfig --list +``` + +#### 3、常用的用户相关显示命令 + +``` +--显示在线登录用户 +who、w + +--显示用户自己的身份 +whoami + +--显示用户当前使用的终端 +tty + +--显示当前用户的id信息 +id + +--显示当前用户属于哪些组 +groups + +--查看用户登录日志 +last + +--查看当前用户的计划任务 +crontab -l +``` diff --git "a/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\345\233\233\357\274\211.md" "b/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\345\233\233\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..462d4255aa0afade53fae83e776d195e14617fa5 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\203\345\221\275\344\273\244\357\274\210\345\233\233\357\274\211.md" @@ -0,0 +1,113 @@ +### 8.变量元命令 + +``` +参数 参数说明 + +--提示用户用文本格式来指定变量名称。 +\prompt [TEXT] NAME + +--设置内部变量NAME为VALUE或者如果给出了多于一个值,设置为所有这些值的连接结果。如果没有给出第二个参 +数,只设变量不设值。 +\set [NAME [VALUE]] +注:有一些常用变量被gsql特殊对待,它们是一些选项设置,通常所有特殊对待的变量都是由大写字母组成(可能还有数字和下划线)。 + +--不设置(或删除)gsql变量名。 +\unset NAME +``` +### 9.大对象元命令 + +``` +--显示一个目前存储在该数据库里的所有openGauss大对象和提供给他们的注释。 +\lo_list +``` +### 附:可调节的打印选项 + + - border + +value必须是一个数字。通常这个数字越大,表的边界就越宽线就越多,但是这个取决于特定的格式。 +在HTML格式下,取值范围为大于0的整数。 +在其他格式下,取值范围: +0:无边框 +1:内部分隔线 +2:台架 + + - expanded (或x) + +在正常和扩展格式之间切换。 +当打开扩展格式时,查询结果用两列显示,字段名称在左、数据在右。这个模式在数据无法放进通常的”水平”模式的屏幕时很有用。 +在正常格式下,当查询输出的格式比屏幕宽时,用扩展格式。正常格式只对aligned和wrapped格式有用。 + + - fieldsep + +声明域分隔符来实现非对齐输出。这样就可以创建其他程序希望的制表符或逗号分隔的输出。要设置制表符域分隔符,键入\pset fieldsep ‘\t’。缺省域分隔符是 ‘|’ (竖条符)。 + + - fieldsep_zero + +声明域分隔符来实现非对齐输出到零字节。 + + - footer + +用来切换脚注。 + + - format + +设置输出格式。允许使用唯一缩写(这意味着一个字母就够了)。 +取值范围: + +``` +unaligned:写一行的所有列在一条直线上中,当前活动字段分隔符分隔。 +aligned:此格式是标准的,可读性最好的文本输出。 +wrapped:类似aligned,但是包装跨行的宽数据值,使其适应目标字段的宽度输出。 +html:把表输出为可用于文档里的对应标记语言。输出不是完整的文档。 +latex:把表输出为可用于文档里的对应标记语言。输出不是完整的文档。 +troff-ms:把表输出为可用于文档里的对应标记语言。输出不是完整的文档。 +``` + + - null + +打印一个字符串,用来代替一个null值。 +缺省是什么都不打印,这样很容易和空字符串混淆。 + + - numericlocale + +切换分隔小数点左边的数值的区域相关的分组符号。 + +``` +on:显示指定的分隔符。 +off:不显示分隔符。 +忽略此参数,显示默认的分隔符。 +``` + + - pager + +控制查询和gsql帮助输出的分页器。如果设置了环境变量PAGER,输出将被指向到指定程序,否则使用系统缺省。 + +``` +on:当输出到终端且不适合屏幕显示时,使用分页器。 +off:不使用分页器。 +always:当输出到终端无论是否符合屏幕显示时,都使用分页器。 +``` + + - recordsep + +声明在非对齐输出格式时的记录分隔符。 + + - recordsep_zero + +声明在非对齐输出到零字节时的记录分隔符。 + + - tableattr(或T) + +声明放在html输出格式中HTML table标签的属性(例如:cellpadding或bgcolor)。 +注意:这里可能不需要声明border,因为已经在\pset border里用过了。如果没有给出value,则不设置表的属性。 + + - title + +为随后打印的表设置标题。这个可以用于给输出一个描述性标签。如果没有给出value,不设置标题。 + + - tuples_only (或者t) + +在完全显示和只显示实际的表数据之间切换。完全显示将输出像列头、标题、各种脚注等信息。在tuples_only模式下,只显示实际的表数据。 + + + diff --git "a/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\345\206\205\345\255\230\347\256\241\347\220\206.md" "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\345\206\205\345\255\230\347\256\241\347\220\206.md" new file mode 100644 index 0000000000000000000000000000000000000000..092a6200fa3f44149db45a36e0f7db6dbf7b2478 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\345\206\205\345\255\230\347\256\241\347\220\206.md" @@ -0,0 +1,131 @@ +# 内存管理 +数据库在运行过程中涉及许多对象,这些对象具有不同的生命周期,有些处理需要频繁分配内存。 + +##openGauss内存管理的功能 +1.引入jemalloc开源库,替换glibc的内存分配和释放,减少内存碎片 +2.引入逻辑内存管理机制,控制进程内存使用,避免出现OOM问题 +3.引入多种内存上下文(共享内存上下文、栈式内存上下文、对齐内存上下文),满足不同场景代码开发诉求。 +4.引入ASAN(Address Sanitizer)开源库,在Debug版本下定位内存泄漏和内存越界问题 。 +5.引入丰富的内存查询视图,方便观察内存使用情况,定位潜在内存问题。 + +## 内存管理实现 +openGauss在内存管理上采用了上下文的概念,即具有同样生命周期或者属于同一个上下文语义的内存放到一个MemoryContext管理,MemoryContext的结构代码如下: +``` +typedef struct MemoryContextData* MemoryContext; +typedef struct MemoryContextData { + NodeTag type; /* 上下文类别*/ + MemoryContextMethods* methods; /* 虛函数表*/ + MemoryContext parent; /* 父上下文。顶级上下文为 NULL*/ + MemoryContext firstchild; /* 子上下文的链表头*/ + MemoryContext prevchild; /* 前向子上下文 */ + MemoryContext nextchild; /* 后向子上下文 */ + char* name; /* 上下文名称,方便调试 */ + pthread_rwlock_t lock; /*上下文共享时的并发控制锁 */ + bool is_shared; /* 上下文是否在多个线程共享 */ + bool isReset; /* isReset为true时,表示复位后没有内存空间用于分配*/ + int level; /* 上下文层次级别*/ + uint64 session_id; /* 上下文属于的会话ID */ + ThreadId thread_id; /* 上下文属于的线程ID */ +} MemoryContextData; +``` +虛函数表就是具体的内存管理操作函数指针,具体定义代码如下(函数功能参照注释): +``` +typedef struct MemoryContextMethods { +/*在上下文中分配内存*/ + void* (*alloc)(MemoryContext context, Size align, Size size, const char* file, int line); + /* 释放pointer 内存到上下文中*/ +void (*free_p)(MemoryContext context, void* pointer); +/*在上下文中重新分配内存*/ +void* (*realloc)(MemoryContext context, void* pointer, Size align, Size size, const char* file, int line); + void (*init)(MemoryContext context); /*上下文初始化*/ + void (*reset)(MemoryContext context); /*上下文复位*/ + void (*delete_context)(MemoryContext context); /*删除上下文 */ + Size (*get_chunk_space)(MemoryContext context, void* pointer); /*获取上下文块大小 */ + bool (*is_empty)(MemoryContext context); /*上下文是否为空*/ + void (*stats)(MemoryContext context, int level); /*上下文信息统计*/ +#ifdef MEMORY_CONTEXT_CHECKING + void (*check)(MemoryContext context); /*上下文异常检查*/ +#endif +} MemoryContextMethods; +``` +这些回调函数指针初始化是在AllocSetContextSetMethods函数中调用AllocSetMethodDefinition函数完成的。 +``` +AllocSetMethodDefinition函数的实现代码如下: + +template +void AlignMemoryAllocator::AllocSetMethodDefinition(MemoryContextMethods* method) +{ + method->alloc = &AlignMemoryAllocator::AllocSetAlloc; + method->free_p = &AlignMemoryAllocator::AllocSetFree; + method->realloc = &AlignMemoryAllocator::AllocSetRealloc; + method->init = &AlignMemoryAllocator::AllocSetInit; + method->reset = &AlignMemoryAllocator::AllocSetReset; + method->delete_context = &AlignMemoryAllocator::AllocSetDelete; + method->get_chunk_space = &AlignMemoryAllocator::AllocSetGetChunkSpace; + method->is_empty = &AlignMemoryAllocator::AllocSetIsEmpty; + method->stats = &AlignMemoryAllocator::AllocSetStats; +#ifdef MEMORY_CONTEXT_CHECKING + method->check = &AlignMemoryAllocator::AllocSetCheck; +#endif +} +``` +这些实际操作内存管理的函数为AlignMemoryAllocator类中的AllocSetAlloc函数、AllocSetFree函数、AllocSetRealloc函数、AllocSetInit函数、AllocSetReset函数、AllocSetDelete函数、AllocSetGetChunkSpace函数、AllocSetIsEmpty函数、AllocSetStats函数和AllocSetCheck函数。 +代码如下: +``` +typedef AllocSetContext* AllocSet; +typedef struct AllocSetContext { + MemoryContextData header; /*内存上下文,存储空间是在这个内存上下文中分配的 */ + AllocBlock blocks; /* AllocSetContext所管理内存块的块链表头 */ + AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; /* 空闲块链表*/ + /*这个上下文的分配参数 */ + Size initBlockSize; /* 初始块大小*/ + Size maxBlockSize; /* 最大块大小 */ + Size nextBlockSize; /* 下一个分配的块大小 */ + Size allocChunkLimit; /* 块大小上限*/ + AllocBlock keeper; /* 在复位时,保存的块 */ + Size totalSpace; /* 这个上下文分配的总空间 */ + Size freeSpace; /* 这个上下文总的空闲空间 */ + Size maxSpaceSize; /* 最大内存空间 */ + MemoryTrack track; /* 跟踪内存分配信息 */ +} AllocSetContext; +AllocBlock定义如下: +typedef struct AllocBlockData* AllocBlock; +typedef struct AllocBlockData { + AllocSet aset; /* 哪个AllocSetContext 拥有此块,AllocBlockData 归属AllocSetContext管理*/ + AllocBlock prev; /* 在块链表中的前向指针 */ + AllocBlock next; /* 在块链表中的后向指针 */ + char* freeptr; /* 这个块空闲空间的起始地址 */ + char* endptr; /* 这个块空间的结束地址*/ + Size allocSize; /* 分配的大小*/ +#ifdef MEMORY_CONTEXT_CHECKING + uint64 magicNum; /* 魔鬼数字值,用于内存校验。当前代码固定填写为DADA */ +#endif +} AllocBlockData; +typedef struct AllocChunkData* AllocChunk; /* AllocChunk 内存前面部分是一个AllocBlock结构*/ +typedef struct AllocChunkData { + void* aset; /* 拥有这个chunk的AllocSetContext,如果空闲,则为空闲列表链接*/ + Size size; /* chunk中的使用空间 */ +#ifdef MEMORY_CONTEXT_CHECKING + Size requested_size; /* 实际请求大小,在空闲块中时为0 */ + const char* file; /* palloc/palloc0调用时的文件名称 */ + int line; /* palloc/palloc0 调用时的行号*/ + uint32 prenum; /* 前向魔鬼数字*/ +#endif +} AllocChunkData; +``` +从前面的数据结构可以看出,核心数据结构为AllocSetContext,这个数据结构有3个成员“MemoryContextData header;”、“AllocBlock blocks;”和“AllocChunk freelist[ALLOCSET_NUM_FREELISTS];”。这3个成员把内存管理分为3个层次。 + +(1) MemoryContext管理上下文之间的父子关系,设置MemoryContext的内存管理函数。 +(2) AllocBlock blocks把所有内存块通过双链表链接起来。 +(3) 指定内存单元块。内存块从内存块AllocBlock中分配。内存块和内存块之间的转换关系为:AllocChunk = (AllocChunk)(((char*)*block) + ALLOC_BLOCKHDRSZ;And" AllocBlock block = (AllocBlock)(((char*)*chunk) - ALLOC_BLOCKHDRSZ);". +内存单元块被转换为获得最终的用户指针。内存单元块和用户指针之间的转换关系是:((AllocPointer)(((char*)(*chk)) + ALLOC_CHUNKHDRSZ))和((AllocChunk)(((char*)*(ptr)) -ALLOC_CHUNKHDRSZ))。 +数据结构的基本关系如图所示。 +![](/api/attachments/398168) + +### MemoryContext的实现函数 +主要实现在mcxt.cpp文件中 +![](/api/attachments/398169) + +### AllocSet的实现函数 +主要实现在aset.cpp文件中 +![](/api/attachments/398170) diff --git "a/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\345\244\232\347\272\277\347\250\213\346\236\266\346\236\204.md" "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\345\244\232\347\272\277\347\250\213\346\236\266\346\236\204.md" new file mode 100644 index 0000000000000000000000000000000000000000..fcabeb0ab36b7b58e8f362f9c465b6bafc1e898f --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\345\244\232\347\272\277\347\250\213\346\236\266\346\236\204.md" @@ -0,0 +1,63 @@ +#多线程架构 + +##为什么要使用多线程架构 +随着计算机领域多核技术的发展,如何充分有效的利用多核的并行处理能力,是每个服务器端应用程序都必须考虑的问题。由于数据库服务器的服务进程或线程间存在着大量数据共享和同步,而多线程可以充分利用多CPU来并行执行多个强相关任务,例如执行引擎可以充分的利用线程的并发执行以提供性能。在多线程的架构下,数据共享的效率更高,能提高服务器访问的效率和性能,同时维护开销和复杂度更低,这对于提高数据库系统的并行处理能力非常重要。 + +##openGauss主要线程 +openGauss的后台线程是不对等的,其中Postmaster是主线程,其他线程都是它创建出来的。openGauss后台线程的功能介绍如表所示。 +![](/api/attachments/397888) + +##线程间通信 +penGauss后台线程之间紧密配合,共同完成了数据库的数据处理任务。这些后台线程之间需要交换信息来协调彼此的行为。openGauss多线程通信使用了原来的PostgreSQL的多进程通信方式。具体如表所示。 +![](/api/attachments/397889) + +##线程初始化 +openGauss进程的主函数入口在“\openGauss-server\src\gausskernel\process\main\main.cpp”文件中。在main.cpp文件中,主要完成实例Context(上下文)的初始化、本地化设置,根据main.cpp文件的入口参数调用BootStrapProcessMain函数、GucInfoMain函数、PostgresMain函数和PostmasterMain函数。BootStrapProcessMain函数和PostgresMain函数是在initdb场景下初始化数据库使用的。GucInfoMain函数作用是显示GUC(grand unified configuration,大统一配置,在数据库中指的是运行参数)参数信息。正常的数据库启动会进入PostmasterMain函数。 + +###PostmasterMain函数。 +1.进行Postmaster的Context初始化,初始化GUC参数,解析命令行参数。 +2.调用StreamServerPort函数启动服务器监听和双机监听(如果配置了双机),调用reset_shared函数初始化共享内存和LWLock锁,调用gs_signal_monitor_startup函数注册信号处理线程,调用InitPostmasterDeathWatchHandle函数注册Postmaster死亡监控管道,把openGauss进程信息写入pid_file文件中,调用gspqsignal函数注册Postmaster的信号处理函数。 +3. 根据配置初始化黑匣子,调用pgstat_init函数初始化统计信息传递使用的UDP套接字通信,调用InitializeWorkloadManager函数初始化负载管理器,调用InitUniqueSQL函数初始化UniqueSQL,调用SysLogger_Start函数初始化运行日志的通信管道和SYSLOGGER线程,调用load_hba函数加载hba鉴权文件。 +4.调用initialize_util_thread函数启动STARTUP线程,调用ServerLoop函数进入一个周期循环。在ServerLoop函数的周期循环中,进行客户端请求监听,如果有客户端连接请求,在非线程池模式下,则调用BackendStartup函数创建一个后台线程worker处理客户请求。在线程池模式下,把新的链接加入一个线程池组中。在ServerLoop函数的周期循环中,检查其他线程的运行状态。如果数据库是第一次启动,则调用initialize_util_thread函数启动其他后台线程。如果有后台线程FATAL级别错误退出,则调用initialize_util_thread函数重新启动该线程。如果是PANIC级别错误退出,则整个实例进行重新初始化。 + +PostmasterMain完成了线程之间的通信初始化和线程的启动,无论是后台线程的启动函数initialize_util_thread,还是工作线程的启动函数initialize_worker_thread,最后都是调用initialize_thread函数完成线程的启动。 + +###initialize_thread函数 +initialize_thread函数调用gs_thread_create函数创建线程,调用InternalThreadFunc函数处理线程。它的相关代码如下所示: +``` +ThreadId initialize_thread(ThreadArg* thr_argv) +{ + gs_thread_t thread; + if (0 != gs_thread_create(&thread, InternalThreadFunc, 1, (void*)thr_argv)) { + gs_thread_release_args_slot(thr_argv); + return InvalidTid; + } + return gs_thread_id(thread); +} +``` +nternalThreadFunc函数的代码如下。该函数根据角色调用GetThreadEntry函数,GetThreadEntry函数直接以角色为下标,返回对应GaussdbThreadEntryGate数组对应的元素。数组的元素是处理具体任务的回调函数指针,指针指向的函数为GaussDbThreadMain。相关代码如下所示: +``` +static void* InternalThreadFunc(void* args) +{ + knl_thread_arg* thr_argv = (knl_thread_arg*)args; + gs_thread_exit((GetThreadEntry(thr_argv->role))(thr_argv)); + return (void*)NULL; +} +GaussdbThreadEntry GetThreadEntry(knl_thread_role role) +{ + Assert(role > MASTER && role < THREAD_ENTRY_BOUND); + return GaussdbThreadEntryGate[role]; +} +static GaussdbThreadEntry GaussdbThreadEntryGate[] = {GaussDbThreadMain, + GaussDbThreadMain, + GaussDbThreadMain, + GaussDbThreadMain, + ......}; +``` +在GaussDbThreadMain函数中,首先初始化线程基本信息,Context和信号处理函数,接着就是根据thread_role角色的不同调用不同角色的处理函数,进入各个线程的main函数,比如GaussDbAuxiliaryThreadMain函数、AutoVacLauncherMain函数、WLMProcessThreadMain函数等。其中GaussDbAuxiliaryThreadMain函数是后台辅助线程处理函数。该函数的处理也类似GaussDbThreadMain函数,根据thread_role角色的不同调用不同角色的处理函数,进入各个线程的main函数,比如StartupProcessMain函数、CheckpointerMain函数、WalWriterMain函数、walrcvWriterMain函数等。 + +##总结 +openGauss多线程架构主要包括3个方面: +1.多线程之间的通信,由主线程在初始化阶段完成。 +2.多线程的启动,由主线程创建各个角色线程,调用不同角色的处理函数完成。 +3. 主线程负责监控各个线程的运行,异常退出和重新拉起。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\345\244\232\347\273\264\347\233\221\346\216\247.md" "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\345\244\232\347\273\264\347\233\221\346\216\247.md" new file mode 100644 index 0000000000000000000000000000000000000000..af1caab598af9e53d7d5e5e8a1e003cd745190a7 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\345\244\232\347\273\264\347\233\221\346\216\247.md" @@ -0,0 +1,103 @@ +#多维监控 +数据库是企业的关键组件,数据库的性能直接决定了很多业务的吞吐量。为了简化数据库维护人员的调优,openGauss对数据库运行进行了多维度的监控,并且开发了一些维护特性,比如WDR(wordload dignostic report,工作负荷诊断报告)性能诊断报告、慢SQL诊断、session性能诊断、系统KPI(key performance indicator,关键性能指标)辅助诊断等,帮助维护人员对数据库的性能进行诊断。 + +这些监视项显示在视图模式中,并集中在DBE_PERF模式中。除了WDR Snapshot的快照元数据外,其他数据表源也是DBE_PERF模式中的视图。WDR快照表命名原则:snap_{*源表},根据这种关系可以找到原表对应的快照表。在opengaus网站(https://opengauss.org) 上的开发人员指南手册的“DBE_PERF模式”一节中解释了这些视图。 +性能视图的源代码在openguss -server\src\common\backend\catalog\performance_views中。sql文件。https://gitee.com/opengauss/openGauss-server/blob/master/src/common/backend/catalog/performance_views.sql, 安装后将复制到安装路径“/ share/postgresql/performance_views SQL”)。initdb在数据库初始化阶段读取该文件,以在数据库系统中创建相应的视图。这些视图遵循openguss通用视图的实现逻辑,即视图来自函数的封装,这些函数可以是内置的,也可以是存储的。下面是dbe_perf的代码: +``` +CREATE OR REPLACE FUNCTION dbe_perf.get_global_os_runtime + (OUT node_name name, OUT id integer, OUT name text, OUT value numeric, OUT comments text, OUT cumulative boolean) +RETURNS setof record +AS $$ +DECLARE + row_data dbe_perf.os_runtime%rowtype; +query_str := 'SELECT * FROM dbe_perf.os_runtime'; + FOR row_data IN EXECUTE(query_str) LOOP + ...... + END LOOP; + return; + END; $$ +LANGUAGE 'plpgsql' NOT FENCED; +CREATE VIEW dbe_perf.global_os_runtime AS + SELECT DISTINCT * FROM dbe_perf.get_global_os_runtime(); +``` +global_os_runtime视图来自存储函数get_global_os_runtime的封装,访问dbe_perf。os_runtime VIEW FROM存储函数。os_runtime视图的SQL语句是“CREATE view dbe_perf”。os_runtime AS SELECT * FROM pv_os_run_info();"pv_os_run_info函数是一个内置函数,用于读取数据库系统的监视指标。pv_os_run_info函数的代码如下: +``` +Datum pv_os_run_info(PG_FUNCTION_ARGS) +{ + FuncCallContext* func_ctx = NULL; + /* 判断是不是第一次调用 */ + if (SRF_IS_FIRSTCALL()) { + MemoryContext old_context; + TupleDesc tup_desc; + /* 创建函数上下文 */ + func_ctx = SRF_FIRSTCALL_INIT(); + /* + * 切换内存上下文到多次调用上下文 + */ + old_context = MemoryContextSwitchTo(func_ctx->multi_call_memory_ctx); + /* 创建一个包含5列的元组描述模板*/ + tup_desc = CreateTemplateTupleDesc(5, false); + TupleDescInitEntry(tup_desc, (AttrNumber)1, "id", INT4OID, -1, 0); + TupleDescInitEntry(tup_desc, (AttrNumber)2, "name", TEXTOID, -1, 0); + TupleDescInitEntry(tup_desc, (AttrNumber)3, "value", NUMERICOID, -1, 0); + TupleDescInitEntry(tup_desc, (AttrNumber)4, "comments", TEXTOID, -1, 0); + TupleDescInitEntry(tup_desc, (AttrNumber)5, "cumulative", BOOLOID, -1, 0); + + /* 填充元组描述模板 */ + func_ctx->tuple_desc = BlessTupleDesc(tup_desc); + /* 收集系统信息 */ + getCpuNums(); + getCpuTimes(); + getVmStat(); + getTotalMem(); + getOSRunLoad(); + (void)MemoryContextSwitchTo(old_context); + } + + /*设置函数的上下文,每次函数调用都需要*/ + func_ctx = SRF_PERCALL_SETUP(); + while (func_ctx->call_cntr < TOTAL_OS_RUN_INFO_TYPES) { + /* 填充所有元组每个字段的值 */ + Datum values[5]; + bool nulls[5] = {false}; + HeapTuple tuple = NULL; + errno_t rc = 0; + rc = memset_s(values, sizeof(values), 0, sizeof(values)); + securec_check(rc, "\0", "\0"); + rc = memset_s(nulls, sizeof(nulls), 0, sizeof(nulls)); + securec_check(rc, "\0", "\0"); + if (!u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].got) { + ereport(DEBUG3, + (errmsg("the %s stat has not got on this plate.", + u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].name))); + func_ctx->call_cntr++; + continue; + } + values[0] = Int32GetDatum(func_ctx->call_cntr); + values[1] = CStringGetTextDatum(u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].name); + values[2] = u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].getDatum( + u_sess->stat_cxt.osStatDataArray[func_ctx->call_cntr]); + values[3] = CStringGetTextDatum(u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].comments); + values[4] = BoolGetDatum(u_sess->stat_cxt.osStatDescArray[func_ctx->call_cntr].cumulative); + + tuple = heap_form_tuple(func_ctx->tuple_desc, values, nulls); + SRF_RETURN_NEXT(func_ctx, HeapTupleGetDatum(tuple)); + } + /* 填充结束,返回结果 */ + SRF_RETURN_DONE(func_ctx); +} +``` +pv_os_run_info函数可以分为三个部分: + +(1)调用CreateTemplateTupleDesc函数和TupleDescInitEntry函数定义元组描述。 +(2)调用getCpuNums、getCpuTimes、getVmStat、getTotalMem、getOSRunLoad命令收集系统信息。 +(3)将收集到的u_sess信息填充到元组数据中,最后返回给调用者。openguss为实现返回结果的通用SQL函数提供了实现步骤和方法:SRF_IS_FIRSTCALL、SRF_PERCALL_SETUP、SRF_RETURN_NEXT和SRF_RETURN_DONE。从代码中可以看出,pv_os_run_info的实现过程也遵循了openguss常用的SQL函数实现方法。 +系统指标的采集来自于对系统信息的读取和对数据库系统中某些模块的打分(打分是指按照规范收集指定的数据,记录系统运行的一些关键点)。许多要点集中在两个方面:执行的事务数量和执行时间。可以推断出最大时间,最小时间,平均时间。这些是分散的,并且代码逻辑相对简单,这里不再讨论。只需查看内置函数读取的变量并查看这些变量的赋值位置,就可以跟踪实现的位置。 + +openguss数据库的主要维护特性的实现代码在openguss -server \src\gausskernel\cbb\instruments目录中,例如WDR和SQL百分位计算,这里不进行描述。 + +性能统计也会给openguss的正常运行带来一定的性能损失,所以这些特性都是切换控制的。具体情况如下。 + +(1)实时收集事件信息的功能为enable_instr_track_wait。 +(2)启用enable_instr_unique_sql和enable_inst_rt_percentile,实时收集Unique SQL信息。 +(3)数据库监控快照开关为enable_wdr_snapshot。 diff --git "a/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\346\225\260\346\215\256\345\272\223\345\210\235\345\247\213\345\214\226.md" "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\346\225\260\346\215\256\345\272\223\345\210\235\345\247\213\345\214\226.md" new file mode 100644 index 0000000000000000000000000000000000000000..706da5a2ee3196add945471113c4cde7ff9aab0d --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\346\225\260\346\215\256\345\272\223\345\210\235\345\247\213\345\214\226.md" @@ -0,0 +1,144 @@ +#数据库初始化 +数据库正常启动时需要指定数据目录,数据目录中包括了系统表的初始化数据。数据库初始化的过程会生成这些初始系统表数据文件,该过程由initdb和openGauss进程配合生成。 +initdb控制执行过程,创建目录和基本的配置文件;openGauss进程负责系统表的初始化。initdb通过PG_CMD_OPEN宏启动openGauss进程,同时打开一个管道流,然后通过解析系统表文件中的SQL命令,并把命令通过PG_CMD_PUTS宏的管道流发给openGauss进程,最后通过PG_CMD_CLOSE宏关闭管道流。PG_CMD_OPEN宏是系统函数popen的封装宏,PG_CMD_PUTS宏是系统函数fputs的封装宏,PG_CMD_CLOSE宏是系统函数pclose的封装宏。交互过程如图1所示。 +![](/api/attachments/397886) +initdb在创建template1模板数据库时,命令参数指定了“snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, “”%s" --boot -x1 %s %s", backend_exec, boot_options, talkargs);”,其中“–boot”表示openGauss进程以一个特殊的bootstrap模式运行。在其他初始化系统表时,initdb命令参数指定了“snprintf_s(cmd, sizeof(cmd), sizeof(cmd) - 1, “”%s" %s template1 >%s", backend_exec, backend_options, DEVNULL); ”,其中“static const char* backend_options = "–single "”表示openGauss进程以单用户模式运行。 + +下面以setup_schema函数为例详细介绍这个过程。相关代码如下: +``` +static void setup_schema(void) +{ + PG_CMD_DECL; + char** line; + char** lines; + int nRet = 0; + char* buf_features = NULL; + fputs(_("creating information schema ... "), stdout); + (void)fflush(stdout); + lines = readfile(info_schema_file); + /* + * 使用-j 避免在information_schema.sql反斜杠处理 + */ + nRet = snprintf_s( + cmd, sizeof(cmd), sizeof(cmd) - 1, "\"%s\" %s -j template1 >%s", backend_exec, backend_options, DEVNULL); + securec_check_ss_c(nRet, "\0", "\0"); + PG_CMD_OPEN; + for (line = lines; *line != NULL; line++) { + PG_CMD_PUTS(*line); + FREE_AND_RESET(*line); + } + FREE_AND_RESET(lines); + PG_CMD_CLOSE; + nRet = snprintf_s( + cmd, sizeof(cmd), sizeof(cmd) - 1, "\"%s\" %s template1 >%s", backend_exec, backend_options, DEVNULL); + securec_check_ss_c(nRet, "\0", "\0"); + PG_CMD_OPEN; + PG_CMD_PRINTF1("UPDATE information_schema.sql_implementation_info " + " SET character_value = '%s' " + " WHERE implementation_info_name = 'DBMS VERSION';\n", + infoversion); + + buf_features = escape_quotes(features_file); + PG_CMD_PRINTF1("COPY information_schema.sql_features " + " (feature_id, feature_name, sub_feature_id, " + " sub_feature_name, is_supported, comments) " + " FROM E'%s';\n", + buf_features); + FREE_AND_RESET(buf_features); + PG_CMD_CLOSE; + check_ok(); +} +``` +在这个函数中,PG_CMD_DECL是一个变量定义宏,它有两个变量,由语句"char cmd[MAXPGPATH]"和"FILE* cmdfd = NULL"定义。这样的效果是代码格式统一,便于阅读。 + +语句“readfile(info_schema_file)”读取info_schema_file文件,该文件包含用于初始化系统表的SQL命令。 + +语句"snprintf_s(cmd, sizeof(cmd), sizeof(cmd) -1," "%s" %s -j template1 >%s", backend_exec, backend_options, DEVNULL "是格式化openguss后台进程的命令。PG_CMD_OPEN以popen模式运行cmd命令,启动opengaus进程。 + +语句"for (line = lines;*线!=零;行+ +)意味着遍历info_schema_file文件中的每个SQL命令,宏PG_CMD_PUTS将每个SQL命令发送到openguss进程执行。 + +在整个文件执行完之后,调用宏PG_CMD_CLOSE来停止进程并关闭管道。setup_schema函数的其余部分处理方式类似,除了SQL命令是在函数中生成的,并使用宏PG_CMD_PRINTF1将其写入管道到openguss进程。 + +其他系统对象(如setup_sysviews、setup_dictionary、setup_privileges)的初始化过程类似,不会重复。 + +initdb的整个初始化过程如下所示。 + +1.解析命令行参数。 + +2.找到openguss程序,设置PGDATA、PGDATA、PGDATA、PGPATH等环境变量。设置原始数据库初始化文件postgres。bki postgres.description、postgres。shdescription postgresql.conf。样本,mot.conf。样本,pg_hba.conf。样本,pg_ident。相依样本。shell命令中的这些文件使install在安装后执行,默认为“opengaus - server/dest/share/postgresql”目录。 + +3.当数据库在本地初始化时,locale初始化为en_US。默认为UTF-8,数据库编码初始化为UTF8,文本搜索默认化为英文。 + +4.检查数据库数据目录pg_data是否为空,是否需要创建,权限是否正确。 + +5.创建subdirs变量指定的子目录。 + +6.初始化配置文件postgresql.conf。 + +7.创建template1数据库bootstrap_template1在此步骤中,需要启动后台opengaus进程,以便在数据库中执行SQL语句并创建系统表。bootstrap_template1函数从postgres中读取SQL语句。bki文件,并将它们发送到opengaus进程执行。bootstrap_template1函数用于创建系统表。下面是一个SQL语句的示例。create pg_type表示创建pg_type类型的系统表,INSERT OID表示要插入到系统表中的默认数据。这里的语法是为initdb定制的引导解析语法,而不是正式的SQL语法,语法文件也是独立的。有关详细信息,请参见bootscanner。l和bootparse。在openguss -server\src\gausskernel\bootstrap. y文件。initdb初始化过程中pg_type系统对象的引导语法代码如下。在初始化过程中,通过解析以下语法格式创建pg_type系统对象: +``` +create pg_type 1247 bootstrap rowtype_oid 71 + ( + typname = name , + typnamespace = oid , + typowner = oid , + typlen = int2 , + typbyval = bool , + typtype = char , + typcategory = char , + typispreferred = bool , + typisdefined = bool , + typdelim = char , + typrelid = oid , + typelem = oid , + typarray = oid , + typinput = regproc , + typoutput = regproc , + typreceive = regproc , + typsend = regproc , + typmodin = regproc , + typmodout = regproc , + typanalyze = regproc , + typalign = char , + typstorage = char , + typnotnull = bool , + typbasetype = oid , + typtypmod = int4 , + typndims = int4 , + typcollation = oid , + typdefaultbin = pg_node_tree , + typdefault = text , + typacl = aclitem[] + ) +INSERT OID = 16 ( bool 11 10 1 t b B t t \054 0 0 1000 boolin boolout boolrecv boolsend - - - c p f 0 -1 0 0 _null_ _null_ _null_ ) +INSERT OID = 17 ( bytea 11 10 -1 f b U f t \054 0 0 1001 byteain byteaout bytearecv byteasend - - - i x f 0 -1 0 0 _null_ _null_ _null_ ) +........... +close pg_type +``` +8.使用setup_auth函数初始化pg_authid权限。此函数执行一条SQL语句,该语句在函数内静态定义了“static const char* pg_authid_setup[]”。 + +9.使用setup_depend函数创建系统表依赖关系。此函数执行一条SQL语句,该语句在函数内静态定义了“static const char* pg_depend_setup[]”。 + +10.使用load_plpgsql函数加载plpgsql扩展组件。这个函数只执行一条SQL语句:"CREATE EXTENSION plpgsql;". + +11.使用setup_sysviews创建系统视图。这个函数将读取system_views中的sql语句。SQL文件,并将它们发送到opengaus执行。它的主要功能是创建系统视图。 + +12.使用setup_perfviews函数创建性能视图。这个函数读取performance_views中的sql语句。SQL并将它们发送到opengaus执行。该功能用于创建性能视图。 + +13.使用setup_conversion函数创建编码转换。该函数将读取conversion_create中的sql语句。SQL文件,并将它们发送到opengaus执行。该函数主要用于创建编码转换函数。 + +14.使用setup_dictionary函数创建词干数据字典。这个函数从snowball_create。SQL文件,并将其发送到opengaus执行。主要功能是创建一个文本搜索功能。 + +15.使用setup_privileges函数设置权限。setup_privileges函数通过xstrdu将SQL常量字符串复制到一个动态数组中,然后遍历指定的SQL语句。 + +16.使用load_supported_extension函数加载曲面。这个函数执行对应扩展组件的CREATE EXTENSION语句。 + +17.使用setup_update函数更新系统表。COPY pg_cast_oid.txt函数用于创建类型强制处理函数。 + +18.如果要清除template1的垃圾数据,请执行三个SQL语句ANALYZE;”和“真空完整;”和“真空冻结;. + +19.创建template0数据库,即将template1拷贝到template0中。 + +20.通过将template1复制到postgres来创建postgres数据库。 + +21.清理垃圾数据,冻结template0、template1和postgres的事务id。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\346\250\241\346\213\237\344\277\241\345\217\267\346\234\272\345\210\266.md" "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\346\250\241\346\213\237\344\277\241\345\217\267\346\234\272\345\210\266.md" new file mode 100644 index 0000000000000000000000000000000000000000..3493bb68873e90ca8410202f2e166ed5c199e75d --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\346\250\241\346\213\237\344\277\241\345\217\267\346\234\272\345\210\266.md" @@ -0,0 +1,71 @@ +#模拟信号机制 +信号是Linux进程/线程之间的通信机制。向进程发送信号的系统函数是kill,向线程发送信号的系统函数是pthread_kill。在openguss中,既有gs_ctl发送给openguss进程的进程间信号,也有openguss进程中的线程间信号。 + +信号是有限的资源。操作系统提供SIGINT、SIGQUIT、SIGTERM、SIGALRM、SIGPIPE、SIGFPE、SIGUSR1、SIGUSR2、SIGCHLD、SIGTTIN、SIGTTOU、SIGXFSZ等信号。这些信号通常与系统相关。每个信号都有特定的用途。例如,SIGALRM是系统定时器的通知信号。剩下的主要信号是SIGUSR1和SIGUSR2。 + +为了在系统信号有限的情况下,在openGauss中表达不同的丰富的通信语义,openGauss添加了新的变量来表达特定的语义。opengaus是一个多线程体系结构。如果同一进程中的不同线程注册了不同的处理程序,则后者将覆盖前者的信号处理。为了让不同的线程注册不同的处理程序,您需要自己管理与信号对应的注册函数。为了解决这些问题,opengaus实现了信号仿真机制。信号仿真的基本原理是,每个线程注册和管理自己的信号处理函数,信号枚举值仍然使用系统信号值,线程使用自己的变量记录信号和回调函数的对应关系。在线程之间发送信号时,首先将变量设置为特定的信号值,然后使用系统调用pthread_kill发送信号。在收到通知后,线程将根据附加变量所表示的特定信号值回调相应的信号处理函数。 + +信号处理所涉及的数据结构代码如下。每个线程都有一个GsSignalSlot结构,它存储线程ID、线程名称和GsSignal结构。GsSignal结构存储了与每个信号对应的处理程序数组以及与每个线程关联的信号池。struct SignalPool包含已使用信号列表和空闲信号列表。当模拟信号到达时,找到一个空闲信号GsNode,并将其放入已使用信号列表中。GsNode存储GsSndSignal sig_data结构。发送的信号的具体值和发送的线程ID存储在GsSndSignal结构中。如果需要设置其他检查信息,可以设置“GsSignalCheck”的内容。相关代码如下: +``` +typedef struct GsSignalSlot { + ThreadId thread_id; + char* thread_name; + GsSignal* gssignal; +} GsSignalSlot; +typedef struct GsSignal { + gs_sigfunc handlerList[GS_SIGNAL_COUNT]; + sigset_t masksignal; + SignalPool sig_pool; + volatile unsigned int bitmapSigProtectFun; +} GsSignal; +typedef struct SignalPool { + GsNode* free_head; /* 空闲信号列表头部 */ + GsNode* free_tail; /* 空闲信号列表尾部 */ + GsNode* used_head; /* 使用信号列表头部*/ + GsNode* used_tail; /*使用信号列表尾部*/ + int pool_size; /* 数组列表大小*/ + pthread_mutex_t sigpool_lock; +} SignalPool; +typedef struct GsNode { + GsSndSignal sig_data; + struct GsNode* next; +} GsNode; +typedef struct GsSndSignal { + unsigned int signo; /* 需要处理的信号*/ + gs_thread_t thread; /* 发送信号的线程ID */ + GsSignalCheck check; /* 信号发送线程需要检查的信息 */ +} GsSndSignal; +typedef struct GsSignalCheck { + GsSignalCheckType check_type; + uint64 debug_query_id; + uint64 session_id; +} GsSignalCheck; +``` +信号处理的主要过程是模拟信号机制的初始化、信号处理函数的配准、信号发送和信号处理。具体处理逻辑如下。 + +(1)初始化模拟信号机制函数gs_signal_slots_init。以下函数在gs_signal_slots_init处理函数中执行。 +1.根据传入插槽的数量分配内存。遍历和初始化每个槽。初始化过程中,调用gs_signal_init函数初始化每个槽位的GsSignal (GsSignal是openguss封装的模拟信号结构,包含信号掩码和信号处理函数)。 + +2.在gs_signal_init函数中,为GsSignal分配并初始化内存。初始化过程中,调用gs_signal_sigpool_init函数初始化信号池。 + +3.在gs_signal_sigpool_init函数中分配内存并初始化信号池。 + + +(2)寄存器信号处理函数gspqsignal。在gspqsignal handler函数中执行以下操作。 +1.叫gs_signal_register_handler (t_thrd。signal_slot - > gssignal signo, func);GsSignal函数用GsSignal注册与该信号对应的处理程序。在注册之前,需要给线程分配一个signal_slot,这在gs_signal_startup_siginfo函数中完成。 + +2.在gs_signal_startup_siginfo函数中,调用gs_signal_alloc_slot_for_new_thread函数为线程分配一个signal_slot。函数是遍历“g_instance”。signal_base->slots”,找到一个未使用的槽位(thread_id 0表示未使用),然后设置本地线程ID和线程名。 + +(3)发送信号函数gs_signal_send。在gs_signal_send处理函数中执行以下函数。 +1.调用gs_signal_find_slot函数定位要发送的线程所在的GsSignalSlot。 + +2.调用gs_signal_set_signal_by_threadid设置模拟信号。函数首先检查信号是否已经存在于使用列表中,如果存在则返回。如果它不存在,它会在空闲列表中找到一个空闲的GsNode,设置信号值,发送线程ID、check_type到sig_data,最后将空闲的GsNode移动到使用列表中。 + +3.调用gs_signal_thread_kill命令发送信号通知。这个函数遍历GsSignalSlot,找到一个匹配的线程ID,然后调用"gs_signal_thread_kill(thread_id, RES_SIGNAL);"。函数向特定线程发送信号通知。“#define RES_SIGNAL SIGUSR2”表示使用SIGUSR2发送通知。 + +(4)处理信号函数gs_signal_handle。以下函数在gs_signal_handle中执行。 +1.遍历信号池的使用列表,找到需要处理的信号。 + +2.找到该信号对应的信号处理函数。将GsNode移动到空闲列表中。 + +3.调用gs_signal_handle_check函数检查当前条件是否满足。如果它仍然工作,则回调处理程序。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\347\263\273\347\273\237\350\241\250.md" "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\347\263\273\347\273\237\350\241\250.md" new file mode 100644 index 0000000000000000000000000000000000000000..372ab7e6601fdbdd5f652124aa57bd1e7eb7c7d7 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\347\263\273\347\273\237\350\241\250.md" @@ -0,0 +1,94 @@ +#系统表 +系统表是GaussDB(for openGauss)存放结构元数据的地方,它是GaussDB(for openGauss)数据库系统运行控制信息的来源,是数据库系统的核心组成部分。 + +系统视图提供了查询系统表和访问数据库内部状态的方法。 + +系统表和系统视图要么只对管理员可见,要么对所有用户可见。下面的系统表和视图有些标识了需要管理员权限,这些系统表和视图只有管理员可以查询。 + +用户可以删除后重新创建这些表、增加列、插入和更新数值,但是用户修改系统表会导致系统信息的不一致,从而导致系统控制紊乱。正常情况下不应该由用户手工修改系统表或系统视图,或者手工重命名系统表或系统视图所在的模式,而是由SQL语句关联的系统表操作自动维护系统表信息。 + +##系统表的定义 +``` +openGauss系统表定义全部在src/include/catalog目录下,每个头文件就是一个系统表的定义。如pg_database.h就是对pg_database的定义。在pg_database.h中,主要包括pg_database的表OID(object identifier,对象标识符)、类型OID、结构体定义、字段个数和每个字段ID(identifier,标识符)枚举值、数据库初始化默认值。下面是代码及其具体解释: +/* pg_database本身也是一张表,DatabaseRelationId表示pg_database在系统表pg_class中的OID为1262(pg_class系统表中保存的是表的定义信息) */ +#define DatabaseRelationId 1262 +/* pg_database本身也是一个结构类型,DatabaseRelation_Rowtype_Id表示pg_database在系统表pg_type中的OID为1248 */ +#define DatabaseRelation_Rowtype_Id 1248 +/* pg_database本身也是一个结构类型,DatabaseRelation_Rowtype_Id表示pg_database在系统表pg_type中的OID为1248 */ +/* BKI_SHARED_RELATION表示pg_database是实例级别的系统表 */ +CATALOG(pg_database,1262) BKI_SHARED_RELATION BKI_ROWTYPE_OID(1248) BKI_SCHEMA_MACRO +{ + NameData datname; /* 数据库名称 */ + Oid datdba; /* 数据库拥有者 */ + int4 encoding; /* 字符集编码 */ + NameData datcollate; /* LC_COLLATE 设置值 */ + NameData datctype; /* LC_CTYPE 设置值 */ + bool datistemplate; /* 是否允许作为模板数据库*/ + bool datallowconn; /* 是否允许链接 */ + int4 datconnlimit; /* 最大连接数*/ + Oid datlastsysoid; /* 系统OID最大值*/ + ShortTransactionId datfrozenxid; /* 冻结事务ID,所有小于这个值的事务ID已经冷冻。*/ + Oid dattablespace; /* 数据库的缺省表空间 */ + NameData datcompatibility;/* 数据库兼容模式*/ +#ifdef CATALOG_VARLEN /* 下面字段是变长字段 */ + aclitem datacl[1]; /* 访问权限*/ +#endif + TransactionId datfrozenxid64; /* 冷冻事务ID,64-bit事务ID */ +} FormData_pg_database; + +``` +CATALOG的宏定义代码为: +``` +#define CATALOG(name,oid) typedef struct CppConcat(FormData_,name) +``` +因此CATALOG(pg_database,1262)就是对结构体FormData_pg_database的定义。之所以采用CATALOG,是因为这个格式是和BKI(backend interface,后端接口)脚本约定的格式,BKI脚本根据这个格式生成数据库的建表脚本。 + +接下来是数据库对象字段总数和每个字段ID的值的定义代码 +``` +#define Natts_pg_database 14 +#define Anum_pg_database_datname 1 +#define Anum_pg_database_datdba 2 +#define Anum_pg_database_encoding 3 +#define Anum_pg_database_datcollate 4 +#define Anum_pg_database_datctype 5 +#define Anum_pg_database_datistemplate 6 +#define Anum_pg_database_datallowconn 7 +#define Anum_pg_database_datconnlimit 8 +#define Anum_pg_database_datlastsysoid 9 +#define Anum_pg_database_datfrozenxid 10 +#define Anum_pg_database_dattablespace 11 +#define Anum_pg_database_compatibility 12 +#define Anum_pg_database_datacl 13 +#define Anum_pg_database_datfrozenxid64 14 +``` +最后是创建数据库时的默认数值。代码中的值表示默认创建template1数据库,DATA的字段值和数据库结构体的值对应。 +对应代码如下: +``` +DATA(insert OID = 1 ( template1 PGUID ENCODING "LC_COLLATE" "LC_CTYPE" t t -1 0 0 1663 "DB_COMPATIBILITY" _null_ 3)); +SHDESCR("default template for new databases"); +#define TemplateDbOid 1 +#define DEFAULT_DATABASE "postgres" +``` + +##系统表的访问 +除了创建的表以外,数据库还包含很多系统表。这些系统表包含集群安装信息以及GaussDB(for openGauss)上运行的各种查询和进程的信息。可以通过查询系统表来收集有关数据库的信息。“系统表和系统视图”中每个表的说明指出了表是对所有用户可见还是只对初始化用户可见。必须以初始化用户身份登录才能查询只对初始化用户可见的表。 +openGauss对系统表的访问主要是通过syscache机制。syscache机制是一个通用的机制,主要对系统表的数据进行缓存,提升系统表数据的访问速度。pg_database在枚举类型enum SysCacheIdentifier中定义的枚举值有一个:DATABASEOID,表示根据数据库OID访问pg_database系统表。同时需要把pg_database系统表访问模式添加到“struct cachedesc cacheinfo”中。与pg_database相关的代码如下: +``` + {DatabaseRelationId, /* DATABASEOID */ + DatabaseOidIndexId, + 1, + {ObjectIdAttributeNumber, 0, 0, 0}, + 4}, +``` +这几个值与cachedesc结构体的字段对应,表示pg_database表的OID值、索引的OID值、搜索时有1个key字段、搜索key字段ID为ObjectIdAttributeNumber、初始化为4个hash桶。相关的代码如下: +``` +struct cachedesc { + Oid reloid; /* 缓存的表的OID */ + Oid indoid; /* 缓存数据的索引OID */ + int nkeys; /* 缓存搜索的key的个数 */ + int key[4]; /* key属性的编号*/ + int nbuckets; /* 缓存hash桶的个数*/ +}; +``` +系统表的定义和访问主要逻辑如上问所述。 +与pg_database相关的SQL命令是ALTER DATABASE、CREATE DATABASE、DROP DATABASE,这些命令执行的结果是把数据库相关的信息存储到pg_database系统表中。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\347\272\277\347\250\213\346\261\240\346\212\200\346\234\257.md" "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\347\272\277\347\250\213\346\261\240\346\212\200\346\234\257.md" new file mode 100644 index 0000000000000000000000000000000000000000..e621b84dc64b6a42bc3c2311ea990fe0a7c86ac1 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\205\254\345\205\261\347\273\204\344\273\266\344\271\213\347\272\277\347\250\213\346\261\240\346\212\200\346\234\257.md" @@ -0,0 +1,90 @@ +#线程池技术 + +##为什么要使用线程池 +在OLTP领域中,数据库需要处理大量的客户端连接。因此,高并发场景的处理能力是数据库的重要能力之一。 +对于外部连接最简单的处理模式是per-thread-per-connection模式,即来一个用户连接产生一个线程。这个模式好处是架构上处理简单,但是高并发下,由于线程太多,线程切换和数据库轻量级锁区域的冲突过大导致性能急剧下降,使得系统性能(吞吐量)严重下降,无法满足用户性能的SLA。 +因此,需要通过线程资源池化复用的技术来解决该问题。线程池技术的整体设计思想是线程资源池化、并且在不同连接之间复用。系统在启动之后会根据当前核数或者用户配置启动固定一批数量的工作线程,一个工作线程会服务一到多个连接session,这样把session和thread进行了解耦。因为工作线程数是固定的,因此在高并发下不会导致线程的频繁切换,而由数据库层来进行session的调度管理。 + +##线程池原理 + +### 线程池工作流程 +![](/api/attachments/397897) +### 线程池对象 +![](/api/attachments/397892) +些对象相互配合实现了线程池机制,它们的主要交互过程如下。 + +1.客户端向数据库发起连接请求,Postmaster线程接收到连接请求并被唤醒。Postmaster线程创建该连接对应的socket,调用ThreadPoolControler函数创建会话(session)数据结构。ThreadPoolControler函数遍历当前所有的Thread Group(线程组),找到当前活跃会话数量最少的Thread Group,并把最新的会话分发给该Thread Group,加入该Thread Group的epoll列表之中。 +2.Thread Group的listener线程负责监听epoll列表中所有的客户连接。 +3.客户端发起任务请求,listener线程被唤醒。listener线程检查当前的Thread Group是否有空闲worker线程;如果有,则把当前会话分配给该worker线程,并唤醒该worker线程;如果没有,则把该会话放在等待队列之中。 +4.worker线程被唤醒后,读取客户端连接上的请求,执行相应请求,并返回请求结果。在一次事务结束(提交、回滚)或者事务超时退出的时候,worker线程的一次任务完成。worker线程将session返回给listener线程,listener线程继续等待该会话的下一次请求。worker线程返还会话后,检查会话等待队列;如果存在等待响应请求的会话,则直接从该队列中取出新的会话并继续工作;如果没有等待响应的session,则将自身标记为free(空闲)状态,等待listener线程唤醒。 +5.客户端断开连接时,worker线程被唤醒,关闭连接,同时清理会话相关结构,释放内存和fd(文件句柄)等资源。 +6.如果worker线程FATAL级别错误退出,退出时worker线程会从worker队列中注销掉。此时listener线程会重新启动一个新的worker线程,直到达到指定数量的worker线程。 + +### 线程池实现 +线程池函数由GUC参数enable_thread_pool控制。当该参数为true时,线程池功能才可用。 +Postmaster线程在ServerLoop中判断如果你启用了线程池的函数,就会在线程池初始化时调用" threadpoolcontroller:: Init"函数。在线程池的初始化过程中,通过确定NUMA节点的数量来处理NUMA结构。相关代码如下: +``` + if (threadPoolActivated) { + bool enableNumaDistribute = (g_instance.shmem_cxt.numaNodeNum > 1); + g_threadPoolControler->Init(enableNumaDistribute); +} +``` +" threadpoolcontroller:: Init"函数的主要作用是创建m_sessCtrl成员和成员m_groups对象,根据核策略分配线程数,调用"ThreadPoolGroup:: Init"函数初始化线程组,调用"ThreadPoolGroup:: WaitReady"函数等待每个线程组初始化结束。创建m_scheduler成员对象,并为“ThreadPoolScheduler:: StartUp”线程池线程调度调用函数。在ThreadPoolGroup::init函数中,创建m_listener对象并启动listener线程。threadworkersentinel函数分配内存并初始化每个worker的互斥锁和条件变量。调用“ThreadPoolGroup:: AddWorker”创建工作对象函数,启动工作线程。 + +Postmaster线程在ServerLoop中如果有客户端链接请求,监听判断线程池启用函数时,会调用“threadpoolcontroller:: DispatchSession”函数进行会话分发。相关代码如下: +``` +if (threadPoolActivated &&!(i < MAXLISTEN && t_thrd.postmaster_cxt.listen_sock_type[i] == HA_LISTEN_SOCKET)) +result = g_threadPoolControler->DispatchSession(port); +/* ThreadPoolControler::DispatchSession的代码实现如下,找到一个会话数最少的线程组,创建会话,把会话添加到线程组的监听线程中 */ +int ThreadPoolControler::DispatchSession(Port* port) +{ + ThreadPoolGroup* grp = NULL; + knl_session_context* sc = NULL; + grp = FindThreadGroupWithLeastSession(); + if (grp == NULL) { + Assert(false); + return STATUS_ERROR; + } + sc = m_sessCtrl->CreateSession(port); + if (sc == NULL) + return STATUS_ERROR; + grp->GetListener()->AddNewSession(sc); + return STATUS_OK; +} +``` +线程的主要功能是TpoolListenerMain(ThreadPoolListener* listener)。在此函数中设置线程名称和信号处理程序,创建一个epoll等待事件,通知Postmaster线程已经准备好,调用t_pool_listener_loop函数(称为"ThreadPoolListener:: WaitTask()"函数已达到等待事件状态)。如果有一个即将到来的事件,调用“ThreadPoolListener:: HandleConnEvent”函数来查找与会话对应的事件。调用"ThreadPoolListener:: DispatchSession"函数,如果有空闲的工作线程,通知工作线程进行处理;如果没有空闲的工作线程,会话就挂在等待队列上。 + +工作线程的主要功能是普通的SQL处理程序PostgresMain。与非线程模式相比,工作线程有三个主要的处理功能: + +(1)通知工作线程准备好了。 + +(2)等待会话通知。 + +(3)连接退出处理。 + +工作线程的相关代码如下: +``` + if (IS_THREAD_POOL_WORKER) { + u_sess->proc_cxt.MyProcPort->sock = PGINVALID_SOCKET; + t_thrd.threadpool_cxt.worker->NotifyReady(); +} + if (IS_THREAD_POOL_WORKER) { + t_thrd.threadpool_cxt.worker->WaitMission(); + Assert(u_sess->status != KNL_SESS_FAKE); + } + case 'X': + case EOF: + RemoveTempNamespace(); + InitThreadLocalWhenSessionExit(); + if (IS_THREAD_POOL_WORKER) { + t_thrd.threadpool_cxt.worker->CleanUpSession(false); + break; + } +``` +“ThreadPoolWorker::WaitMission”函数的主要作用是阻塞所有系统信号,避免系统信号比如SIGHUP等中断当前的处理。清除线程上的会话信息,保证没有上一个会话的内容,等待会话上新的请求,把会话给线程进行处理,允许系统信号中断。 + +“ThreadPoolWorker::CleanUpSession”函数的主要作用是清除会话,从Listener中去除会话,释放会话资源。 + +##总结 +线程池主要是解决大并发的用户连接,在一定程度上可以起到流量控制的作用,即使用户的连接数很多,后端也不需要分配太多的线程。 +线程是OS的一种资源,如果线程太多,OS资源占用很多,并且大量线程的调度和切换会带来昂贵的开销。如果没有线程池,随着连接数的增多,系统的吞吐量会逐渐降低。另外一方面,把线程池划分为线程组,可以很好地匹配NUMA CPU架构的节点,提升多核情况下的访问性能。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\345\210\206\345\270\203\345\274\217\351\203\250\347\275\262\357\274\210\344\270\200\357\274\211.md" "b/TestTasks/wang-luyang/\345\210\206\345\270\203\345\274\217\351\203\250\347\275\262\357\274\210\344\270\200\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..941e4aef922bb599bd1610806c679f1be94a6154 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\210\206\345\270\203\345\274\217\351\203\250\347\275\262\357\274\210\344\270\200\357\274\211.md" @@ -0,0 +1,57 @@ +GaussDB(openGauss)定位为企业级云分布式数据库,架构上着重构筑传统数据库的企业级能力和互联网分布式数据库的高扩展和高可用能力。 +传统数据集中式数据库与分布式数据库对比: + +|集中式数据库|分布式数据库 | +|--|--| +| 仅在单个位置存储,定位,维护 | 有多个相互连接且分布在不同物理位置 | +有多个用户的情况下访问时间更长|多个用户情况下访问时间更少| +数据库管理修改备份更容易|管理,修改和备份困难 +提供统一完整的视图|难以提供统一视图 +|具有数据高度一致性|可能具有数据复制,数据一致性较低 +|故障后无法访问|一个数据库故障不会影响其他数据库 +成本较低|比较昂贵 + + + +## 集中式部署 +集中式部署又包括单机和主备两种类型。 +以主备为例,支持1+2(最大保护)主备,基于数据库日志复制的热备,在单机性能可满足需求的情况下,提供高可用。 +其中,1+1(最大可用)指的是,数据会同步写往备机。但如果出现网络等影响,无法完成同步操作,会转为异步。后续网络恢复,会自动追上。在数据不同步期间,切换会有数据丢失。1+2(最大保护)则意味着数据会同步写往备机,且要求必须有一个确认,才向客户端返回。可靠性高。 +集中式版本拥有开源生态,用户可以通过开源网站直接下载,作为国内唯一开源数据库,也是华为开源、开放、不LOCKIN单一厂商的最佳证明。 +## 分布式部署: +分布式部署方面,数据按shard划分,读写负载准线性扩展,满足大规模业务量场景,支持两地三中心高可用部署。另外,分布式版本承载华为云自研分布式组件体系,是传统企业拥抱互联网,面向未来海量事务型场景挑战的有力保障。 +分布式架构又可以分为独立部署&混合部署: + + - 混合部署方案适合通用客户,其方案包括:1、各角色3副本,数据3副本部署;2、各角色进程合一部署,对外只体现数据库节点;这样部署的优势是组网简洁明了,交付界面高效;起点配置要求低,适配场景比较通用;和未来的技术演进方向匹配。 + - 另一个独立部署方案适合高端客户,它的方案包括:1、各角色3副本,数据3副本部署;2、关键角色进程分开部署,对外体现CMS、GTM、CN、DN主、DN备。独立部署方案下,用户可以根据业务负载确定CN和DN的最佳比例,达成最高效的组网。 + +### 如何保证分布式事务的原子性和强一致性? + - **原子性(Atomic)** + + 一个事务包含多个操作,这些操作要么全部执行,要么全都不执行。实现事务的原子性,要支持回滚操作,在某个操作失败后,回滚到事务执行之前的状态。 + + - **一致性(Consistency)** + + 一致性是指事务使得系统从一个一致的状态转换到另一个一致状态。事务的一致性决定了一个系统设计和实现的复杂度。在用户获得响应时,主库和备库的数据副本已经达到了一致,所以后续的读操作肯定不会出现问题,这种模式称为**强一致性**。 + + 1. 分布式事务原子性和两阶段提交协议 + +openGauss采用两阶段提交(2PC)协议,保证分布式事务的原子性,防止出现部分DN提交、部分DN回滚的“中间态”事务,顾名思义,两阶段提交协议将事务的提交操作分为两个阶段: + +> 阶段一,准备阶段(prepare phase),在这个阶段,将所有提交操作所需要使用到的信息和资源全部写入磁盘,完成持久化; +> 阶段二,提交阶段(commit prepared phase),根据之前准备好的提交信息和资源,执行提交或回滚操作。 + +两阶段提交协议之所以能够保证分布式事务原子性的关键在于:一旦准备阶段执行成功,那么提交需要的所有信息都完成持久化下盘,即使后续提交阶段某个DN发生执行错误,该DN可以再次从持久化的提交信息中尝试提交,直至提交成功。最终该分布式事务在所有DN上的状态一定是相同的,要么所有DN都提交,要么所有DN都回滚。因此,对外来说,该事务的状态变化是原子的。 + + 2. 分布式事务一致性和全局事务管理 + +为了防止瞬时不一致现象,支持分布式事务的强一致性,我们需要全局范围内的事务号和快照,以保证全局MVCC和快照的一致性。在openGauss中,GTM负责提供和分发全局的事务号和快照。对于任何一个读事务,其都需要到GTM上获取全局快照;对于任何一个写事务,其都需要到GTM上获取全局事务号。对于读事务来说,由于写事务在其从GTM获取的快照中,因此即使写事务在不同DN上的提交顺序和读事务的执行顺序不同,也不会造成不一致的可见性判断和不一致的读取结果。 + + +分布式数据库缺陷: +``` +众多节点之间通信会花费大量时间。 +数据的安全性和保密性在众多节点之间会受到威胁。 +在复杂的存取结构中,原有的有效存取数据技术可能不再适用。 +分布式的数据划分、负载均衡、分布式事务处理和分布式执行技术缺乏新的突破。 +``` \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\345\210\206\345\270\203\345\274\217\351\203\250\347\275\262\357\274\210\344\272\214\357\274\211.md" "b/TestTasks/wang-luyang/\345\210\206\345\270\203\345\274\217\351\203\250\347\275\262\357\274\210\344\272\214\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..ecda314cd925d52ad6458320a10bc08764d6c3f7 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\210\206\345\270\203\345\274\217\351\203\250\347\275\262\357\274\210\344\272\214\357\274\211.md" @@ -0,0 +1,60 @@ + +### GaussDB(openGauss)分布式部署技术 + + 1. 分布式执行框架 + +业务应用下发SQL给Coordinator,SQL可以包含对数据的增(insert)、删(delete/drop)、改(update)、查(select)。Coordinator利用数据库的优化器生成执行计划,每个DN会按照执行计划的要求去处理数据。 + +因为数据是通过一致性Hash技术均匀分布在每个节点,因此DN在处理数据的过程中,可能需要从其他DN获取数据,GaussDB 提供了三种stream流(广播流、聚合流和重分布流)来降低数据在DN节点间的流动。 +DN将结果集返回给Coordinate进行汇总。Coordinator将汇总后的结果返回给业务应用。 + + 2. GTM-Lite技术 + +GTM-Lite技术可以在保证事务全局强一致的同时,提供高性能的事务处理能力,避免了单GTM的性能瓶颈。这里的高性能事务管理指的是无锁、多版本、高并发事务技术。而且分布式的GTM-Lite方案提供全局事务快照和提交号管理,实现强一致性,且无中心节点性能瓶颈。 + + 3. 基于NUMA-Aware实现高性能事务处理 + +基于鲲鹏CPU的NUMA-Aware数据库架构,性能更强。 + + 4. 集群HA,多层级冗余实现系统无单点故障 + +GaussDB(openGauss)通过硬件冗余、实例冗余、数据冗余,实现整个系统无单点故障,高可用。 +其中硬件高可用包括存储磁盘RAID冗余、网络双交换机冗余、多网卡冗余、主机UPS电源保护。 +软件高可用则包括协调节点CN实例多活冗余、数据节点/全局事务管理/ 集群管理器实例Active-Standby冗余。 +除此之外,还支持软硬件故障检测,最终进一步保障数据库的高可用。 + + 5. 跨AZ/Region容灾技术带来高可用 + +集群内HA,数据不丢失,业务秒级中断;同城跨AZ容灾,数据不丢失,分钟级恢复,以及两地三中心部署。除了同城跨AZ和两地三中心,GaussDB(openGauss)还在探索异地多活,其主要特征有: +支持多中心统一查询及全局一致读,整体资源利用率高;灵活的高可用方案:通过配置多副本,可以实现DC,AZ,Region级高可用容灾策略;负载分担及故障无缝切换;支持平滑在线扩容。 + + 6. Scale-out在线横向扩展带来高扩展 + +GaussDB(openGauss)支持但集群最大256节点,节点扩展下能够获得卓越的性能线性比同时节点扩容能够做到完全在线,对客户透明,节点扩容完成后DN数据自动重分布,CN自动完成负载均衡。 + + +### 附:openGauss与shardingsphere +openGauss 融合了众多开源组件,用以构建集数据水平扩展、分布式事务及治理一体化的全栈开源分布式解决方案。其中 ShardingSphere-Proxy 为开源分布式解决方案,具有分库、分表、分布式事务、弹性伸缩、读写分离等众多能力;HAProxy 结合 Patroni 的 REST API,可以始终识别数据库的主节点,保证高可用场景,同时可实现负载均衡;每个 Patroni 高可用节点支持一主多备,每个节点使用 Paxos 协议保证数据的一致性,各个节点可以部署在相同或不同的区域,用以保证多地多中心的数据安全。 + +openGauss在OLTP场景下有超高性能和可靠性,shardingsphere是业界领先的开源分布式中件间解决方案,二者结合使得用户性能、可靠性与可扩展性兼得: + + - SQL适配 + +> 针对语法处理额外增加了专用于openGuass的功能模块,包括适配支持了表、索引、CURD语法、事务等功能,使用shardingsphere-proxy就像使用openGauss一样平滑过渡。 + + - 极致性能 + +> 适配openGauss批量插入协议,极大的提升了大规模插入性能;针对openGauss调整执行计划,优化多个瓶 +> 颈 点 和 热 点 函 数 , 充 分 利 用 openGauss高性能特性。 已验证16节点下性能>1000万tpmc + + - 弹性伸缩 + +> scaling功能完美适配openGauss逻辑复制槽的mpp_decoding插件,迁移时服务不停止,扩缩容数据不丢失,新旧配置生效秒级切换。 + + - 安全认证 + +> 适配openGauss更安全的sha256和 sm3认证协议,打造默认安全的分布式 数据库。 + + - 双路由机制 + +> 支持jdbc和proxy混合访问数据库动 态双路由模式,保持客户端始终处于 高 性 能 状 态 , 避 免 超 大 查 询 导 致 OOM。 diff --git "a/TestTasks/wang-luyang/\345\215\232\345\256\242\346\200\273\347\273\223-\346\257\205\351\230\263\351\230\237-openGauss.md" "b/TestTasks/wang-luyang/\345\215\232\345\256\242\346\200\273\347\273\223-\346\257\205\351\230\263\351\230\237-openGauss.md" new file mode 100644 index 0000000000000000000000000000000000000000..fc89d6bab7d59c6c0e804455abf4e64552fc4d01 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\215\232\345\256\242\346\200\273\347\273\223-\346\257\205\351\230\263\351\230\237-openGauss.md" @@ -0,0 +1,85 @@ + +## 博客整体介绍 + +### 参与队伍 + +毅阳队:洪毅、王路阳、黄俊源 + +### 个人主页 + +* [洪毅](https://forum.gitlink.org.cn/accounts/Eejvbfo94/memos) +* [王路阳](https://forum.gitlink.org.cn/accounts/Rorre/memos) +* [黄俊源](https://forum.gitlink.org.cn/accounts/litm12138/memos) + +### 整体主题 + +整体主题包括但不限于**基本操作和部署、密态等值查询技术、安全管理、慢SQL原因剖析、事务机制、公共组件**等内容。专注于从对源代码的解读中、openGauss的实际安装和使用中总结出模块的作用机制和原理。在我们的努力下,一共完成了60+篇博客。 + +博客力求跳脱出单个源代码文件的限制,以全局的视角审视各个源代码文件、数据结构、执行函数在openGauss的某个特定工作的运作流程中发挥的作用,并将我们的**理解、执行流程图等以及相关的代码**写于博客中,以带给阅读者对openGauss运转机制更为深刻的认识。 + +## 工作介绍 + +### 黄俊源 + +#### 概述 + +1. 对openGauss中的**全密态技术**进行了分析,包括数据加密步骤、加密驱动和数据检索等。 +2. 对openGauss的**安全管理机制**进行分析,包括认证机制、角色管理、对象权限管理和审计等。 +3. 对导致**慢SQL**的原因进行了实践验证和分析,并整理总结了如何将慢SQL改写为快SQL。 + +#### 逻辑关系图 + +![](/api/attachments/398303) + + +### 王路阳 + +#### 概述 + +1. 结合源码分析表达式处理机制,探索OpenGauss数据库**分布式部署**相关技术 +2. 搜集并总结了**OpenGauss基本指令,gsql元命令集、索引和表达式语法** + +#### 逻辑关系图 + + +![](/api/attachments/398309) + +### 洪毅 + +#### 概述 + +1. 对openGauss的**公共组件**进行了分析,包括**系统表、数据库初始化、多线程架构、线程池技术、内存管理、多维监控和模拟信号机制**。 + +2. 对openGauss的**事务机制**进行了分析,包括**事务状态机、事务ID分配及CLOG/CSNLOG、MVCC可见性判断机制、进程内多线程管理机制**等 + +#### 逻辑关系图 + +![](/api/attachments/398306) + +## 五篇代表性博客 + +[表达式篇总结](https://forum.gitlink.org.cn/forums/8192/detail) + +[元命令集-总结篇](https://forum.gitlink.org.cn/forums/8196/detail) + +[慢SQL原因分析](https://forum.gitlink.org.cn/forums/8168/detail) + +[密态等值查询](https://forum.gitlink.org.cn/forums/8169/detail) + +[公共组件](https://forum.gitlink.org.cn/forums/8170/detail) + + + +## 队伍总结 + +在认真阅读[比赛要求](https://www.gitlink.org.cn/competitions/index/gcc-annotation-2022)的时候,我们发现第四届的参赛选手已经撰写了关于openGauss的很多博客,与此同时,我们还发现在openGauss社区也有非常丰富的相关资料。我们相信,站在巨人的肩膀上能够让我们走得更远、认识的更深刻。但为了保证我们作品的原创性、发挥自主创造力和想象力,我们**在一开始并不选择参考别人写得资料,而是仅仅参考[openGauss的总体介绍](https://forum.gitlink.org.cn/forums/4662/detail),并详细梳理阅读[项目源码](https://www.gitlink.org.cn/huawei/openGauss-server/tree/master/src/gausskernel),在理解之后逐步写出我们的初步作品**。 + +思想碰撞才会产生灵感的火花,在完成初步作品之后,我们开始阅读其他选手撰写的博客,遇到不相同的主题,我们也会结合源码力求理解,遇到相同的主题则进行两者的对比,并思考谁的理解更加深刻或正确等,在觉得别人的思考更好的时候,我们会**附上引用链接,并更为详细说明问题,发表见解**。以此来修改深化我们的作品。 + +注:因精力有限,撰写博客时难免有疏漏,若参考作品未给予引用说明的,请联系我们,侵删。 + +完成作品后,也想着向openGauss开源社区贡献我们的力量,因此我们在csdn等平台上也发布了我们的博客。 + +![](/api/attachments/398313) + +最后,感谢大赛官方给予的机会,让我们能够对 openGauss 国产数据库进行学习与分析,并在发布博客,终于对 openGauss 有了一定的了解,提高了我们对国产数据库的兴趣,增强了我们对国产数据库的自信心。 diff --git "a/TestTasks/wang-luyang/\345\237\272\347\241\200\346\223\215\344\275\234\357\274\210\344\270\200\357\274\211.md" "b/TestTasks/wang-luyang/\345\237\272\347\241\200\346\223\215\344\275\234\357\274\210\344\270\200\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..d14224eda77ca6f4b283de234eee827b101de10d --- /dev/null +++ "b/TestTasks/wang-luyang/\345\237\272\347\241\200\346\223\215\344\275\234\357\274\210\344\270\200\357\274\211.md" @@ -0,0 +1,105 @@ +# OpenGauss及相关基础操作(一) +搭建OpenGauss数据库中搜集到的一些相关的指令操作: +## OpenGauss部分: +``` +--以操作系统用户omm登录数据库主节点 +su - omm + +--启动服务 +gs_om -t start + +--重启服务 +gs_om -t restart + +--关闭服务 +gs_om -t stop + +--查询实例状态 +gs_om -t status --detail + +--检查数据库性能 +gs_checkperf -i pmk -U omm +gs_checkperf -i pmk -U omm --detai(详细信息) + +--登陆默认数据库postgres +gsql -d postgres -p 36000 + +--登陆自建数据库 +gsql -d 数据库名 -p 36000 -U 用户名 -W 密码 -r +``` +## Linux部分: +### vi/vim +文本编辑器,若文件存在则是编辑,若不存在则是创建并编辑文本。vi与vim它们都是多模式编辑 + +器,不同的是vim 是vi的升级版本,它不仅兼容vi的所有指令,而且还有一些新的特性在里面。 + +vim的这些优势主要体现在以下几个方面: + + - 1、多级撤消: 我们知道在vi里,按 u只能撤消上次命令,而在vim里可以无限制的撤消。 + - 2、易用性: vi只能运行于unix中,而vim不仅可以运行于unix,windows ,mac等多操作平台。 + - 3、语法加亮: vim可以用不同的颜色来加亮你的代码。 + - 4、可视化操作: 就是说vim不仅可以在终端运行,也可以运行于x window、 mac os、 windows。 + - 5、对vi的完全兼容: 某些情况下,你可以把vim当成vi来使用。 + +命令语法: + +``` +vim [参数] +参数说明:可编辑的文件名。 +命令示例: + +--编辑名为tesout的txt文本 +vim tesout.txt +``` + +--编辑器三种模式 +1.正常模式:其它模式下按Esc或Ctrl+[进入,左下角显示文件名或为空。 +2.插入模式:正常模式下按i键进入,左下角显示–INSERT–。 +3.可视模式:正常模式下按v键进入,左下角显示–VISUAL–。 + +--退出命令(输入冒号后输入字符回车) +:wq //保存并退出。 +:q! //强制退出并忽略所有更改。 +:e! //放弃所有修改,并打开原有文件。 +### cd +显示当前目录的名称,或切换当前的目录(打开指定目录)。 + +命令语法: + +``` +cd [参数] +参数说明: +无参数:切换用户当前目录。 +. :表示当前目录; +… :表示上一级目录; +~ :表示home目录; +/ :表示根目录。 +``` + +命令示例: + +``` +--切换到usr目录下的bin目录中: +cd /usr/bin + +--切换到用户home目录: +cd + +--切换到当前目录(cd后面接一个.): +cd . + +--切换到当前目录上一级目录(cd后面接两个.): +cd .. + +--切换到用户home目录: +cd ~ + +--切换到根目录下: +cd / +``` + +绝对路径:在Linux中,绝对路径是从/(即根目录)开始的, +例如 /opt/software、/etc/profile, 如果目录以 / 就是绝对目录。 + +相对路径:是以 . 或 … 开始的目录。 . 表示用户当前操作所在的位置, +而 … 表示上级目录。例如 ./gs_om 表示当前目录下的文件或者目录。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\345\237\272\347\241\200\346\223\215\344\275\234\357\274\210\344\270\211\357\274\211.md" "b/TestTasks/wang-luyang/\345\237\272\347\241\200\346\223\215\344\275\234\357\274\210\344\270\211\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..a46d17e535f1b60e4be39884b2742404f6756c76 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\237\272\347\241\200\346\223\215\344\275\234\357\274\210\344\270\211\357\274\211.md" @@ -0,0 +1,380 @@ +# OpenGauss及相关基础操作(三) +搭建OpenGauss数据库中搜集到的一些相关的指令操作: +## OpenGauss部分: +``` +--列举schema: +postgres=# \dn + +--查看索引 +postgres=# \di + +--查询表空间 +使用gsql程序的元命令查询表空间。postgres=# \db +检查pg_tablespace系统表。如下命令可查到系统和用户定义的全部表空间。 +postgres=# select spcname from pg_tablespace; + +--查看数据库用户列表 +postgres=# select * from pg_user; + +--要查看用户属性 +postgres=# select * from pg_authid; + +--查看所有角色 +postgres=# select * from PG_ROLES; + +--切换用户 +postgres=# \c – username + +--修改omm管理员密码 +--openGauss数据库登陆如果提示omm密码过期,需要修改omm管理员密码,例如: +alter role omm identified by '新密码' replace '旧密码' + +--退出数据库 +postgres=# \q +​ +``` +## Linux部分: + +### wget +wget是Linux下下载文件的最常用命令。wget支持HTTP,HTTPS和FTP协议,支持自动下载,即可以在用户退出系统后在后台执行,直到下载结束。 + +命令语法: + +``` +wget [选项] [URL] + +常用选项: + +-c:接着下载没下载完的文件; + +-b:启动后转入后台执行; + +-P:指定下载目录; + +-O:变更下载文件名; + +–ftp-user --ftp-password:使用FTP用户认证下载。 + +参数说明: + +指定的文件下载URL地址。 +``` + +命令示例: + +``` +下载openGauss数据库安装文件到当前文件夹: +--下载openGauss数据库安装文件到当前文件夹: +wget https://opengauss.obs.cn-south-1.myhuaweicloud.com/1.1.0/x86_openEuler/openGauss-1.1.0-openEuler-64bit-all.tar.gz + +--使用wget断点续传: +wget –c https://opengauss.obs.cn-south-1.myhuaweicloud.com/1.1.0/x86_openEuler/openGauss-1.1.0-openEu +``` + +### ln +为某一个文件在另外一个位置建立一个同步的链接(软硬链接,不带选项为硬链接)。当需要在不 + +同的目录,用到相同的文件时,就不需要在每一个需要要的目录下都放一个必须相同的文件,我们 + +只要在某个固定的目录,放上该文件,然后在 其它的目录下用ln命令链接(link)它就可以,不必 + +重复的占用磁盘空间。 + +命令语法: + +``` +ln [选项] 参数1 参数2 + +常用选项: + +-b --删除,覆盖以前建立的链接; + +-d --允许超级用户制作目录的硬链接; + +-s --软链接(符号链接)。 + +参数说明: + +参数1:源文件或目录。 + +参数2:被链接的文件或目录。 + +常用选项: + +-b --删除,覆盖以前建立的链接; + +-d --允许超级用户制作目录的硬链接; + +-s --软链接(符号链接)。 +``` + +命令示例: + +``` +--为python3文件创建软链接/usr/bin/python,如果python3丢失,/usr/bin/python将失效: +ln -s python3 /usr/bin/python + +--为python3创建硬链接/usr/bin/python,python3与/usr/bin/python的各项属性相同: +ln python3 /usr/bin/python +``` + +### mkdir +创建指定的名称的目录,要求创建目录的用户在当前目录中具有写权限,并且指定的目录名不能是当前目录中已有的目录。 + +命令语法: + +``` +mkdir [选项] [参数] + +常用选项: + +-p --可以是一个路径名称。此时若路径中的某些目录尚不存在,加上此选项后, +系统将自动建立好那些尚不存在的目录,即一次可以建立多个目录(递归); + +-v --每次创建新目录都显示信息; + +-m --设定权限<模式> (类似chmod),而不是rwxrwxrwx减umask。 + +参数说明: + +需要创建的目录。 +``` + +命令示例: + +``` +--创建一个空目录: +mkdir test + +--递归创建多个目录: +mkdir -p /opt/software/openGauss + +--创建权限为777的目录(目录的权限为rwxrwxrwx): +mkdir –m 777 test +``` + +### chmod +更改文件权限。 + +命令语法: + +``` +chmod [选项] + +常用选项: + +-R, --以递归的方式对目前目录下的所有文件与子目录进行相同的权限变更。 +参数说明: + +mode:权限设定字串,详细格式如下 : +[ugoa...][[+-=][rwxX]...][,...], + +其中,[ugoa…]:u 表示该档案的拥有者,g 表示与该档案的拥有者属于同一个群体(group)者,o 表示其他以外的人,a 表示所有(包含上面三者);[±=]:+ 表示增加权限,- 表示取消权限,= 表示唯一设定权限;[rwxX]:r 表示可读取,w 表示可写入,x 表示可执行,X表示只有当该档案是个子目录或者该档案已经被设定过为可执行。 + +file:文件列表(单个或者多个文件、文件夹)。 +``` + +命令示例: + +``` +设置所有用户可读取文件 cluterconfig.xml: +--设置所有用户可读取文件 cluterconfig.xml: +chmod ugo+r cluterconfig.xml +chmod a+r cluterconfig.xml + +--设置当前目录下的所有档案与子目录皆设为任何人可读写: +chmod -R a+rw * +​ +--赋予cluterconfig.xml文件可读可写可执行权限(所有权限): +chmod 777 cluterconfig.xml + + +--赋予/opt/software/openGauss目录下所有文件及其子目录 用户所有权限组可读可执行权限, +其他用户可读可执行权限: +chmod R 755 /opt/software/openGauss +``` + +​ + +### chown +利用 chown 将指定文件的拥有者改为指定的用户或组,用户可以是用户名或者用户ID;组可以是组名或者组ID;文件是以空格分开的要改变权限的文件列表,支持通配符。只有系统管理者(root)才有这样的权限。使用权限 : ****root****。 + +``` +命令语法: + +chown [选项] user[:group] file... + +常用选项: + +-c : 显示更改的部分的信息; + +-f : 忽略错误信息; + +-R : 处理指定目录以及其子目录下的所有文件。 + +参数说明: + +user : 新的文件拥有者的使用者 ID。 + +group : 新的文件拥有者的使用者组(group)。 + +file:文件。 +``` + +命令示例: + +``` +--将文件 file1.txt 的拥有者设为omm,群体的使用者dbgrp: +chown omm:dbgrp /opt/software/openGauss/clusterconfig.xml + +--将目前目录下的所有文件与子目录的拥有者皆设为omm,群体的使用者dbgrp: +chown -R omm:dbgrp * +​ +``` + +### ls +列出文件和目录的内容。 + +命令语法: + +``` +ls [选项] [参数] + +常用选项: + +-l --以长格式显示,列出文件的详细信息,如创建者,创建时间,文件的读写权限列表等等; + +-a --列出文件下所有的文件,包括以".“和”…"开头的隐藏文件 (Linux下文件隐藏文件是以 .开头的,如果存在 … 代表存在着父目录); + +-d --列出目录本身而非目录内的文件,通常要与-l一起使用; + +-R --同时列出所有子目录层,与-l相似,只是不显示出文件的所有者,相当于编程中的“递归”实现; + +-t --按照时间进行文件的排序,Time(时间); + +-s --在每个文件的后面打印出文件的大小,size(大小); + +-S --以文件的大小进行排序。 + +参数说明: + +目录或文件。 +``` + +命令示例: + +``` +--以长格式列出当前目录中的文件及目录: +ls -l +``` + +### cp +复制文件或者目录。 + +命令语法: + +``` +cp [选项] 参数1 参数2 + +常用选项: + +-f --如果目标文件无法打开则将其移除并重试(当 -n 选项存在时则不需再选此项); + +-n --不要覆盖已存在的文件(使前面的 -i 选项失效); + +-I --覆盖前询问(使前面的 -n 选项失效); + +-p --保持指定的属性(默认:模式,所有权,时间戳),如果可能保持附加属性:环境、链接、xattr 等; + +-R,-r --复制目录及目录内的所有项目。 + +参数说明: + +参数1:源文件。 + +参数2:目标文件。 +``` + +命令示例: + +``` +​--将home目录中的abc文件复制到opt目录下: +cp /home/abc /opt + +注:目标文件存在时,会询问是否覆盖。这是因为cp是cp -i的别名。 +目标文件存在时,即使加了-f标志,也还会询问是否覆盖。 +``` + +​ + +### rm +删除一个目录中的一个或多个文件或目录,它也可以将某个目录及其下的所有文件及子目录均删除。对于链接文件,只是删除了链接,原有文件均保持不变。 + +rm是一个危险的命令,使用的时候要特别当心,否则整个系统就会毁在这个命令(比如在/(根目录)下执行rm * rf)。所以,我们在执行rm之前最好先确认一下在哪个目录,到底要删除什么东西,操作时保持高度清醒的头脑。 + +命令语法: + +``` +rm [选项] 文件 + +常用选项: + +-f --忽略不存在的文件,从不给出提示; + +-r --指示rm将参数中列出的全部目录和子目录均递归地删除。 + +参数说明: + +需要删除的文件或目录。 +``` + +命令示例: + +``` +​--删除文件: +rm qwe +注:输入rm qwe命令后,系统会询问是否删除,输入y后就会删除文件,不想删除文件则输入n。 + +--强制删除某个文件: +rm-rf clusterconfig.log +``` + +​ + +### cat +连接文件并在标准输出上输出。这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用。 + +命令语法: + +``` +cat [选项] [参数] + +常用选项: + +-E --在每行结束显示$; + +-n –由1开始对给所有输出行编号; + +-b 或 --number-nonblank:和 -n 相似,只不过对于空白行不编号; + +-v --使用 ^ 和 M- 符号,除了 LFD 和 TAB 之外。 + +参数说明: + +可操作的文件名。 +``` + +命令示例: + +``` +显示testfile文件的内容: +​--显示testfile文件的内容: +cat textfile + +--把 textfile1 和 textfile2 的文档内容加上行号(空白行不加) +之后将内容追加到 textfile3 文档里: +cat -b textfile1 textfile2 >> textfile3 + +--向/etc/profile中追加内容(输入EOF表示结束追加): +cat >>/etc/profile<:设置服务器的用户和密码; + +-x/–proxy :在给定的端口上使用HTTP代理; + +-#/–progress-bar:进度条显示当前的传送状态。 + +参数说明: + +URL:指定的文件传输URL地址。 +``` + +命令示例: + +``` +将url(https://mirrors.huaweicloud.com/repository/conf/openeuler_x86_64.repo)的内容保存到/etc/yum.repos.d/openEuler_x86_64.repo 文件中。 +--将url(https://mirrors.huaweicloud.com/repository/conf/openeuler_x86_64.repo)的内容保存到/etc/openuler.repo 文件中。 +curl -o /etc/openuler.repo https://mirrors.huaweicloud.com/repository/conf/openeuler_x86_64.repo +如果在传输过程中掉线,可以使用-C的方式进行续传。 +``` + +### yum +Shell 前端软件包管理器。基于 RPM 包管理,能够从指定的服务器自动下载 RPM 包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖的软体包,无须繁琐地一次次下载和安装。 + +命令语法: + +``` +yum [options] [command] [package ...] + +常用选项: + +-h:查看帮助; + +-y:当安装过程提示选择全部为 “yes”; + +-q:不显示安装的过程。 + +参数说明: + +command:要进行的操作。 + +package:安装的包名。 +``` + +命令示例: + +``` +列出所有可更新的软件清单命令: +--列出所有可更新的软件清单命令: +yum check-update + +--更新所有软件命令: +yum update + +--列出所有可安裝的软件清单命令: +yum list + +--安装指定的软件: +yum install mariadb-5.5.56-2.el7.x86_64 +``` diff --git "a/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\344\270\200\357\274\211\346\246\202\350\277\260.md" "b/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\344\270\200\357\274\211\346\246\202\350\277\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..f42bf42b9cb61dd5dfb0b7055b2a27084bf60383 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\344\270\200\357\274\211\346\246\202\350\277\260.md" @@ -0,0 +1,39 @@ +openGauss的安全能力涵盖了访问登录认证、用户权限管理、审计与追溯及数据安全隐私保护等。 + +## 整体架构 + +安全管理模块并非逻辑集中的,而是分散化的,在数据库整个业务逻辑的不同阶段提供对应的安全能力,从而构建数据库整体纵深安全防御能力 + +其整体架构如下: + +![](/api/attachments/397752) + +虽然整个安全机制是分散化的,但是每一个安全子模块都独立负责了一个完整的安全能力。如安全认证机制模块主要解决用户访问控制、登录通道安全问题;用户角色管理模块解决用户创建及用户权限管理问题。 + +## 安全认证 + +认证模块在业务流程上主要包括认证配置文件管理、用户身份识别、口令校验等过程,流程如下: + +![](/api/attachments/397753) + +## 角色管理 + +角色管理模块主要包括角色创建、修改、删除、授权和回收。设计的主要功能如下: + +![](/api/attachments/397754) + +## 对象访问控制 + +对象访问控制模块主要包括对象授权、对象权限回收以及实际对象操作时的对象权限检查,主要流程如下: + +![](/api/attachments/397755) + +## 审计 + +审计模块主要包括审计日志的创建和管理,审计追溯数据库的各类管理活动和业务活动。审计日志管理包括创建审计日志、审计日志轮转和清理。审计日志追溯包括活动发生时的日志记录以及审计信息查询接口。 + +![](/api/attachments/397756) + + + + diff --git "a/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\344\270\211\357\274\211\350\256\244\350\257\201\344\271\213\350\272\253\344\273\275.md" "b/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\344\270\211\357\274\211\350\256\244\350\257\201\344\271\213\350\272\253\344\273\275.md" new file mode 100644 index 0000000000000000000000000000000000000000..8f944033d41b87604f2645173e129f238420ea8d --- /dev/null +++ "b/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\344\270\211\357\274\211\350\256\244\350\257\201\344\271\213\350\272\253\344\273\275.md" @@ -0,0 +1,175 @@ + +## 身份认证 + +从访问用户,访问数据库的方式、从何处访问、访问哪个数据库来定义身份。 + +在配置文件HBA(host-based authentication file,主机认证)中记录openGauss的访问规则信息,每一行代表一个访问规则,格式如下: + +```c++ +hostssl DATABASE USER ADDRESS METHOD [OPTIONS] +``` + +字段的含义如下: +1. 套接字方法 +2. 允许被访问的数据库 +3. 允许被访问的用户 +4. 允许访问的IP地址 +5. 访问的认证方式 +6. 作为对第5个字段认证信息的补充 + +数据结构HbaLine定义了存储访问规则,核心代码如下所示 + +```c++ +typedef struct HbaLine +{ + int linenumber; /* 规则行号 */ + ConnType conntype; /* 连接套接字方法 */ + List* databases; /* 允许访问的数据库集合*/ + List* roles; /* 允许访问的用户组 */ +… + char* hostname; /* 允许访问的IP地址 */ + UserAuth auth_method; /* 认证方法 */ +… +} HbaLine; + + +``` + +HBA文件在数据库管理员配置完后存放在数据库服务端。 + +当某个用户发起认证请求时,连接相关的信息存放在数据结构Port中,如下: + +```c++ +typedef struct Port { +… +SockAddr laddr; /* 本地进程IP(internet protocol,互联网协议)地址信息 */ +SockAddr raddr; /* 远端客户端进程IP地址信息 */ +char* remote_host; /* 远端host(主机)名称字符串或IP地址*/ +char* remote_hostname; /* 可选项,远程host名称字符串或IP地址*/ + … + /* 发送给backend(后端)的数据包信息,包括访问的数据库名称、用户名、配置参数*/ +char* database_name; +char* user_name; +char* cmdline_options; +List* guc_options; + +/* 认证相关的配置信息*/ +HbaLine* hba; +… + /* SSL(secure sockets layer,安全套接层,工作于套接字层的安全协议。)认证信息*/ +#ifdef USE_SSL + SSL* ssl; + X509* peer; + char* peer_cn; + unsigned long count; +#endif +… + /* Kerberos认证数据结构信息*/ +#ifdef ENABLE_GSS + char* krbsrvname; /* Kerberos服务进程名称*/ + gss_ctx_id_t gss_ctx; /* GSS(generic security service,通用安全服务)数据内容*/ + gss_cred_id_t gss_cred; /* 凭证信息*/ + gss_name_t gss_name; + gss_buffer_desc gss_outbuf; /* GSS token信息*/ +#endif +} Port; + +``` + +上面的Port中的user_name、database_name、raddr以及对应的HBA等字段即为认证相关的用户信息、访问数据库信息以及IP地址信息。 + +Port中还包含了SSL认证相关的信息以及节点间做Kerberos认证相关的信息。有了Port信息,后台服务会根据传入的信息与HbaLine中记录的信息逐一比较,完成对应的身份识别。 + +check_hba函数完成完整的身份认证过程,其核心逻辑代码如下所示: + +```c++ +/**扫描HBA文件,寻找匹配连接请求的规则项 */ +static void check_hba(hbaPort* port) +{ + …… + /* 获取当前连接用户的id */ + roleid = get_role_oid(port->user_name, true); + + foreach (line, t_thrd.libpq_cxt.parsed_hba_lines) { + hba = (HbaLine*)lfirst(line); + /* 认证连接行为分为本地连接行为和远程连接行为,需分开考虑 */ + if (hba->conntype == ctLocal) { + /* 对于local套接字,仅允许初始安装用户本地登录 */ + if (roleid == INITIAL_USER_ID) { + char sys_user[SYS_USERNAME_MAX + 1]; +…… + /* 基于本地环境的uid(user identity,用户身份标识)信息获取当前系统用户名 */ + (void)getpwuid_r(uid, &pwtmp, pwbuf, pwbufsz, &pw); + …… + + /* 记录当前系统用户名 */ + securec_check(strncpy_s(sys_user,SYS_USERNAME_MAX+1, pw->pw_name, SYS_USERNAME_MAX), "\0", "\0"); + +/* 对于访问用户与本地系统用户不相匹配的场景,均需提供密码 */ + if (strcmp(port->user_name, sys_user) != 0) + hba->auth_method = uaSHA256; + } else if (hba->auth_method == uaTrust) { + hba->auth_method = uaSHA256; + } +…… + } else { + /* 访问行为是远端访问行为,需要逐条判断包括认证方式在内的信息正确性 */ + if (IS_AF_UNIX(port->raddr.addr.ss_family)) + continue; + /* SSL连接请求套接字判断 */ +#ifdef USE_SSL +if (port->ssl != NULL) { + if (hba->conntype == ctHostNoSSL) + continue; + } else { + if (hba->conntype == ctHostSSL) + continue; + } +#else + if (hba->conntype == ctHostSSL) + continue; +#endif + /* IP白名单校验 */ + switch (hba->ip_cmp_method) { + case ipCmpMask: + if (hba->hostname != NULL) { + if (!check_hostname(port, hba->hostname)) + continue; + } else { + if (!check_ip(&port->raddr, (struct sockaddr*)&hba->addr, (struct sockaddr*)&hba->mask)) + continue; + } + break; + case ipCmpAll: + break; + case ipCmpSameHost: + case ipCmpSameNet: + if (!check_same_host_or_net(&port->raddr, hba->ip_cmp_method)) + continue; + break; + default: + /* shouldn't get here, but deem it no-match if so */ + continue; + } + } /* != ctLocal */ + + /* 校验数据库信息和用户信息 */ + if (!check_db(port->database_name, port->user_name, roleid, hba->databases)) + continue; + if (!check_role(port->user_name, roleid, hba->roles)) + continue; + …… + port->hba = hba; + return; + } + + /* 没有匹配则拒绝当前连接请求 */ + hba = (HbaLine*)palloc0(sizeof(HbaLine)); + hba->auth_method = uaImplicitReject; + port->hba = hba; +} + + +``` + + diff --git "a/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\350\256\244\350\257\201\346\246\202\350\277\260.md" "b/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\350\256\244\350\257\201\346\246\202\350\277\260.md" new file mode 100644 index 0000000000000000000000000000000000000000..9a83ff846c0618e22a142f3c66754d519595ab8f --- /dev/null +++ "b/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\344\272\214\357\274\211\350\256\244\350\257\201\346\246\202\350\277\260.md" @@ -0,0 +1,22 @@ + +## 概述 + +认证是访问数据库的第一步,访问数据库的人员只有完成身份识别、通过认证机制的检查,才可以访问数据库,进行数据库的管理相关的工作。在整个认证过程中,涉及用户身份管理识别、用户口令存储以及认证机制3大模块。对于系统内部的进程间通信,则是调用业界通用的Kerberos认证机制。 + +安全认证机制解决的核心问题是谁可以访问数据库。 + + + + + + + + + + + + + + + + diff --git "a/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\345\233\233\357\274\211\350\256\244\350\257\201\344\271\213\345\217\243\344\273\244\345\222\214\350\256\244\350\257\201\346\234\272\345\210\266.md" "b/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\345\233\233\357\274\211\350\256\244\350\257\201\344\271\213\345\217\243\344\273\244\345\222\214\350\256\244\350\257\201\346\234\272\345\210\266.md" new file mode 100644 index 0000000000000000000000000000000000000000..810325f8654eb7ad143df7d1fabdf0df57408c63 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\256\211\345\205\250\347\256\241\347\220\206\347\263\273\345\210\227\357\274\210\345\233\233\357\274\211\350\256\244\350\257\201\344\271\213\345\217\243\344\273\244\345\222\214\350\256\244\350\257\201\346\234\272\345\210\266.md" @@ -0,0 +1,68 @@ + +## 口令 + +口令是认证过程中的重要凭证。 + +在执行创建用户或修改用户口令操作时,数据库会将口令通过单向哈希方式加密后存储在pg_authid系统表中。口令加密的方式与参数“password_encryption_type”的配置有关,有MD5、SHA256 + MD5(同时存储SHA256和MD5哈希值)和SHA256三种加密方式,默认采用SHA256方式加密。 + +修改`pg_hba.conf`配置文件中的认证方式即可选择不同的加密方式 + +![](/api/attachments/397757) + +CreateRole函数为创建用户、AlterRole函数为修改用户属性。在函数内对口令加密前会先校验是否满足口令复杂度,如果满足则调用calculate_encrypted_password函数实现口令的加密。加密时根据password_encryption_type参数配置选择对应的加密方式,加密完成后会清理内存中的敏感信息并返回口令密文。 + +加密流程如下: + +![](/api/attachments/397758) + +sha256加密由calculate_encrypted_sha256_password函数完成、md5加密由pg_md5_encrypt完成、calculate_encrypted_combined_password函数综合了两者。 + +calculate_encrypted_sha256_password函数执行流程如下: + +![](/api/attachments/397759) + +## 认证机制 + +认证识别是认证过程的最后一步,通过用户名和密码来验证数据库用户的身份,判断其是否为合法用户。 + +openGauss使用包含服务器和客户端双向认证的用户认证机制。 + +### 客户端 + +在客户端输入用户名username和密码password,客户端发送用户名username给服务端,服务端检索其的认证信息,然后服务端发送相关必要信息给客户端。接下来客户端需要进行相关计算,给服务端发送ClientProof认证信息,服务端通过ClientProof对客户端进行认证,并发送ServerSignature给客户端。最后客户端通过ServerSignature对服务端进行认证。 + +具体密钥计算代码如下: + +```c++ +SaltedPassword := Hi(password, salt, iteration_count) /*其中,Hi()本质上是PBKDF2*/ +ClientKey := HMAC(SaltedPassword, "Client Key") +StoredKey := sha256(ClientKey) +ServerKey := HMAC(SaltedPassword, "Sever Key") +ClientSignature:=HMAC(StoredKey, token) +ServerSignature:= HMAC(ServerKey, token) +ClientProof:= ClientSignature XOR ClientKey + +``` + +密钥的生成过程如下: + +![](/api/attachments/397760) + +### 服务端 + +服务器端存储StoredKey和ServerKey,它们的作用如下: + +#### StoredKey用来验证客户端用户身份。 + +服务端认证客户端通过计算ClientSignature与客户端发来的ClientProof进行异或运算,从而恢复得到ClientKey,然后将其进行HMAC(hash-based message authentication code,散列信息认证码)运算,将得到的值与StoredKey进行对比,如果相等,证明客户端验证通过。其中ClientSignature通过StoredKey和token(随机数)进行HMAC计算得到。 + +#### ServerKey用来向客户端表明自己身份的。 + +客户端认证服务端,通过计算ServerSignature与服务端发来的值进行比较,如果相等,则完成对服务端的认证。其中ServerSignature通过ServerKey和token(随机数)进行HMAC计算得到。 + +* 注:在认证过程中,服务端可以计算出来ClientKey,验证完后直接丢弃不必存储。 + + + + + diff --git "a/TestTasks/wang-luyang/\345\256\241\350\256\241\346\227\245\345\277\227\350\256\276\350\256\241\350\257\246\350\247\243.md" "b/TestTasks/wang-luyang/\345\256\241\350\256\241\346\227\245\345\277\227\350\256\276\350\256\241\350\257\246\350\247\243.md" new file mode 100644 index 0000000000000000000000000000000000000000..a3cf3c985bb814d41564d1e68ba5d09d44d7d632 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\256\241\350\256\241\346\227\245\345\277\227\350\256\276\350\256\241\350\257\246\350\247\243.md" @@ -0,0 +1,99 @@ + +## 保存方式 + +将审计结果记录到OS文件中(即审计日志)的方式,审计日志文件夹受操作系统权限保护,默认只有超级用户可以读写,从数据库安全角度出发,保证了审计结果的可靠性。日志文件的存储目录由audit_directory参数指定。 + +## 记录的字段 + +* time +* type +* result +* userid +* username +* database +* client_conninfo +* object_name +* detail_info +* node_name +* thread_id +* local_port +* remote_port + +## 文件结构 + +日志主要包括两类文件: + +* 形如0_adt的审计文件 +* 名为index_table索引文件 + +![](/api/attachments/398070) + +## 数据结构 + +### adt结尾的文件 + +一个AuditData结构体表示一条审计记录,如下 + +```cpp +typedef struct AuditData { + AuditMsgHdr header; /* 记录文件头,存储记录的标识、大小等信息 */ + AuditType type; /* 审计类型 */ + AuditResult result; /* 执行结果 */ + char varstr[1]; /* 二进制格式存储的具体审计信息 */ +} AuditData; +``` + +#### 字段详解 + +##### AuditMsgHdr + +AuditMsgHdr记录着审计记录的标识信息,如下 + +```cpp +typedef struct AuditMsgHdr { + char signature[2]; /* 审计记录标识,目前固定为AUDIT前两个字符’A’和’U’ */ + uint16 version; /* 版本信息,目前固定为0 */ + uint16 fields; /* 审计记录字段数,目前为13 */ + uint16 flags; /* 记录有效性标识,如果被删除则标记为DEAD */ + pg_time_t time; /* 审计记录创建时间 */ + uint32 size; /* 审计信息占字节长度 */ +} AuditMsgHdr; +``` + +##### AuditType + +审计类型 + +##### AuditResult + +执行结果,有AUDIT_UNKNOWN、AUDIT_OK、AUDIT_FAILED三种结果 + +### 索引文件index_table + +#### 作用 + +记录着审计文件的数量、审计日志文件编号、审计文件修改日期等信息 + +#### 数据结构 + +```cpp +typedef struct AuditIndexTable { + uint32 maxnum; /* 审计目录下审计文件个数的最大值 */ + uint32 begidx; /* 审计文件开始编号 */ + uint32 curidx; /* 当前使用的审计文件编号 */ + uint32 count; /* 当前审计文件的总数 */ + pg_time_t last_audit_time; /* 最后一次写入审计记录的时间 */ + AuditIndexItem data[1]; /* 审计文件指针 */ +} AuditIndexTable; + +``` + +索引文件中每一个AuditIndexItem对应一个审计文件,其结构如下: + +```cpp +typedef struct AuditIndexItem { + pg_time_t ctime; /* 审计文件创建时间 */ + uint32 filenum; /* 审计文件编号 */ + uint32 filesize; /* 审计文件占空间大小 */ +} AuditIndexItem; +``` diff --git "a/TestTasks/wang-luyang/\345\256\241\350\256\241\350\256\260\345\275\225\347\232\204\346\223\215\344\275\234\346\216\245\345\217\243.md" "b/TestTasks/wang-luyang/\345\256\241\350\256\241\350\256\260\345\275\225\347\232\204\346\223\215\344\275\234\346\216\245\345\217\243.md" new file mode 100644 index 0000000000000000000000000000000000000000..93675b63743d4e7c01b3ae2f81512cf0619f78f6 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\256\241\350\256\241\350\256\260\345\275\225\347\232\204\346\223\215\344\275\234\346\216\245\345\217\243.md" @@ -0,0 +1,34 @@ + +## 写入接口 + +### 函数原型 + +```cpp +void audit_report(AuditType type, AuditResult result, const char* object_name, const char* detail_info); +``` + +### 执行详解 + +1. 检查审计的各项开关,判断是否需要审计该操作 +2. 根据传入的参数、全局变量中的参数以及当前时间,生成审计日志所需的信息并拼接成字符串 +3. 调用审计日志文件读写接口,将审计日志写入文件中 + +## 查询接口 + +```cpp +SELECT * FROM pg_query_audit (timestamptz startime,timestamptz endtime, audit_log); +``` + +传入参数为需要查询审计记录的起始时间和终止时间以及审计日志文件所在的物理路径。当不指定audit_log时,默认查看连接当前实例的审计日志信息。 + +## 删除接口 + +```cpp +SELECT * FROM pg_delete_audit (timestamptz startime,timestamptz endtime); +``` + +传入参数为需要被删除审计记录的起始时间和终止时间。 + +注:该函数通过调用pgaudit_delete_file函数来将审计日志文件中startime与endtime之间的审计记录标记为AUDIT_TUPLE_DEAD,达到删除审计日志的效果,而不实际删除审计记录的物理数据。带来的效果是执行该函数审计日志文件大小不会减小。 + + diff --git "a/TestTasks/wang-luyang/\345\257\206\346\200\201\347\255\211\345\200\274\346\237\245\350\257\242.md" "b/TestTasks/wang-luyang/\345\257\206\346\200\201\347\255\211\345\200\274\346\237\245\350\257\242.md" new file mode 100644 index 0000000000000000000000000000000000000000..83b1bbc3aa1709e734155bf7c3e798e5603846c6 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\257\206\346\200\201\347\255\211\345\200\274\346\237\245\350\257\242.md" @@ -0,0 +1,371 @@ +## 简介 + +openGauss支持的数据全生命周期保护方案:全密态数据库机制。在这种机制下数据在客户端即被加密,从客户端传输到数据库内核,再到在内核中完成查询运算,到返回结果给客户端,数据始终处于加密状态,仅用户数据持有加解密所需的密钥。实现了数据拥有者和数据处理者的数据权属分离,有效规避第三方等威胁造成的数据泄漏风险。 + +## 密态等值查询的能力 + +* 数据加密:openGauss通过客户端驱动加密敏感数据,将密钥分为数据加密密钥和密钥加密密钥,客户端驱动保管密钥加密密钥,由此保证只有客户端才拥有解密数据密文的能力。 +* 数据检索:openGauss支持在用户无感知的情况下,为其提供对数据库密文进行等值检索的能力。在加密阶段,openGauss会将与加密相关的元数据存储在系统表中,当处理敏感数据时,客户端会自动检索加密相关元数据并对数据进行加解密。 +openGauss新增数据加解密表语法,通过采用驱动层过滤技术,在客户端的加密驱动中集成了SQL语法解析、密钥管理和敏感数据加解密等模块来处理相关语法。加密驱动源码流程如下。 + +![](/api/attachments/392177) + +用户执行SQL查询语句时,通过Pqexec函数执行SQL语句,SQL语句在发送之前首先进入run_pre_query函数,通过前端解析器解析涉及密态的语法。然后在run_pre_statement函数中通过分类器对语法标签进行识别,进入对应语法的处理逻辑。在不同的处理逻辑函数中,查找出要替换的数据参数,并存储在结构体StatementData中。最后通过replace_raw_values函数重构SQL语句,将其发送给服务端。在PqgetResult函数接收到从服务端返回的数据时,若是加密数据类型,则用deprocess_value函数对加密数据进行解密。接收完数据后还需要在run_post_query函数中刷新相应的缓存。 + +![](/api/attachments/392178) + +## 加密步骤 + +### 创建CMK客户端主密钥 + +```cpp +CREATE CLIENT MASTER KEY cmk_1 WITH (KEY_STORE = LOCALKMS , KEY_PATH = "kms_1" , ALGORITHM = RSA_2048); +``` + + +### 创建CEK列加密密钥 + +```cpp +CREATE COLUMN ENCRYPTION KEY cek_1 WITH VALUES (CLIENT_MASTER_KEY = cmk_1, ALGORITHM = AEAD_AES_256_CBC_HMAC_SHA256); +``` + +### 创建加密表 + +```cpp +CREATE TABLE creditcard_info (id_number int, name text encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), gender varchar(10) encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), salary float4 encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC),credit_card varchar(19) encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC)); +``` + +# 创建CMK客户端主密钥 + +## 创建语句 + +```cpp +CREATE CLIENT MASTER KEY cmk_1 WITH (KEY_STORE = LOCALKMS , KEY_PATH = "kms_1" , ALGORITHM = RSA_2048); +``` + +## 数据结构 + +```cpp +/* 保存创建客户端主密钥的语法信息 */ +typedef struct CreateClientLogicGlobal { + NodeTag type; + List *global_key_name; /* 全密态数据库主密钥名称 */ + List *global_setting_params; /* 全密态数据库主密钥参数,每一个元素是一个ClientLogicGlobalparam */ +} CreateClientLogicGlobal; + +/* 保存客户端主密钥参数信息 */ +typedef struct ClientLogicGlobalParam { + NodeTag type; + ClientLogicGlobalProperty key; /* 键 */ + char *value; /* 值 */ + unsigned int len; /* 值长度 */ + int location; /* 位置标记 */ +} ClientLogicGlobalParam; + +/* 保存客户端主密钥参数的key的枚举类型 */ +typedef enum class ClientLogicGlobalProperty { + CLIENT_GLOBAL_FUNCTION, /* 默认为encryption */ + CMK_KEY_STORE, /* 目前仅支持localkms */ + CMK_KEY_PATH, /* 密钥存储路径 */ + CMK_ALGORITHM /* 指定加密CEK的算法 */ +} ClientLogicGlobalProperty; +``` + +## 参数说明 + +* KEY_STORE:指定管理CMK的组件 +* KEY_PATH:唯一标识CMK。 +* ALGORITHM:指定加密CEK的算法,即指定CMK的密钥类型。 + +创建主密钥本质上是将CMK的元信息解析并保存在CreateClientLogicGlobal。其中global_key_name是密钥名称,global_setting_params是一个List,每个节点是ClientLogicGlobalParam结构。客户端先通过解析器`fe_raw_parser()`解析为CreateClientLogicGlobal结构体,对其参数进行校验并发送查询语句到服务端;服务端解析为CreateClientLogicGlobal结构体并检查用户namespace等权限,CMK元信息保存在系统表中。 + + +## 示例 + +```cpp +CREATE CLIENT MASTER KEY cmk_1 WITH (KEY_STORE = LOCALKMS , KEY_PATH = "kms_1" , ALGORITHM = RSA_2048); +``` + +## 流程图 + +![](/api/attachments/392179) + +# 创建CEK列加密密钥 + +## 创建前提 + +先创建好主密钥CMK + +## 创建语句 + +```cpp +CREATE COLUMN ENCRYPTION KEY cek_1 WITH VALUES (CLIENT_MASTER_KEY = cmk_1, ALGORITHM = AEAD_AES_256_CBC_HMAC_SHA256); +``` + +## 数据结构 + +```cpp +/* 保存创建列加密密钥的语法信息 */ +typedef struct CreateClientLogicColumn { + NodeTag type; + List *column_key_name; /* 列加密密钥名称 */ + List *column_setting_params; /* 列加密密钥参数 */ +} CreateClientLogicColumn; + +/* 保存列加密密钥参数,保存在CreateClientLogicColumn的column_setting_params中 */ +typedef struct ClientLogicColumnParam { + NodeTag type; + ClientLogicColumnProperty key; + char *value; + unsigned int len; + List *qualname; + int location; +} ClientLogicColumnParam; + +/* 保存列加密密钥参数的key的枚举类型 */ +typedef enum class ClientLogicColumnProperty { + CLIENT_GLOBAL_SETTING, /* 加密CEK的CMK */ + CEK_ALGORITHM, /* 加密用户数据的算法 */ + CEK_EXPECTED_VALUE, /* CEK密钥明文,可选参数 */ + COLUMN_COLUMN_FUNCTION, /* 默认为encryption */ +} ClientLogicColumnProperty; +``` + +## 参数说明 + +* `CLIENT_MASTER_KEY`:用于加密CEK的CMK对象。 +* `ALGORITHM`:指定加密用户数据的算法,即指定CEK的密钥类型。 +* `ENCRYPTED_VALUE`:列加密密钥的明文,默认随机生成。也可由用户指定,用户指定时密钥长度范围为28~256位。 + +## 创建示例 + +```cpp +CREATE COLUMN ENCRYPTION KEY cek_1 WITH VALUES (CLIENT_MASTER_KEY = cmk_1, ALGORITHM = AEAD_AES_256_CBC_HMAC_SHA256); +``` + +# 创建加密表 + +## 创建语句 + +```cpp +CREATE TABLE creditcard_info (id_number int, +name text encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), gender varchar(10) encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), +salary float4 encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC), +credit_card varchar(19) encrypted with (column_encryption_key = cek_1, encryption_type = DETERMINISTIC)); +``` + +## 处理逻辑 + +先对sql语句进行语句解析,后使用`CreateStmt`函数处理逻辑,在`run_pre_create_statement`函数中,对`CreateStmt->tableElts`中每个`ListCell`进行判断,加密表列定义及约束处理函数段代码如下: + +```cpp +bool createStmtProcessor::run_pre_create_statement(const CreateStmt * const stmt, StatementData *statement_data) +{ + … + /* 加密表列定义及约束处理 */ + foreach (elements, stmt->tableElts) { + Node *element = (Node *)lfirst(elements); + switch (nodeTag(element)) { + case T_ColumnDef: { + … + /* 校验distribute by是否符合规格 */ + if (column->colname != NULL && + !check_distributeby(stmt->distributeby, column->colname)) { + return false; + } + /* 列定义处理,存储加密类型,加密密钥等信息 */ + if (!process_column_defintion(column, element, &expr_vec, &cached_columns, + &cached_columns_for_defaults, statement_data)) { + return false; + } + break; + } + /* 处理check, unique 或其他约束 */ + case T_Constraint: { + Constraint *constraint = (Constraint*)element; + if (constraint->keys != NULL) { + ListCell *ixcell = NULL; + foreach (ixcell, constraint->keys) { + char *ikname = strVal(lfirst(ixcell)); + for (size_t i = 0; i < cached_columns.size(); i++) { + if (strcmp((cached_columns.at(i))->get_col_name(), ikname) == 0 && !check_constraint( + constraint, cached_columns.at(i)->get_data_type(), ikname, &cached_columns)) { + return false; + } + } + } + } else if (constraint->raw_expr != NULL) { + if (!transform_expr(constraint->raw_expr, "", &cached_columns)) { + return false; + } + } + break; + } + default: + break; + } + } + … + /* 加密约束中需要加密的明文数据 */ + if (!RawValues::get_raw_values_from_consts_vec(&expr_vec, statement_data, 0, &raw_values_list)) { + return false; + } + return ValuesProcessor::process_values(statement_data, &cached_columns_for_defaults, 1, + &raw_values_list); +} +``` + +在数据发送到数据库之前,数据加密驱动程序进行数据加密,数据在整个语句的处理过程中以密文形式存在,在返回结果时,解密返回的数据集。 + +定义了完整的加密表后,用户就可以用正常的方式将数据插入到表中。 + +`encrypt_data`函数用于加密,其核心逻辑代码如下所示: + +```cpp +int encrypt_data(const unsigned char *plain_text, int plain_text_length, const AeadAesHamcEncKey &column_encryption_key, + EncryptionType encryption_type, unsigned char *result, ColumnEncryptionAlgorithm column_encryption_algorithm) +{ + …… + /* 得到16位的iv值 */ + unsigned char _iv [g_key_size + 1] = {0}; +unsigned char iv_truncated[g_iv_size + 1] = {0}; +/* 确定性加密,则通过hmac_sha256生成iv */ + if (encryption_type == EncryptionType::DETERMINISTIC_TYPE) { + hmac_sha256(column_encryption_key.get_iv_key(), g_auth_tag_size, plain_text, plain_text_length, _iv); + …… +} else { +/* 随机加密,则随机生成iv */ + if (encryption_type != EncryptionType::RANDOMIZED_TYPE) { + return 0; + } + int res = RAND_priv_bytes(iv_truncated, g_block_size); + if (res != 1) { + return 0; + } + } + int cipherStart = g_algo_version_size + g_auth_tag_size + g_iv_size; + /* 调用encrypt计算密文 */ +int cipherTextSize = encrypt(plain_text, plain_text_length, column_encryption_key.get_encyption_key(), iv_truncated, + result + cipherStart, column_encryption_algorithm); + …… + int ivStartIndex = g_auth_tag_size + g_algo_version_size; + res = memcpy_s(result + ivStartIndex, g_iv_size, iv_truncated, g_iv_size); + securec_check_c(res, "\0", "\0"); + /* 计算 HMAC */ + int hmacDataSize = g_algo_version_size + g_iv_size + cipherTextSize; + hmac_sha256(column_encryption_key.get_mac_key(), g_auth_tag_size, + result + g_auth_tag_size, hmacDataSize, result); + return (g_auth_tag_size + hmacDataSize); +} +``` + +# 查询 + +## 简要介绍 + +### 特点 + +* 对用户无感知 +* 展示数据时将密文数据进行解密 + +### 过程 + +客户端解析`SELECT`查询语句中的列属性信息,如果在缓存中已存在则直接从缓存中提取列属性信息;如果缓存中尚不存在,需要先从服务端查询该信息,并缓存。列加密密钥`CEK`是以密文形式存储在服务端,客户端需要先解密`CEK`,然后用其加密`SELECT`查询语句中的条件参数。加密后的SELECT查询语句发送给数据库服务端执行完成后,返回加密的查询结果集。客户端用解密后的列加密密钥`CEK`解密`SELECT`查询结果集,并返回解密后的明文结果集给应用端。 + +## 相关代码 + +`run_pre_insert_statement`函数用于等值查询处理,其核心逻辑代码如下所示: + +```cpp +bool Processor::run_pre_select_statement(const SelectStmt * const select_stmt, const SetOperation &parent_set_operation, + const bool &parent_all, StatementData *statement_data, ICachedColumns *cached_columns, ICachedColumns *cached_columns_parents) +{ +bool select_res = false; +/* 处理SELECT语句中的集合操作 */ + if (select_stmt->op != SETOP_NONE) { + select_res = process_select_set_operation(select_stmt, statement_data, cached_columns); + RETURN_IF(!select_res); + } + /* 处理WHERE子句 */ + ExprPartsList where_expr_parts_list; + select_res = exprProcessor::expand_expr(select_stmt->whereClause, statement_data, &where_expr_parts_list); + RETURN_IF(!select_res); +…… + /* 从FROM子句中获取缓存加密列 */ +CachedColumns cached_columns_from(false, true); + select_res = run_pre_from_list_statement(select_stmt->fromClause, statement_data, &cached_columns_from, + cached_columns_parents); +…… +/* 将查询的加密列放在cached_columns结构中 */ + for (size_t i = 0; i < cached_columns_from.size(); i++) { + if (find_in_name_map(target_list, cached_columns_from.at(i)->get_col_name())) { + CachedColumn *target = new (std::nothrow) CachedColumn(cached_columns_from.at(i)); + if (target == NULL) { + fprintf(stderr, "failed to new CachedColumn object\n"); + return false; + } + cached_columns->push(target); + } + } + if (cached_columns_from.is_empty()) { + return true; + } + /* 加密列不支持ORDER BY(排序)操作 */ + if (!deal_order_by_statement(select_stmt, cached_columns)) { + return false; + } + +/* 将WHERE子句中加密的值进行加密处理 */ + if (!WhereClauseProcessor::process(&cached_columns_from, &where_expr_parts_list, statement_data)) { + return false; +} +…… + return true; +} +``` + +客户端密文解密函数代码如下 + +```cpp +int decrypt_data(const unsigned char *cipher_text, int cipher_text_length, + const AeadAesHamcEncKey &column_encryption_key, unsigned char *decryptedtext, + ColumnEncryptionAlgorithm column_encryption_algorithm) +{ + if (cipher_text == NULL || cipher_text_length <= 0 || decryptedtext == NULL) { + return 0; + } + /* 校验密文长度 */ + if (cipher_text_length < min_ciph_len_in_bytes_with_authen_tag) { + printf("ERROR(CLIENT): The length of cipher_text is invalid, cannot decrypt.\n"); + return 0; + } + /* 校验密文中的版本号 */ + if (cipher_text[g_auth_tag_size] != '1') { + printf("ERROR(CLIENT): Version byte of cipher_text is invalid, cannot decrypt.\n"); + return 0; + } + …… + /* 计算MAC标签 */ + unsigned char authenticationTag [g_auth_tag_size] = {0}; + int HMAC_length = cipher_text_length - g_auth_tag_size; + int res = hmac_sha256(column_encryption_key.get_mac_key(), g_auth_tag_size, + cipher_text + g_auth_tag_size, HMAC_length, authenticationTag); + if (res != 1) { + printf("ERROR(CLIENT): Fail to compute a keyed hash of a given text.\n"); + return 0; + } + /* 校验密文是否被篡改 */ + int cmp_result = my_memcmp(authenticationTag, cipher_text, g_auth_tag_size); + /* 解密数据 */ + int decryptedtext_len = decrypt(cipher_text + cipher_start_index, cipher_value_length, + column_encryption_key.get_encyption_key(), iv, decryptedtext, column_encryption_algorithm); + if (decryptedtext_len < 0) { + return 0; + } + decryptedtext[decryptedtext_len] = '\0'; + return decryptedtext_len; +} +``` + + + diff --git "a/TestTasks/wang-luyang/\345\257\271\350\261\241\346\235\203\351\231\220\346\243\200\346\237\245.md" "b/TestTasks/wang-luyang/\345\257\271\350\261\241\346\235\203\351\231\220\346\243\200\346\237\245.md" new file mode 100644 index 0000000000000000000000000000000000000000..1514537b1d2900432b1a21bd88c35cb6e25a487e --- /dev/null +++ "b/TestTasks/wang-luyang/\345\257\271\350\261\241\346\235\203\351\231\220\346\243\200\346\237\245.md" @@ -0,0 +1,35 @@ + +## 简介 + +用户在对数据库对象进行访问操作时,数据库会检查用户是否拥有该对象的操作权限。 + +通常数据库对象的所有者和超级用户(superuser)拥有该对象的全部操作权限,其他普通用户需要被授予权限才可以执行相应操作。 + +数据库通过查询数据库对象的访问控制列表检查用户对数据库对象的访问权限,数据库对象的ACL保存在对应的系统表中,当被授予或回收对象权限时,系统表中保存的ACL权限位会被更新。 + +常用的数据库对象权限检查函数、ACL检查函数、ACL所在系统表以及对象所有者检查函数对应关系如下: + +![](/api/attachments/397901) + +## 表权限检查函数pg_class_aclcheck + +### 定义 + +```cpp +AclResult pg_class_aclcheck(Oid table_oid, Oid roleid, AclMode mode, bool check_nodegroup) +{ + if (pg_class_aclmask(table_oid, roleid, mode, ACLMASK_ANY, check_nodegroup) != 0) + return ACLCHECK_OK; + else + return ACLCHECK_NO_PRIV; +} +``` + +### 解析 + +pg_class_aclcheck函数有4个入参,其中table_oid用于表示待检查的表,roleid用于表示待检查的用户或角色,mode表示待检查的权限,此权限可以是一种权限也可以是多种权限的组合。第4个参数check_nodegroup用于表示是否检查nodegroup逻辑集群权限,如果调用时不给此参数赋值则默认为true。函数返回值为枚举类型AclResult,如果检查结果有权限返回ACLCHECK_OK,无权限则返回ACLCHECK_NO_PRIV。 + +pg_class_aclcheck函数通过调用pg_class_aclmask函数实现对象权限检查。pg_class_aclmask函数有5个参数,其中第4个参数how为AclMaskHow枚举类型,包括ACLMASK_ALL和ACLMASK_ANY两种取值;ACLMASK_ALL表示需要满足待检查权限mode中的所有权限,ACLMASK_ANY表示只需满足待检查权限mode中的一种权限即可。pg_class_aclmask函数的其余4个参数table_oid、roleid、mode和check_nodegroup,直接由pg_class_aclcheck函数传入。pg_class_aclmask函数从pg_class系统表中获取ACL权限信息并调用aclmask函数完成权限位校验,通过AclMode数据类型返回权限检查结果。 + + + diff --git "a/TestTasks/wang-luyang/\345\257\271\350\261\241\346\235\203\351\231\220\347\256\241\347\220\206.md" "b/TestTasks/wang-luyang/\345\257\271\350\261\241\346\235\203\351\231\220\347\256\241\347\220\206.md" new file mode 100644 index 0000000000000000000000000000000000000000..5d88ad1cd45b85af6ee7459b3d35f7ebda6e26f4 --- /dev/null +++ "b/TestTasks/wang-luyang/\345\257\271\350\261\241\346\235\203\351\231\220\347\256\241\347\220\206.md" @@ -0,0 +1,69 @@ + +## SQL命令 + +使用`GRANT/REVOKE`来授予或回收一个或多个角色在对象上的权限。 + + +## 底层原理 + +`GRANT/REVOKE`命令都由函数ExecuteGrantStmt实现,该函数接受一个GrantStmt类型的参数,基本执行流程如下: + +![](/api/attachments/397898) + +## 相关数据结构和函数执行流程 + +GrantStmt定义如下: + +```cpp +typedef struct GrantStmt { + NodeTag type; + bool is_grant; /* true = 授权, false = 回收 */ + GrantTargetType targtype; /* 操作目标的类型 */ + GrantObjectType objtype; /* 被操作对象的类型:表、数据库、模式、函数等 */ + List* objects; /* 被操作对象的集合 */ + List* privileges; /* 要操作权限列表 */ + List* grantees; /* 被授权者的集合 */ + bool grant_option; /* true = 再授予权限 */ + DropBehavior behavior; /* 回收权限的行为 */ +} GrantStmt; + +``` + +函数ExecuteGrantStmt首先将GrantStmt结构转换为InternalGrant结构,并将权限列表转换为内部的AclMode表示形式。 + +当privileges 取值为NIL时,表示授予或回收所有的权限,此时置InternalGrant的all_privs字段为true,privileges字段为ACL_NO_RIGHTS。 + + +InternalGrant的结构如下: + +```cpp +typedef struct InternalGrant { + bool is_grant; /* true=授权, false=回收 */ + GrantObjectType objtype; /* 被操作对象的类型:表、数据库、模式、函数等 */ + List* objects; /* 被操作对象的集合 */ + bool all_privs; /* 是否授予或回收所有的权限 */ +AclMode privileges; /* AclMode形式表示的DML类操作对应的权限 */ +AclMode ddl_privileges; /* AclMode形式表示的DDL类操作对应的权限 */ +List* col_privs; /* 对列执行的DML类操作对应的权限 */ +List* col_ddl_privs; /* 对列执行的DDL类操作对应的权限 */ + List* grantees; /* 被授权者的集合 */ + bool grant_option; /* true=再授予权限 */ + DropBehavior behavior; /* 回收权限的行为 */ +} InternalGrant; + +``` + +函数ExecuteGrantStmt在完成结构转换之后,调用函数ExecGrantStmt_oids,根据对象类型分别调用相应对象的权限管理函数。 + +接下来以表对象的权限管理过程为例介绍权限管理的算法。 + +函数ExecGrant_Relation用来处理表对象权限的授予或回收操作,入参为InternalGrant类型的变量,存储着授权或回收操作的操作对象信息、被授权者信息和权限信息。 + +函数ExecGrant_Relation的处理流程如下。 + +![](/api/attachments/397899) + +1. 从系统表pg_class中获取旧ACL。如果不存在旧的ACL,则新建一个ACL,并调用函数acldefault将默认的权限信息赋给该ACL。根据对象的不同,初始的缺省权限含有部分可赋予PUBLIC的权限。如果存在旧的ACL,则将旧的ACL存储为一个副本。 +2. 调用select_best_grantor函数来获取授权者对操作对象所拥有的授权权限avail_goptions;将参数avail_goptions传入函数restrict_and_check_grant,结合SQL命令中给出的操作权限,计算出实际需要授予或回收的权限。 +3. 调用merge_acl_with_grant函数生成新的ACL。如果是授予权限,则将要授予的权限添加到旧ACL中;如果是回收权限,则将要被回收的权限从旧ACL中删除。 +4. 将新的ACL更新到系统表pg_class对应元组的ACL字段,完成授权或回收过程。 diff --git "a/TestTasks/wang-luyang/\345\257\271\350\261\241\346\235\203\351\231\220\347\256\241\347\220\206\344\271\213\350\256\277\351\227\256\346\216\247\345\210\266\345\210\227\350\241\250.md" "b/TestTasks/wang-luyang/\345\257\271\350\261\241\346\235\203\351\231\220\347\256\241\347\220\206\344\271\213\350\256\277\351\227\256\346\216\247\345\210\266\345\210\227\350\241\250.md" new file mode 100644 index 0000000000000000000000000000000000000000..2010ad2dfeeaf7e2fa6b26a7e100ee6c177b6eea --- /dev/null +++ "b/TestTasks/wang-luyang/\345\257\271\350\261\241\346\235\203\351\231\220\347\256\241\347\220\206\344\271\213\350\256\277\351\227\256\346\216\247\345\210\266\345\210\227\350\241\250.md" @@ -0,0 +1,54 @@ + +## 前言 + +openGauss权限管理基于访问控制列表(access control list,ACL)实现。 + +## ACL + +### 介绍 + +访问控制列表是实现数据库对象权限管理的基础,每个对象都具有ACL,存储该对象的所有授权信息。当用户访问对象时,只有用户在对象的ACL中并且具有所需的权限才能够访问该对象。 + +### 数据结构 + +每个ACL是由1个或多个AclItem构成的链表,每1个AclItem由授权者、被授权者和权限位3部分构成,记录着可在对象上进行操作的用户及其权限。 + +AclItem的代码如下: + +```cpp +typedef struct AclItem { + Oid ai_grantee; /* 被授权者的OID */ + Oid ai_grantor; /* 授权者的OID */ + AclMode ai_privs; /* 权限位:32位的比特位 */ +} AclItem; +``` + +其中ai_privs字段是AclMode类型。 + +### AclMode + +AclMode是一个32位的比特位。 + +其高16位为权限选项位,当该比特位取值为1时,表示AclItem中的ai_grantee对应的用户具有此对象的相应操作的授权权限,否则表示用户没有授权权限;低16位为操作权限位,当该比特位取值为1时,表示AclItem中的ai_grantee对应的用户具有此对象的相应操作权限,否则表示用户没有相应的权限。 + +Grant Option记录各权限位的权限授予或被转授情况。低16位记录各权限的授予情况,当授权语句使用ALL时,则表示对象的所有权限。 + +AclMode结构图如下: + +![](/api/attachments/397893) + +openGauss将执行DML类操作和DDL类操作的权限分别记在两个AclMode结构中,并以第15位的值来区分2者,从而实现对于每一个数据库对象,相同的授权者和被授权者对应两个不同的AclMode,分别表示记录DML类操作权限和DDL类操作权限。 + + +openGauss记录DML类操作权限的AclMode结构如下: + +![](/api/attachments/397894) + +openGauss记录DDL类操作权限的AclMode结构如下: + +![](/api/attachments/397895) + + +每个权限参数代表的权限如下表: + +![](/api/attachments/397896) diff --git "a/TestTasks/wang-luyang/\345\270\270\350\247\204\351\224\201\346\255\273\351\224\201\346\243\200\346\265\213.md" "b/TestTasks/wang-luyang/\345\270\270\350\247\204\351\224\201\346\255\273\351\224\201\346\243\200\346\265\213.md" new file mode 100644 index 0000000000000000000000000000000000000000..d210ef551881b24305bc1e18085bcfda58f9e6cd --- /dev/null +++ "b/TestTasks/wang-luyang/\345\270\270\350\247\204\351\224\201\346\255\273\351\224\201\346\243\200\346\265\213.md" @@ -0,0 +1,100 @@ + +## 原理 + +在获取锁时如果没有冲突可以直接上锁;如果有冲突则设置一个定时器timer,并进入等待,过一段时间会被timer唤起进行死锁检测。 + +如果在某个锁的等锁队列中,进程T2排在进程T1后面,且进程T2需要获取的锁与T1需要获取的锁资源冲突,则T2到T1会有一条软等待边(soft edge)。如果进程T2的加锁请求与T1进程所持有的锁冲突,则有一条硬等待边(hard edge)。 + +整体思路就是通过递归调用,从当前顶点等锁的顶点出发,沿着等待边向前走,看是否存在环,如果环中有soft edge,说明环中两个进程都在等锁,重新排序,尝试解决死锁冲突。如果没有soft edge,那么只能终止当前等锁的事务,解决死锁等待环。 + +![](/api/attachments/398090) + +如上图所示,虚线代表soft edge,实线代表hard fdge。线程A等待线程B,线程B等待线程C,线程C等待线程A,因为线程A等待线程B的是soft edge,进行一次调整成为上图右边的等待关系,此时发现线程A等待线程C,线程C等待线程A,没有soft edge,检测到死锁。 + +## 主要函数 + +### DeadLockCheck + +死锁检测函数 + +### DeadLockCheckRecurse + +如果死锁则返回true,如果有soft edge,返回false并且尝试解决死锁冲突。 + +### check_stack_depth + +检查死锁递归检测堆栈(死锁检测递归栈过长,会引发在死锁检测时,长期持有所有锁的LWLock分区,从而阻塞业务) + + +### CheckDeadLockRunningTooLong + +检查死锁检测时间,防止死锁检测时间过长,阻塞后面所有业务 + +```cpp +static void CheckDeadLockRunningTooLong(int depth) +{ /* 每4层检测一下 */ + if (depth > 0 && ((depth % 4) == 0)) { + TimestampTz now = GetCurrentTimestamp(); + long secs = 0; + int usecs = 0; + + if (now > t_thrd.storage_cxt.deadlock_checker_start_time) { + TimestampDifference(t_thrd.storage_cxt.deadlock_checker_start_time, now, &secs, &usecs); + if (secs > 600) { /* 如果从死锁检测开始超过十分钟,则报错处理。 */ +#ifdef USE_ASSERT_CHECKING + DumpAllLocks();/* 在debug版本时,导出所有的锁信息,便于定位问题。 */ +#endif + + ereport(defence_errlevel(), (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Deadlock checker runs too long and is greater than 10 minutes."))); + } + } + } +} + +``` + +### FindLockCycle + +检查是否有死锁环 + +### FindLockCycleRecurse + +死锁检测内部递归调用函数 + +## 数据结构 + +### 有向边 + +```cpp +typedef struct EDGE { + PGPROC *waiter; /* 等待的线程 */ + PGPROC *blocker; /* 阻塞的线程 */ + int pred; /* 拓扑排序的工作区 */ + int link; /* 拓扑排序的工作区 */ +} EDGE; + +``` + +### 可重排的等待队列 + +```cpp +typedef struct WAIT_ORDER { + LOCK *lock; /* the lock whose wait queue is described */ + PGPROC **procs; /* array of PGPROC *'s in new wait order */ + int nProcs; +} WAIT_ORDER; +``` + +### 死锁检测最后打印的相应信息 + +```cpp +typedef struct DEADLOCK_INFO { + LOCKTAG locktag; /* 等待锁对象的唯一标识 */ + LOCKMODE lockmode; /* 等待锁对象的锁类型 */ + ThreadId pid; /* 阻塞线程的线程ID */ +} DEADLOCK_INFO; +``` + + + diff --git "a/TestTasks/wang-luyang/\346\205\242SQL\344\271\213\350\257\255\345\217\245\345\217\212\345\205\266\346\224\271\345\206\231.md" "b/TestTasks/wang-luyang/\346\205\242SQL\344\271\213\350\257\255\345\217\245\345\217\212\345\205\266\346\224\271\345\206\231.md" new file mode 100644 index 0000000000000000000000000000000000000000..23bf2d6878e0073195f5b45f506975baec432f8d --- /dev/null +++ "b/TestTasks/wang-luyang/\346\205\242SQL\344\271\213\350\257\255\345\217\245\345\217\212\345\205\266\346\224\271\345\206\231.md" @@ -0,0 +1,4 @@ + +下面还列出了几种比较常见的、可能优化openGauss数据库性能的SQL改写规则 + +![](/api/attachments/398275) diff --git "a/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\263\273\347\273\237\351\205\215\347\275\256.md" "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\263\273\347\273\237\351\205\215\347\275\256.md" new file mode 100644 index 0000000000000000000000000000000000000000..6a6deca6a6bfb81845129d943631719bde6a6a76 --- /dev/null +++ "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\263\273\347\273\237\351\205\215\347\275\256.md" @@ -0,0 +1,34 @@ + +## 前言 + +在系统配置中,最常见的配置项就是对资源的配置。这包括允许使用的最大资源(主要是内存)、以及资源的使用方式等。除了调整资源配置,有些情况下还需要配置数据库优化器Cost Model的代价值。 + +下面是几个会影响SQL语句成为慢SQL的系统参数: + +### max_process_memory + +该参数与enable_memory_limit配合使用,用于限制一个openGauss实例可用的最大内存。需要将该参数值与宿主机系统的内存总量进行匹配,将宿主机用于操作系统正常运行所需的内存刨除后,剩下的内存空间就可以尽可能多地划分给openGauss实例使用了。否则,openGauss为了避免造成OOM问题,会通过该参数限制数据库允许使用的最大内存。因此,如果在客户端或者日志中出现类似“memory usage reach the max_dynamic_memory”的报错时,一般是由于该参数值太小导致的。 + +### shared_buffers + +数据库系统使用的缓存池大小。一般来说,综合来看对数据库影响最大的参数就是它了,因为如果该参数设置得过小,会导致缓存不足,从而产生大量的磁盘I/O. 该参数在openGauss上的默认值很小,只有32MB,对于绝大多数的生产场景是不够的。一般的经验值是设置为系统内存的25%, 甚至在某些场景中还可以再大一点。不过openGauss的buffer没有通过DirectIO实现,仍然使用了系统缓存(cache),所以一般认为超过系统内存的40%也起不到再好的效果了。与此同时,checkpoint_segments 参数也需要随着shared_buffers的调大跟着变大一些。 + + +### work_mem + +显式指定内排序和哈希表能使用的内存空间大小,如果该值设得比较小,会向磁盘写入更多的临时文件。因此,我们可以适当地增加该值的大小。但是需要注意的是,业务系统可能存在并行执行的复杂语句,如果这些语句都占用非常多的work_mem大小的资源,则可能会导致内存使用占满(openGauss存在内存管控机制,一般不至于由于OOM导致系统重启)。故而,该值设置得很大的时候要关注系统的并发问题。该参数对ORDER BY, DISTINCT, JOIN (merge join, hash join), HASH Agg, 基于hash的IN子查询都有影响。 + +### enable_nestloop + +开启该参数可以让优化器使用Nest Loop Join(NLJ), 但是关闭该参数也不会完全压制优化器选择NLJ. 对于某些复杂查询(如在TPC-H benchmark中的语句)来说,不应该选择NLJ, 但是优化器往往会出现规划错误。那么,在此场景下,可以通过禁用该参数来鼓励优化器选择使用其他JOIN方法。 + +### random_page_cost + +一般与seq_page_cost配合调整。该参数调整数据库的CBO优化器中随机扫描的代价。该值设置得越大,数据库越认为随机扫描不可取,也就越不倾向于使用索引。该参数的默认值是4,对于机械硬盘来说,是合适的。但是,如果业务系统的磁盘是固态硬盘的话,就应该适当调小一下该参数值,一般的经验是调整为1. + +### default_statistics_target + +当前openGauss的默认优化器是CBO, 它高度依赖数据的统计信息。因此,对于复杂查询来说,更优质的统计信息往往可以获得更好的执行计划。通过增大该参数的值,可以获得更准确的统计信息,但是也会增加ANALYZE的时间。因此,对于复杂语句较多的场景,可以适当增加该参数值。 + + + diff --git "a/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\264\242\345\274\225\357\274\210\344\270\212\357\274\211.md" "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\264\242\345\274\225\357\274\210\344\270\212\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..6f8e2d5d6a286596bcd243de8c47e18a6c97d81d --- /dev/null +++ "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\264\242\345\274\225\357\274\210\344\270\212\357\274\211.md" @@ -0,0 +1,73 @@ + +## 前言 + +由于不同的数据库系统架构存在差异、代码实现也有一定的不同,很多慢SQL解决方案不是通用的。 + +## 监控慢SQL + +先通过设置GUC参数log_min_duration_statement 来指定openGauss系统监控的慢SQL阈值。同时,调大instr_unique_sql_count的数值,以免出现“missing SQL statement, GUC instr_unique_sql_count is too small.”的提示。这里以设置慢SQL检测阈值为5秒(默认数值单位是毫秒)为例: + +```cpp +gs_guc reload -D $PGDATA -c 'log_min_duration_statement = 5000' -c 'instr_unique_sql_count = 2000' + +``` + +然后执行一个慢SQL,可以在dbe_perf.statement_history视图中查看到结果: + +```cpp +select pg_sleep(6); -- 构造的慢SQL +select * from dbe_perf.statement_history order by start_time desc; +``` + +## 由索引导致的慢SQL + +存在一下三种情况: + +* 缺乏有效索引 + +* 执行计划没有选择索引扫描,即索引失效 + +* 冗余索引 + +### 缺乏有效索引 + +#### 原因 + +绝大多数此类SQL语句都是SELECT语句,且该类SQL语句涉及到的表数据量较多,且谓词上没有创建索引,导致数据库系统需要通过全盘扫描来获取数据。 + +#### 解决 + +直接在WHERE子句、JOIN子句等涉及到的字段上创建索引。 + +一般存在于WHERE子句中的简单比较都是可以使用索引扫描的,因此在该涉及到的字段上创建索引可能是有效的。 + +索引也并非是创建得越多越好,在创建索引时需要在选择度较高、数据量不是特别少的字段上创建索引,否则该索引收益不大。 + +对于单语句的索引推荐,openGauss数据库已经内置了该功能,用户可以通过调用系统函数gs_index_advise() 进行推荐,例如: + +```cpp +select * from gs_index_advise('select * from t1 where a > 1'); +``` + +单语句索引推荐的核心逻辑可以表示为: + +1. 提取JOIN类算子中的连接条件,保存为连接关系; + +2. 提取Filter类算子中的过滤条件,保存为过滤关系; + +3. 分析过滤关系中涉及字段的选择度和数据量,将评估适合创建索引的字段加入到候选索引列表中; + +4. 分析连接关系,根据表的结果集大小确定驱动表,根据连接关系,将被驱动表中涉及的字段加入到候选索引列表中; + +5. 提取Aggregate类算子涉及的字段,将该字段加入到候选索引列表中; + +6. 提取Sort算子涉及的字段,将该字段加入到候选索引列表中; + +7. 评估候选索引列表中的全部字段,过滤重复索引,合并相关索引; + +8. 输出最终索引推荐的结果。 + +对于推荐出来的候选索引,用户可以自行决策是否创建,也可以通过openGauss的虚拟索引功能来评估索引收益,进行辅助决策。 + +openGauss的索引推荐功能可以建立在查询解析之后的查询树(Query Tree)的基础上进行索引推荐,也就是说,openGauss的索引推荐是建立在算子粒度上的。这样,某些被优化器改写的SQL语句(如exists, in 子查询),也可以被轻易地捕获并进行索引推荐,而前文提到的基于AST进行索引推荐的工具是很难实现的。 + diff --git "a/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\264\242\345\274\225\357\274\210\344\270\213\357\274\211.md" "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\264\242\345\274\225\357\274\210\344\270\213\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..4a82f523e5a78fb62fe516defee2aad74f632ff7 --- /dev/null +++ "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\264\242\345\274\225\357\274\210\344\270\213\357\274\211.md" @@ -0,0 +1,39 @@ + +## 索引冗余 + +### B+ Tree索引 + +日常创建的索引中,使用最多的是B+ Tree索引,下面说明一下原因: + +B+ Tree是一个多叉树,它的每一个子节点都是父节点的一个子“范围”。记录(或记录的位置)最终存储在B+ Tree的叶子节点中。因此,在进行数据检索时,只需要扫描匹配的子节点中的指定“范围”即可。 + +但是,对于数据的删除,也需要付出相同的时间开销,进行B+ Tree节点的调整;如果被索引的数据修改了,还需要调整B+ Tree中原有的节点结构。由于B+ Tree的插入、删除、检索的算法时间复杂度都是相同的,因此当业务系统中的插入和删除操作更多时,索引维护的代价就会更大,甚至超过索引检索时带来的收益。与此同时,索引页也需要占用额外的磁盘空间,被索引数据量越大,索引页占据的空间就越大。而且,当前openGauss中的B+ Tree的实现仍然是有锁的,更多的索引页面有可能涉及更多的锁维护操作。 + +在openGauss数据库中,可以通过下述语句简单识别没有被使用过的索引: + +```cpp +SELECT s.schemaname, + s.relname AS tablename, + s.indexrelname AS indexname, + pg_relation_size(s.indexrelid) AS index_size +FROM pg_catalog.pg_stat_user_indexes s + JOIN pg_catalog.pg_index i ON s.indexrelid = i.indexrelid +WHERE s.idx_scan = 0 -- has never been scanned + AND 0 <>ALL (i.indkey) + AND NOT i.indisunique + AND NOT EXISTS + (SELECT 1 FROM pg_catalog.pg_constraint c + WHERE c.conindid = s.indexrelid) +ORDER BY pg_relation_size(s.indexrelid) DESC; +``` + +可以修改上述SQL语句中的 idx_scan 条件中的阈值,来调整返回结果。 + +对于workload中全量SQL语句进行索引创建其实是非常困难的,因为需要权衡全量SQL中增删查改语句的占比情况,同时需要估计索引的检索收益和维护代价,这个权衡过程十分复杂,一般的人工操作其实是很难的。因此,在日常数据库使用中,当需要创建索引时,最好进行全局业务的评估,衡量是否会干扰到其他业务,以及创建的总体收益是否为正,以免后期难以维护。 + +不过,对于openGauss数据库来说,可以使用系统级别的索引推荐功能来解决上述痛点问题,可以通过下述命令查看使用说明: + +```cpp +gs_dbmind component index_advisor --help +``` + diff --git "a/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\264\242\345\274\225\357\274\210\344\270\255\357\274\211.md" "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\264\242\345\274\225\357\274\210\344\270\255\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..4be46f9814aa528f0d91ff722671585e57cb4115 --- /dev/null +++ "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\347\264\242\345\274\225\357\274\210\344\270\255\357\274\211.md" @@ -0,0 +1,18 @@ + +## 索引失效 + +存在下面这6中情况: + +1. 联合索引(又叫复合索引、多列索引)的最左匹配原则失效:openGauss的联合索引满足最左匹配原则,如果查询不满足最左匹配原则,数据库优化器会倾向于放弃选择该索引扫描; + +2. 使用了SELECT *: 除了可能扫描到不需要的字段之外,使用该写法还有可能导致openGauss的IndexOnlyScan 失效(在MySQL中称为CoveringIndex),也可能导致索引扫描后进行不必要的回表; + +3. 谓词中的索引列参与了运算:这个问题一般不会出现在openGauss数据库中,这是因为openGauss的rewrite过程可以将该写法进行改写。但是openGauss的rewrite过程是基于规则进行的,某些情况下会存在改写匹配不上的情况,例如把WHERE子句的中谓词变得复杂一点就可能出现改写失效,进而导致索引失效,例如select a from t1 where b - 0 > 1 and c < 100; 语句中的减0与否会产生两种截然不同的执行计划; + +4. 索引列涉及函数计算:对于openGauss来说,函数计算结果往往是“不可预测”的,故该索引有可能是失效的;不过openGauss支持函数索引(Functional Index),对于必须在字段上执行函数的情况可以选择使用该索引,只不过该索引的维护代价会比较大;同时,如果定义的函数可以被rewrite过程改写,该索引仍然可能是有效的,这点可能与某些数据库的行为不同; + +5. 谓词中使用like: 对于字符串类型(如varchar, text)的字段,在使用like进行模糊查询时,在openGauss中默认是不走索引的;openGauss对字符串类型的字段,一般在进行等值查询时会选择使用索引,如果对于该字段更多地进行模糊查询(如like或正则),则需要在创建索引时显式地添加text_pattern_ops参数,如 create index on movies (title text_pattern_ops); 同时,该B+ Tree索引也只仅支持前缀匹配查询,如果希望利用B+ Tree进行后缀匹配,可以使用字符串翻转小技巧;对于全文检索,可以使用openGauss支持的tsquery特性,并通过创建GIN或GiST索引加速查询; + +6. SQL语义上不应走索引:这种情况的类型有很多,比较典型的是谓词中对同一张表的两列进行比较、不等值比较(如!=, not in, not exists, is not null)、全量排序、类型转换(如字段的类型是varchar, 在谓词中与bigint进行比较时发生了隐式转换)等。 + + diff --git "a/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\350\241\250\346\225\260\346\215\256.md" "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\350\241\250\346\225\260\346\215\256.md" new file mode 100644 index 0000000000000000000000000000000000000000..54048efc9184cf569d213f705536109eae790021 --- /dev/null +++ "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\350\241\250\346\225\260\346\215\256.md" @@ -0,0 +1,20 @@ + +## 问题 + +一般来说,造成慢SQL的表数据有以下几种情况 + +1. 表的数据量很大,且很少被缓存,导致语句需要扫描的元组很多; + +2. 表的数据量很大,在修改、删除数据时需要修改较多的元组; + +3. 向表中插入的数据量很大; + +4. 业务上需要检索出的数据量很多; + +5. 频繁的数据修改,导致表中存在很多死元组(dead tuple),影响扫描性能; + +## 解决 + +表的数据量较大导致的慢SQL问题,一般需要从业务上进行入手,直接通过修改数据库来达到优化慢SQL的目的是很难实现的。 + +因此,需要用户分析具体的业务,对业务数据进行冷热分离、分库分表、使用分布式中间件等。如果希望在数据库层进行优化,则可以通过增加宿主机的内存,进而增加max_process_memory、shared_buffers、work_mem等的大小;使用性能更佳的磁盘;适当创建索引;使用表空间调整磁盘布局等。 diff --git "a/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\350\265\204\346\272\220\347\253\236\344\272\211.md" "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\350\265\204\346\272\220\347\253\236\344\272\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..dab3188670d3b32c9232fdc8d0fd396f316df3ae --- /dev/null +++ "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\344\271\213\350\265\204\346\272\220\347\253\236\344\272\211.md" @@ -0,0 +1,57 @@ + +当系统同时执行某些SQL语句的时候,它们可能会互相影响,进而导致某些SQL语句变为慢SQL, 这就是典型的资源竞争导致的慢SQL. 同时,不仅数据库中的语句们可能会进行资源竞争。在混合部署的环境中,操作系统上的其他任务也可能会影响数据库系统的表现。 + +对于一般的等待事件(wait event)来说,openGauss具备等待事件的记录视图,用户可以通过下列方法从宏观上查看Top级别的等待事件: + +```cpp +select * from dbe_perf.wait_events order by total_wait_time desc; +``` + +一般来说,对于数据库外部原因导致的资源竞争包括CPU、内存、IO的竞争,最典型的情况是IO风暴(Freeze IO)、CPU的计算资源的占用等。对于这种情况,一般不要将数据库与其他业务系统混合部署即可避免。 + +比较困难的是,数据库自己的某些任务之间互相影响,例如锁竞争、IO竞争等。 + +数据库中的不同SQL语句对锁资源进行占用,阻塞了其他语句的正常执行,导致SQL语句变慢了,甚至还会触发死锁检测。比较简单的排查当前锁占用情况的SQL语句是: + +```cpp + + SELECT c.relkind, + d.datname, + c.relname, + l.mode, + s.query, + extract(epoch + FROM pg_catalog.now() - s.xact_start) AS holding_time + FROM pg_locks AS l + INNER JOIN pg_database AS d ON l.database = d.oid + INNER JOIN pg_class AS c ON l.relation = c.oid + INNER JOIN pg_stat_activity AS s ON l.pid = s.pid + WHERE s.pid != pg_catalog.pg_backend_pid(); +``` + +注意:openGauss并不支持pg_blocking_pids 函数。所以,通过该函数是无法查看到锁等待情况的。 + +下图展示了通过DBMind提供的openGauss-exporter监控到的数据库持锁情况: + +![](/api/attachments/398261) + +还有一种情况是IO使用受到影响,例如系统正在进行IO操作时,执行某条SQL语句,该SQL语句对磁盘的访问被阻塞了。典型的数据库系统IO操作包括Analyze, Vacuum以及checkpoint 等。openGauss为此做了很多优化,例如增量checkpoint, 使用更大的版本号等(可以避免大量的autovacuum for prevent wrap)。 + +当然,除了上面列出的情况外,还存在并发量接近或超过系统负荷导致的性能下降和拒绝服务。例如,大量复杂查询语句对CPU资源的竞争、大并发情况下引起数据库的响应时间变慢等。 + +就资源竞争引起的慢SQL来说,基本都可以通过系统指标来发现。例如监控慢SQL发生时刻的CPU、内存、IO、锁、网络等的使用情况,根据该慢SQL发生的背景信息即可推断出该慢SQL是否由资源竞争导致的,以及是何资源短缺导致的。对于openGauss来说,DBMind提供了非常强大的数据库指标采集功能,即DBMind与Prometheus平台适配的exporter. 用户可以直接通过下述命令查看exporter的启动参数: + +openGauss-exporter: 用于采集数据库指标,除常规指标外,还能监控慢SQL、系统配置等。 + +```cpp +gs_dbmind component opengauss_exporter --help +``` + +reprocessing-exporter: 可以对Prometheus中已经采集到的指标进行聚合,例如计算QPS、内存使用率等。 + +```cpp +gs_dbmind component reprocessing_exporter --help +``` + +注意:openGauss对于采集指标也进行了权限隔离,必须要求openGauss-expoter连接的用户具有sysadmin, monadmin 权限才可以获取某些监控表的指标。 + diff --git "a/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\345\210\206\346\236\220.md" "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\345\210\206\346\236\220.md" new file mode 100644 index 0000000000000000000000000000000000000000..57c73fc3d39bd6af0eb752b262b3e3841eaac5c9 --- /dev/null +++ "b/TestTasks/wang-luyang/\346\205\242SQL\345\216\237\345\233\240\345\210\206\346\236\220.md" @@ -0,0 +1,247 @@ + +## 前言 + +由于不同的数据库系统架构存在差异、代码实现也有一定的不同,很多慢SQL解决方案不是通用的。 + +## 监控慢SQL + +先通过设置GUC参数log_min_duration_statement 来指定openGauss系统监控的慢SQL阈值。同时,调大instr_unique_sql_count的数值,以免出现“missing SQL statement, GUC instr_unique_sql_count is too small.”的提示。这里以设置慢SQL检测阈值为5秒(默认数值单位是毫秒)为例: + +```cpp +gs_guc reload -D $PGDATA -c 'log_min_duration_statement = 5000' -c 'instr_unique_sql_count = 2000' + +``` + +然后执行一个慢SQL,可以在dbe_perf.statement_history视图中查看到结果: + +```cpp +select pg_sleep(6); -- 构造的慢SQL +select * from dbe_perf.statement_history order by start_time desc; +``` + +## 由索引导致的慢SQL + +存在一下三种情况: + +* 缺乏有效索引 + +* 执行计划没有选择索引扫描,即索引失效 + +* 冗余索引 + +### 缺乏有效索引 + +#### 原因 + +绝大多数此类SQL语句都是SELECT语句,且该类SQL语句涉及到的表数据量较多,且谓词上没有创建索引,导致数据库系统需要通过全盘扫描来获取数据。 + +#### 解决 + +直接在WHERE子句、JOIN子句等涉及到的字段上创建索引。 + +一般存在于WHERE子句中的简单比较都是可以使用索引扫描的,因此在该涉及到的字段上创建索引可能是有效的。 + +索引也并非是创建得越多越好,在创建索引时需要在选择度较高、数据量不是特别少的字段上创建索引,否则该索引收益不大。 + +对于单语句的索引推荐,openGauss数据库已经内置了该功能,用户可以通过调用系统函数gs_index_advise() 进行推荐,例如: + +```cpp +select * from gs_index_advise('select * from t1 where a > 1'); +``` + +单语句索引推荐的核心逻辑可以表示为: + +1. 提取JOIN类算子中的连接条件,保存为连接关系; + +2. 提取Filter类算子中的过滤条件,保存为过滤关系; + +3. 分析过滤关系中涉及字段的选择度和数据量,将评估适合创建索引的字段加入到候选索引列表中; + +4. 分析连接关系,根据表的结果集大小确定驱动表,根据连接关系,将被驱动表中涉及的字段加入到候选索引列表中; + +5. 提取Aggregate类算子涉及的字段,将该字段加入到候选索引列表中; + +6. 提取Sort算子涉及的字段,将该字段加入到候选索引列表中; + +7. 评估候选索引列表中的全部字段,过滤重复索引,合并相关索引; + +8. 输出最终索引推荐的结果。 + +对于推荐出来的候选索引,用户可以自行决策是否创建,也可以通过openGauss的虚拟索引功能来评估索引收益,进行辅助决策。 + +openGauss的索引推荐功能可以建立在查询解析之后的查询树(Query Tree)的基础上进行索引推荐,也就是说,openGauss的索引推荐是建立在算子粒度上的。这样,某些被优化器改写的SQL语句(如exists, in 子查询),也可以被轻易地捕获并进行索引推荐,而前文提到的基于AST进行索引推荐的工具是很难实现的。 + + +## 索引失效 + +存在下面这6中情况: + +1. 联合索引(又叫复合索引、多列索引)的最左匹配原则失效:openGauss的联合索引满足最左匹配原则,如果查询不满足最左匹配原则,数据库优化器会倾向于放弃选择该索引扫描; + +2. 使用了SELECT *: 除了可能扫描到不需要的字段之外,使用该写法还有可能导致openGauss的IndexOnlyScan 失效(在MySQL中称为CoveringIndex),也可能导致索引扫描后进行不必要的回表; + +3. 谓词中的索引列参与了运算:这个问题一般不会出现在openGauss数据库中,这是因为openGauss的rewrite过程可以将该写法进行改写。但是openGauss的rewrite过程是基于规则进行的,某些情况下会存在改写匹配不上的情况,例如把WHERE子句的中谓词变得复杂一点就可能出现改写失效,进而导致索引失效,例如select a from t1 where b - 0 > 1 and c < 100; 语句中的减0与否会产生两种截然不同的执行计划; + +4. 索引列涉及函数计算:对于openGauss来说,函数计算结果往往是“不可预测”的,故该索引有可能是失效的;不过openGauss支持函数索引(Functional Index),对于必须在字段上执行函数的情况可以选择使用该索引,只不过该索引的维护代价会比较大;同时,如果定义的函数可以被rewrite过程改写,该索引仍然可能是有效的,这点可能与某些数据库的行为不同; + +5. 谓词中使用like: 对于字符串类型(如varchar, text)的字段,在使用like进行模糊查询时,在openGauss中默认是不走索引的;openGauss对字符串类型的字段,一般在进行等值查询时会选择使用索引,如果对于该字段更多地进行模糊查询(如like或正则),则需要在创建索引时显式地添加text_pattern_ops参数,如 create index on movies (title text_pattern_ops); 同时,该B+ Tree索引也只仅支持前缀匹配查询,如果希望利用B+ Tree进行后缀匹配,可以使用字符串翻转小技巧;对于全文检索,可以使用openGauss支持的tsquery特性,并通过创建GIN或GiST索引加速查询; + +6. SQL语义上不应走索引:这种情况的类型有很多,比较典型的是谓词中对同一张表的两列进行比较、不等值比较(如!=, not in, not exists, is not null)、全量排序、类型转换(如字段的类型是varchar, 在谓词中与bigint进行比较时发生了隐式转换)等。 + + + +## 索引冗余 + +### B+ Tree索引 + +日常创建的索引中,使用最多的是B+ Tree索引,下面说明一下原因: + +B+ Tree是一个多叉树,它的每一个子节点都是父节点的一个子“范围”。记录(或记录的位置)最终存储在B+ Tree的叶子节点中。因此,在进行数据检索时,只需要扫描匹配的子节点中的指定“范围”即可。 + +但是,对于数据的删除,也需要付出相同的时间开销,进行B+ Tree节点的调整;如果被索引的数据修改了,还需要调整B+ Tree中原有的节点结构。由于B+ Tree的插入、删除、检索的算法时间复杂度都是相同的,因此当业务系统中的插入和删除操作更多时,索引维护的代价就会更大,甚至超过索引检索时带来的收益。与此同时,索引页也需要占用额外的磁盘空间,被索引数据量越大,索引页占据的空间就越大。而且,当前openGauss中的B+ Tree的实现仍然是有锁的,更多的索引页面有可能涉及更多的锁维护操作。 + +在openGauss数据库中,可以通过下述语句简单识别没有被使用过的索引: + +```cpp +SELECT s.schemaname, + s.relname AS tablename, + s.indexrelname AS indexname, + pg_relation_size(s.indexrelid) AS index_size +FROM pg_catalog.pg_stat_user_indexes s + JOIN pg_catalog.pg_index i ON s.indexrelid = i.indexrelid +WHERE s.idx_scan = 0 -- has never been scanned + AND 0 <>ALL (i.indkey) + AND NOT i.indisunique + AND NOT EXISTS + (SELECT 1 FROM pg_catalog.pg_constraint c + WHERE c.conindid = s.indexrelid) +ORDER BY pg_relation_size(s.indexrelid) DESC; +``` + +可以修改上述SQL语句中的 idx_scan 条件中的阈值,来调整返回结果。 + +对于workload中全量SQL语句进行索引创建其实是非常困难的,因为需要权衡全量SQL中增删查改语句的占比情况,同时需要估计索引的检索收益和维护代价,这个权衡过程十分复杂,一般的人工操作其实是很难的。因此,在日常数据库使用中,当需要创建索引时,最好进行全局业务的评估,衡量是否会干扰到其他业务,以及创建的总体收益是否为正,以免后期难以维护。 + +不过,对于openGauss数据库来说,可以使用系统级别的索引推荐功能来解决上述痛点问题,可以通过下述命令查看使用说明: + +```cpp +gs_dbmind component index_advisor --help +``` + + +## 前言 + +在系统配置中,最常见的配置项就是对资源的配置。这包括允许使用的最大资源(主要是内存)、以及资源的使用方式等。除了调整资源配置,有些情况下还需要配置数据库优化器Cost Model的代价值。 + +下面是几个会影响SQL语句成为慢SQL的系统参数: + +### max_process_memory + +该参数与enable_memory_limit配合使用,用于限制一个openGauss实例可用的最大内存。需要将该参数值与宿主机系统的内存总量进行匹配,将宿主机用于操作系统正常运行所需的内存刨除后,剩下的内存空间就可以尽可能多地划分给openGauss实例使用了。否则,openGauss为了避免造成OOM问题,会通过该参数限制数据库允许使用的最大内存。因此,如果在客户端或者日志中出现类似“memory usage reach the max_dynamic_memory”的报错时,一般是由于该参数值太小导致的。 + +### shared_buffers + +数据库系统使用的缓存池大小。一般来说,综合来看对数据库影响最大的参数就是它了,因为如果该参数设置得过小,会导致缓存不足,从而产生大量的磁盘I/O. 该参数在openGauss上的默认值很小,只有32MB,对于绝大多数的生产场景是不够的。一般的经验值是设置为系统内存的25%, 甚至在某些场景中还可以再大一点。不过openGauss的buffer没有通过DirectIO实现,仍然使用了系统缓存(cache),所以一般认为超过系统内存的40%也起不到再好的效果了。与此同时,checkpoint_segments 参数也需要随着shared_buffers的调大跟着变大一些。 + + +### work_mem + +显式指定内排序和哈希表能使用的内存空间大小,如果该值设得比较小,会向磁盘写入更多的临时文件。因此,我们可以适当地增加该值的大小。但是需要注意的是,业务系统可能存在并行执行的复杂语句,如果这些语句都占用非常多的work_mem大小的资源,则可能会导致内存使用占满(openGauss存在内存管控机制,一般不至于由于OOM导致系统重启)。故而,该值设置得很大的时候要关注系统的并发问题。该参数对ORDER BY, DISTINCT, JOIN (merge join, hash join), HASH Agg, 基于hash的IN子查询都有影响。 + +### enable_nestloop + +开启该参数可以让优化器使用Nest Loop Join(NLJ), 但是关闭该参数也不会完全压制优化器选择NLJ. 对于某些复杂查询(如在TPC-H benchmark中的语句)来说,不应该选择NLJ, 但是优化器往往会出现规划错误。那么,在此场景下,可以通过禁用该参数来鼓励优化器选择使用其他JOIN方法。 + +### random_page_cost + +一般与seq_page_cost配合调整。该参数调整数据库的CBO优化器中随机扫描的代价。该值设置得越大,数据库越认为随机扫描不可取,也就越不倾向于使用索引。该参数的默认值是4,对于机械硬盘来说,是合适的。但是,如果业务系统的磁盘是固态硬盘的话,就应该适当调小一下该参数值,一般的经验是调整为1. + +### default_statistics_target + +当前openGauss的默认优化器是CBO, 它高度依赖数据的统计信息。因此,对于复杂查询来说,更优质的统计信息往往可以获得更好的执行计划。通过增大该参数的值,可以获得更准确的统计信息,但是也会增加ANALYZE的时间。因此,对于复杂语句较多的场景,可以适当增加该参数值。 + + + + +当系统同时执行某些SQL语句的时候,它们可能会互相影响,进而导致某些SQL语句变为慢SQL, 这就是典型的资源竞争导致的慢SQL. 同时,不仅数据库中的语句们可能会进行资源竞争。在混合部署的环境中,操作系统上的其他任务也可能会影响数据库系统的表现。 + +对于一般的等待事件(wait event)来说,openGauss具备等待事件的记录视图,用户可以通过下列方法从宏观上查看Top级别的等待事件: + +```cpp +select * from dbe_perf.wait_events order by total_wait_time desc; +``` + +一般来说,对于数据库外部原因导致的资源竞争包括CPU、内存、IO的竞争,最典型的情况是IO风暴(Freeze IO)、CPU的计算资源的占用等。对于这种情况,一般不要将数据库与其他业务系统混合部署即可避免。 + +比较困难的是,数据库自己的某些任务之间互相影响,例如锁竞争、IO竞争等。 + +数据库中的不同SQL语句对锁资源进行占用,阻塞了其他语句的正常执行,导致SQL语句变慢了,甚至还会触发死锁检测。比较简单的排查当前锁占用情况的SQL语句是: + +```cpp + + SELECT c.relkind, + d.datname, + c.relname, + l.mode, + s.query, + extract(epoch + FROM pg_catalog.now() - s.xact_start) AS holding_time + FROM pg_locks AS l + INNER JOIN pg_database AS d ON l.database = d.oid + INNER JOIN pg_class AS c ON l.relation = c.oid + INNER JOIN pg_stat_activity AS s ON l.pid = s.pid + WHERE s.pid != pg_catalog.pg_backend_pid(); +``` + +注意:openGauss并不支持pg_blocking_pids 函数。所以,通过该函数是无法查看到锁等待情况的。 + +下图展示了通过DBMind提供的openGauss-exporter监控到的数据库持锁情况: + +![](/api/attachments/398261) + +还有一种情况是IO使用受到影响,例如系统正在进行IO操作时,执行某条SQL语句,该SQL语句对磁盘的访问被阻塞了。典型的数据库系统IO操作包括Analyze, Vacuum以及checkpoint 等。openGauss为此做了很多优化,例如增量checkpoint, 使用更大的版本号等(可以避免大量的autovacuum for prevent wrap)。 + +当然,除了上面列出的情况外,还存在并发量接近或超过系统负荷导致的性能下降和拒绝服务。例如,大量复杂查询语句对CPU资源的竞争、大并发情况下引起数据库的响应时间变慢等。 + +就资源竞争引起的慢SQL来说,基本都可以通过系统指标来发现。例如监控慢SQL发生时刻的CPU、内存、IO、锁、网络等的使用情况,根据该慢SQL发生的背景信息即可推断出该慢SQL是否由资源竞争导致的,以及是何资源短缺导致的。对于openGauss来说,DBMind提供了非常强大的数据库指标采集功能,即DBMind与Prometheus平台适配的exporter. 用户可以直接通过下述命令查看exporter的启动参数: + +openGauss-exporter: 用于采集数据库指标,除常规指标外,还能监控慢SQL、系统配置等。 + +```cpp +gs_dbmind component opengauss_exporter --help +``` + +reprocessing-exporter: 可以对Prometheus中已经采集到的指标进行聚合,例如计算QPS、内存使用率等。 + +```cpp +gs_dbmind component reprocessing_exporter --help +``` + +注意:openGauss对于采集指标也进行了权限隔离,必须要求openGauss-expoter连接的用户具有sysadmin, monadmin 权限才可以获取某些监控表的指标。 + + +## 问题 + +一般来说,造成慢SQL的表数据有以下几种情况 + +1. 表的数据量很大,且很少被缓存,导致语句需要扫描的元组很多; + +2. 表的数据量很大,在修改、删除数据时需要修改较多的元组; + +3. 向表中插入的数据量很大; + +4. 业务上需要检索出的数据量很多; + +5. 频繁的数据修改,导致表中存在很多死元组(dead tuple),影响扫描性能; + +## 解决 + +表的数据量较大导致的慢SQL问题,一般需要从业务上进行入手,直接通过修改数据库来达到优化慢SQL的目的是很难实现的。 + +因此,需要用户分析具体的业务,对业务数据进行冷热分离、分库分表、使用分布式中间件等。如果希望在数据库层进行优化,则可以通过增加宿主机的内存,进而增加max_process_memory、shared_buffers、work_mem等的大小;使用性能更佳的磁盘;适当创建索引;使用表空间调整磁盘布局等。 + + +下面还列出了几种比较常见的、可能优化openGauss数据库性能的SQL改写规则 + +![](/api/attachments/398275) + diff --git "a/TestTasks/wang-luyang/\346\227\240\351\224\201\345\216\237\345\255\220\346\223\215\344\275\234.md" "b/TestTasks/wang-luyang/\346\227\240\351\224\201\345\216\237\345\255\220\346\223\215\344\275\234.md" new file mode 100644 index 0000000000000000000000000000000000000000..975f1857436083e3e30b4b2bf14c4a85a6b34b1c --- /dev/null +++ "b/TestTasks/wang-luyang/\346\227\240\351\224\201\345\216\237\345\255\220\346\223\215\344\275\234.md" @@ -0,0 +1,95 @@ + +## 简介 + +openGauss封装了32、64、128的原子操作,主要用于取代自旋锁,实现简单变量的原子更新操作。 + +## 相关函数 + +## gs_atomic_add_32 + +作用:32位原子加,并且返回加之后的值 + +```cpp +static inline int32 gs_atomic_add_32(volatile int32* ptr, int32 inc) +{ + return __sync_fetch_and_add(ptr, inc) + inc; +} +``` + +### gs_atomic_add_64 + +作用:64位原子加,并且返回加之后的值 + +```cpp +static inline int64 gs_atomic_add_64(int64* ptr, int64 inc) +{ + return __sync_fetch_and_add(ptr, inc) + inc; +} +``` + +### gs_compare_and_swap_32 + +作用:32位CAS操作,如果dest在更新前没有被更新,则将newval写到dest地址。dest地址的值没有被更新,就返回true;否则返回false。 + +```cpp +static inline bool gs_compare_and_swap_32(int32* dest, int32 oldval, int32 newval) +{ + if (oldval == newval) + return true; + + volatile bool res = __sync_bool_compare_and_swap(dest, oldval, newval); + + return res; +} +``` + +### gs_compare_and_swap_64 + +作用:64位CAS操作,如果dest在更新前没有被更新,则将newval写到dest地址。dest地址的值没有被更新,就返回true;否则返回false + +```cpp +static inline bool gs_compare_and_swap_64(int64* dest, int64 oldval, int64 newval) +{ + if (oldval == newval) + return true; + + return __sync_bool_compare_and_swap(dest, oldval, newval); +} +``` + +### arm_compare_and_swap_u128 + +作用:openGauss提供跨平台的128位CAS操作,在ARM平台下,使用单独的指令集汇编了128位原子操作,用于提升内核测锁并发的性能。 + +```cpp +static inline uint128_u arm_compare_and_swap_u128(volatile uint128_u* ptr, uint128_u oldval, uint128_u newval) +{ +#ifdef __ARM_LSE + return __lse_compare_and_swap_u128(ptr, oldval, newval); +#else + return __excl_compare_and_swap_u128(ptr, oldval, newval); +#endif +} +#endif + +``` + +### atomic_compare_and_swap_u128 + +作用:128位CAS操作,如果dest地址的值在更新前没有被其他线程更新,则将newval写到dest地址。dest地址的值没有被更新,就返回新值;否则返回被别人更新后的值。需要注意必须由上层的调用者保证传入的参数是128位对齐的。 + +```cpp +static inline uint128_u atomic_compare_and_swap_u128( + volatile uint128_u* ptr, + uint128_u oldval = uint128_u{0}, + uint128_u newval = uint128_u{0}) +{ +#ifdef __aarch64__ + return arm_compare_and_swap_u128(ptr, oldval, newval); +#else + uint128_u ret; + ret.u128 = __sync_val_compare_and_swap(&ptr->u128, oldval.u128, newval.u128); + return ret; +#endif +} +``` diff --git "a/TestTasks/wang-luyang/\346\255\273\351\224\201.md" "b/TestTasks/wang-luyang/\346\255\273\351\224\201.md" new file mode 100644 index 0000000000000000000000000000000000000000..02b7eab95d5f560dfdac7c342ceb09f0f7560461 --- /dev/null +++ "b/TestTasks/wang-luyang/\346\255\273\351\224\201.md" @@ -0,0 +1,21 @@ + +## 死锁 + +### 形成 + +死锁主要是由于进程B要访问进程A所在的资源,而进程A又由于种种原因不释放掉其锁占用的资源,从而数据库就会一直处于阻塞状态。 + +![](/api/attachments/398074) + +形成必要条件:**资源的请求与保持** + +因为每一个进程都可以在使用一个资源的同时去申请访问另一个资源,导致资源的访问出现了冲突。 + +### 打破方式 + +中断其中的一个事务的执行,打破环状的等待 + + + + + diff --git "a/TestTasks/wang-luyang/\346\257\205\351\230\263\351\230\237-\346\264\252\346\257\205\344\270\252\344\272\272\346\200\273\347\273\223.md" "b/TestTasks/wang-luyang/\346\257\205\351\230\263\351\230\237-\346\264\252\346\257\205\344\270\252\344\272\272\346\200\273\347\273\223.md" new file mode 100644 index 0000000000000000000000000000000000000000..77d42387442b445cbd7de0bcf275797f781f7b44 --- /dev/null +++ "b/TestTasks/wang-luyang/\346\257\205\351\230\263\351\230\237-\346\264\252\346\257\205\344\270\252\344\272\272\346\200\273\347\273\223.md" @@ -0,0 +1,35 @@ +openGauss锁机制 +[锁机制之自旋锁和LWLock轻量级锁](http://forum.gitlink.org.cn/forums/7881/detail "锁机制之自旋锁和LWLock轻量级锁") +[锁机制之常规锁](http://forum.gitlink.org.cn/forums/7884/detail "锁机制之常规锁") +[死锁](http://forum.gitlink.org.cn/forums/7966/detail "死锁") +[LWLock死锁检测自愈](http://forum.gitlink.org.cn/forums/7967/detail "LWLock死锁检测自愈") +[常规锁死锁检测](http://forum.gitlink.org.cn/forums/7968/detail "常规锁死锁检测") +[无锁原子操作](http://forum.gitlink.org.cn/forums/7969/detail "无锁原子操作") + +openGauss公共组件 +[系统表](http://forum.gitlink.org.cn/forums/7880/detail "公共组件之系统表") +[数据库初始化](https://forum.gitlink.org.cn/forums/7882/detail "公共组件之数据库初始化") +[多线程架构](http://forum.gitlink.org.cn/forums/7885/detail "公共组件之多线程架构") +[线程池技术](http://forum.gitlink.org.cn/forums/7892/detail "公共组件之线程池技术") +[内存管理](http://forum.gitlink.org.cn/forums/7978/detail "公共组件之内存管理") +[多维监控](http://forum.gitlink.org.cn/forums/7979/detail "公共组件之多维监控") +[模拟信号机制](http://forum.gitlink.org.cn/forums/7980/detail "公共组件之模拟信号机制") + +openGauss事物机制 +[事务机制概述](http://forum.gitlink.org.cn/forums/7844/detail "事务机制概述") +[事务并发控制概述](http://forum.gitlink.org.cn/forums/7851/detail "事务并发控制概述") +[事务状态机之上层状态机](http://forum.gitlink.org.cn/forums/7853/detail "事务状态机之上层状态机") +[事务状态机之事务底层状态](http://forum.gitlink.org.cn/forums/7854/detail "事务状态机之事务底层状态") +[事务状态转换相关函数介绍](http://forum.gitlink.org.cn/forums/7855/detail "事务状态转换相关函数介绍") +[事务ID分配](http://forum.gitlink.org.cn/forums/7856/detail "事务ID分配") +[CLOG和CSNLOG](http://forum.gitlink.org.cn/forums/7859/detail "CLOG和CSNLOG") + +openGauss进程内多线程管理机制 +[进程内多线程管理机制之事务信息管理](http://forum.gitlink.org.cn/forums/7868/detail "进程内多线程管理机制之事务信息管理") +[进程内多线程管理机制之多版本快照机制](http://forum.gitlink.org.cn/forums/7874/detail "进程内多线程管理机制之多版本快照机制") + + + +openGaussMVCC可见性判断机制 +[MVCC可见性判断机制之事务隔离级别和CSN](http://forum.gitlink.org.cn/forums/7860/detail "MVCC可见性判断机制之事务隔离级别和CSN") +[MVCC可见性判断机制之数据结构及函数](http://forum.gitlink.org.cn/forums/7863/detail "MVCC可见性判断机制之数据结构及函数") diff --git "a/TestTasks/wang-luyang/\347\264\242\345\274\225\357\274\210\344\270\200\357\274\211.md" "b/TestTasks/wang-luyang/\347\264\242\345\274\225\357\274\210\344\270\200\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..5dd58889b968aa94ce9a623421a7fe861f5451c8 --- /dev/null +++ "b/TestTasks/wang-luyang/\347\264\242\345\274\225\357\274\210\344\270\200\357\274\211.md" @@ -0,0 +1,58 @@ +# 索引(一) +## 索引方法 +### B-Tree索引 +B-tree索引适合比较查询和范围查询,当查询条件使用(>,=,<,>=,<=)时,可以使用B-tree索引。B-tree + +索引是PostgreSQL和openGauss的默认索引方式。其索引页分为几种:meta-page、root-page、branch-page和leaf-page + + 1. meta-page: B-tree索引的元数据页,主要存储B-tree索引的元数据信息,可以通过meta page找到root page信息。 + 2. root-page:B-tree的根节点。 + 3. branch-page:内部节点,B-tree中根节点和叶子节点外的其他节点。 + 4. leaf-page:叶子节点,其中的ctid指向heap tuple,非叶子节点的ctid指向其子节点。 + +安装pageinspect后,可以通过: + +``` +--查看meta-page 信息 +select * from bt_metap(‘tab_pkey’) + +--查看索引页信息 +select * from bt_page_stats(‘tab_pkey’,1) + +--查看页内tuple信息 +select * from bt_page_items(‘tab_pkey’,1) +``` + +High-Key表示此page的右兄弟节点的最小值,由于page之间数据是有序的,当前page内所有key <= High-Key的值。对unique index而言,当前page内所有key < High-Key的值。 + +每一层的最右侧节点,由于没有右兄弟节点,因此page内没有High-Key。 + +Special Space为索引页特有,由于存储每个page左右两边page的页号,可通过Special Space找到左右page。 + +列存的B-tree索引整体结构上与行存相同。leaf-page上行存存储的是key到ctid的映射关系,行存可以直接ctid中的block number及offset找到heap tuple的位置。列存的ctid中记录的是(cu_id, offset),还需要再对应的CUDesc表中根据cu_id列的索引找到对应的CUDesc记录,打开对应的CU文件,根据offset找到数据。 + +列存上的B-tree索引不支持创建表达式索引、部分索引和唯一索引。 + +### GiST索引 +GiST(Generalized Search Tree)也是一棵平衡树,B-tree和比较语义强关联,适用于(>、>=、=、<=、<)这五个操作符。但现代数据库中存储的一些数据,如地理位置、图像数据等这五个操作符可能没有实际意义,GiST索引允许定义规则来将数据分布到平衡树中,并允许定义方法来访问数据。例如,GiST索引可以定义一棵存储空间数据的R-Tree,支持相对位置运算符(如位于左侧、右侧、包含等)。 + +GiST屏蔽了数据库的内部工作机制,比如锁的机制和预写日志,使得实现新的GiST索引实例(或称作索引操作符类)的工作相对比较轻松。基于GiST架构的索引操作符类只需实现预定义的几个接口。 + +### GIN索引 +Generalized Inverted Tree倒排索引。主要用于多值类型,如数组、全文索引等。如果对应TID的列表很小,可以和元素放在一个页面内(称为posting list)。如果TID列表很大,需要使用更高效的数据结构B-tree,这棵B-tree存储在单独的页面中(称为posting tree)。 + +行存表支持的索引类型:B-tree(缺省值)、GIN、GiST。 + +列存表支持的索引类型:Psort(缺省值)、B-tree、GIN。 + +## 索引相关系统表 +## PG_AM +PG_AM系统表存储有关索引访问方法的信息。系统支持的每种索引访问方法都有一行。表中各个字段的含义可以参考官方文档: +![https://opengauss.org/zh/docs/2.0.0/docs/Developerguide/PG_AM.html](https://img-blog.csdnimg.cn/0034548244054701bcfce24fd4887da8.png) + + +## PG_INDEX +PG_INDEX系统表存储索引的一部分信息,其他的信息大多数在PG_CLASS中。表中各个字段的含义可以参考官方文档: +![https://opengauss.org/zh/docs/2.0.0/docs/Developerguide/PG_INDEX.html](https://img-blog.csdnimg.cn/7d2d674fd82247e4879184eaf5282156.png) +对于分区表的partition local index,除了在pg_index中有一行数据外,每个分区的索引信息存储在pg_partition中。 +除了上述两张表外,索引使用流程中涉及的相关的系统表还有很多,如 pg_class、pg_attribute、pg_depend、pg_constraint等。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\347\264\242\345\274\225\357\274\210\344\272\214\357\274\211.md" "b/TestTasks/wang-luyang/\347\264\242\345\274\225\357\274\210\344\272\214\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..ef64a784291e50f3b79f6913601094d4eba2a607 --- /dev/null +++ "b/TestTasks/wang-luyang/\347\264\242\345\274\225\357\274\210\344\272\214\357\274\211.md" @@ -0,0 +1,61 @@ +## 索引使用流程 +### 创建索引 +创建索引入口函数。 + +#### DefineIndex + + 1. 创建与索引相关的参数检查和验证。 + 2. 调用index_create完成索引创建主要工作。所有索引创建都需要调用index_create,通过入参决定是不是需要构建索引结构。有一些流程,如create index concurrently,或者分区表的partition local index,在这一步实际只是创建索引相关元数据,构建索引结构在后续流程完成。非分区表的index、分区表的global index构建索引结构在这一步完成。 + 3. 如果创建了分区表的分区本地索引,执行partition_index_create命令遍历所有分区,逐个创建分区索引。 + 4. 如果是并发创建索引,则执行创建索引的并发过程。在这个过程中,添加到表中的锁类型是ShareUpdateExclusiveLock,它不会阻塞读和DML操作。普通创建索引的过程中,增加的锁类型为ShareLock,会阻塞DML操作。分区表不允许并发创建索引。 + +#### index_create + +1. 检查并验证参数。 +2. 创建一个索引元组描述符。元组描述符描述元组的结构。索引元组描述符中的许多属性都是从对应的表元组描述符中复制的。最后,在relcache中索引的元组描述符很多信息都来自于这里创建的元组描述符。ConstructTupleDescriptor +3. 为索引生成一个新的OID。GetNewRelFileNode +4. 将索引信息插入到relcache中。在磁盘上创建索引文件时,新的索引文件记录WAL。创建索引时,relfilenode被设置为与OID相同的值。如果取值为分区表的concurrent create index或分区本地索引,则跳过创建索引文件。heap_create +5. 插入pg_class、pg_attribute、pg_index、pg_constraint、pg_depend等系统表。 +6. 执行索引构建过程。非分区表的索引和分区表的全局索引实际上将在此步骤中构建索引结构。对于分区表的分区本地索引,将跳过此步骤。如果并发创建索引,请跳过此步骤。 + +#### index_build +执行构建索引。在调用index_build之前,插入与index_相关的元数据并创建空索引文件。Index_build根据pg_am中ambuild指定的索引创建处理程序执行索引构建过程。 + + 1. 查找基于pg_am和索引类型构建索引的过程。例如,btree索引的ambuild为btbuild, + gin索引的ambuild为ginbuild。调用相应的处理程序。index_build_storage + 2. 建立索引后,如果索引不是热安全的,请将pg_index中的indcheckxmin设置为true。indcheckxmin的目的是告诉其他事务该索引可能不安全。如果发现索引的indcheckxmin为true,那么我们需要比较创建索引的事务与当前事务的顺序,以确定是否可以使用索引。 + 3. 更新pg_class中表和索引相关的字段。例如,将relhasindex和relallvisible设置为true表示表是否有索引。 + +#### btbuild + +不同类型的索引,对应的建索引的处理函数不同。btbuild是B-tree索引对应的处理函数。 + + 1. 构建一个BTBuildState对象,用于btbuild。BTBuildState中包含两个BTSpool对象指针,用于将heap + tuple加载到内存中,以及heap tuple的排序。BTSpool中包含一个Tuplesortstate + 类型的指针,Tuplesortstate 中用于记录tuple sort过程中的状态,维护tuple sort所需的内存空间/磁盘空间。 + 2. 执行heap scan。如果是普通建索引,需要读取所有heap tuple(SNAPSHOT_ANY),然后判断heap tuple是否需要被索引。如果是create index concurrently 基于MVCC snapshot读取heap tuple(SNAPSHOT_MVCC),每个读取出来的heap tuple抽取出索引需要的列信息。 对于heap-only-tuple,index tuple中的tid指向hot-chain的root。IndexBuildHeapScan ? GlobalIndexBuildHeapScan + 3. 对扫描出的heap tuple进行排序;基于排完序的index tuple,构建完整的B-tree索引。 +#### _bt_leafbuid + 1. 对index tuple进行排序。tuplesort_performsort + 2. 基于排完序的index tuple,构建完整的B-tree索引。 + +#### _bt_load + + 1. 遍历所有排好序的index tuple,逐个调用_bt_buildadd加入到B-tree page中。B-tree从叶子节点开始构建,每一层从左向右构建。如果page写满了会触发下盘,同时创建同层右侧page;如果上层父page不存在,还会创建父page;如果已经存在父page,则将本page的minkey 和页号插入父节点。插入父节点的过程和插入子节点类似,可能触发父节点下盘等动作。index page会在special space记录左右两侧page的页号。每个page都会记WAL。 + 2. 由于构建B-tree的过程是自左向右、自底向上,触发page下盘是page写满时,所以所有index + tuple遍历完后,每一层的最右侧page可能还没有下盘及加入父节点。因此所有index tuple遍历完成后,还需要对每一层的最右侧节点做一次处理。每一层的最右侧节点没有HK,所以最终所有的ItemPointer需要向左移动一个位置。B-tree索引构建完成后,还需要构建meta-page,所有page都会写WAL,在流程结束前会主动调一次fsync,让WAL下盘。_bt_uppershutdown + +#### partition_index_create + +用于创建分区表的partition local index。创建分区表的partition local index时,先获取分区信息,然后遍历每一个分区执行partition_index_create。 + + 1. 为partition local index生成新的OID + 2. 向partcache中插入索引相关信息,创建partition local index索引文件,记录WAL。heapCreatePartition + 3. 在pg_partition中插入partition local index相关信息。insertPartitionEntry + 4. 执行索引构建。index_build + 5. 更新pg_class中表和索引信息。 + + ## 删除索引 +和创建索引类似,删除索引也有concurrent和非concurrent两种方式,对应的加锁类型分别是ShareUpdateExclusiveLock和AccessExclusiveLock。非concurrent删除索引流程上更简单一些,在表和索引上加AccessExclusiveLock,删除索引文件和相关元数据,刷新缓存。 + + diff --git "a/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217-\345\255\220\346\237\245\350\257\242\357\274\214\346\225\260\347\273\204\357\274\214\350\241\214\350\241\250\350\276\276.md" "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217-\345\255\220\346\237\245\350\257\242\357\274\214\346\225\260\347\273\204\357\274\214\350\241\214\350\241\250\350\276\276.md" new file mode 100644 index 0000000000000000000000000000000000000000..539aa2837bb6faf5c574839fbda0b2914084547e --- /dev/null +++ "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217-\345\255\220\346\237\245\350\257\242\357\274\214\346\225\260\347\273\204\357\274\214\350\241\214\350\241\250\350\276\276.md" @@ -0,0 +1,79 @@ +# 1.子查询表达式 + +子查询表达式主要有以下几种: + + - EXISTS/NOT EXISTS +![在这里插入图片描述](https://img-blog.csdnimg.cn/b917de94c55841d99078af508afa96b1.png) + +EXISTS的参数是一个任意的SELECT语句,或者说子查询。系统对子查询进行运算以判断它是否返回行。如果它至少返回一行,则EXISTS结果就为”真”;如果子查询没有返回任何行, EXISTS的结果是”假”。 + +这个子查询通常只是运行到能判断它是否可以生成至少一行为止,而不是等到全部结束。 + + - IN/NOT IN +![在这里插入图片描述](https://img-blog.csdnimg.cn/4580ff19335741f6a83c643ae77538c5.png) + +右边是一个圆括弧括起来的子查询,它必须只返回一个字段。左边表达式对子查询结果的每一行进行一次计算和比较。如果找到任何相等的子查询行,则IN结果为”真”。如果没有找到任何相等行,则结果为”假”(包括子查询没有返回任何行的情况)。 + +表达式或子查询行里的NULL遵照SQL处理布尔值和NULL组合时的规则。如果两个行对应的字段都相等且非空,则这两行相等;如果任意对应字段不等且非空,则这两行不等;否则结果是未知(NULL)。如果每一行的结果都是不等或NULL ,并且至少有一个NULL ,则IN的结果是NULL 。 + + - ANY/SOME +![在这里插入图片描述](https://img-blog.csdnimg.cn/cb075d4be9fd49bd9c55f275296f9357.png) + +右边是一个圆括弧括起来的子查询,它必须只返回一个字段。左边表达式使用operator对子查询结果的每一行进行一次计算和比较,其结果必须是布尔值。如果至少获得一个真值,则ANY结果为“真”。如果全部获得假值,则结果是“假”(包括子查询没有返回任何行的情况)。SOME是ANY的同义词。IN与ANY可以等效替换 。 + + - ALL +![在这里插入图片描述](https://img-blog.csdnimg.cn/d2c6bbd3f4694eceb59a2dd198298c2b.png) + +右边是一个圆括弧括起来的子查询,它必须只返回一个字段。左边表达式使用operator对子查询结果的每一行进行一次计算和比较,其结果必须是布尔值。如果全部获得真值,ALL结果为”真”(包括子查询没有返回任何行的情况)。如果至少获得一个假值,则结果是”假”。 +# 2.数组表达式 +## lN +_expression \*IN ***(value [, …])_ + +右侧括号中的是一个表达式列表。左侧表达式的结果与表达式列表的内容进行比较。如果列表中的内容符合左侧表达式的结果,则IN的结果为true。如果没有相符的结果,则IN的结果为false。 + +> 说明: +> 如果表达式结果为null,或者表达式列表不符合表达式的条件且右侧表达式列表返回结果至少一处为空,则IN的返回结果为null,而不是false。这样的处理方式和SQL返回空值的布尔组合规则是一致的。 +## NOT IN +_expression NOT IN (value [, …])_ + +右侧括号中的是一个表达式列表。左侧表达式的结果与表达式列表的内容进行比较。如果在列表中的内容没有符合左侧表达式结果的内容,则NOT IN的结果为true。如果有符合的内容,则NOT IN的结果为false。 + + +> 如果查询语句返回结果为空,或者表达式列表不符合表达式的条件且右侧表达式列表返回结果至少一处为空,则NOT +> IN的返回结果为null,而不是false。这样的处理方式和SQL返回空值的布尔组合规则是一致的。 +> 提示:在所有情况下X NOT IN Y等价于NOT(X IN Y)。 + +## ANY/SOME (array) +_expression operator \*ANY ***(array expression)_ + +_expression operator \*SOME ***(array expression)_ + +右侧括号中的是一个数组表达式,它必须产生一个数组值。左侧表达式的结果使用操作符对数组表达式的每一行结果都进行计算和比较,比较结果必须是布尔值。 + + - 如果对比结果至少获取一个真值,则ANY的结果为true。 + - 如果对比结果没有真值,则ANY的结果为false。 + + +> 如果结果没有真值,并且数组表达式生成至少一个值为null,则ANY的值为NULL,而不是false。这样的处理方式和SQL返回空值的布尔组合规则是一致的。 + +SOME是ANY的同义词。 +## ALL (array) +_expression operator \*ALL ***(array expression)_ + +右侧括号中的是一个数组表达式,它必须产生一个数组值。左侧表达式的结果使用操作符对数组表达式的每一行结果都进行计算和比较,比较结果必须是布尔值。 + + - 如果所有的比较结果都为真值(包括数组不含任何元素的情况),则ALL的结果为true。 + - 如果存在一个或多个比较结果为假值,则ALL的结果为false。 + +如果数组表达式产生一个NULL数组,则ALL的结果为NULL。如果左边表达式的值为NULL ,则ALL的结果通常也为NULL(某些不严格的比较操作符可能得到不同的结果)。另外,如果右边的数组表达式中包含null元素并且比较结果没有假值,则ALL的结果将是NULL(某些不严格的比较操作符可能得到不同的结果), 而不是真。这样的处理方式和SQL返回空值的布尔组合规则是一致的。 + +# 3.行表达式 +语法: + +row_constructor operator row_constructor + +两边都是一个行构造器,两行值必须具有相同数目的字段,每一行都进行比较,行比较允许使用=,<>,<,<=,>=等操作符,或其中一个相似的语义符。 + +\=<>和别的操作符使用略有不同。如果两行值的所有字段都是非空并且相等,则认为两行是相等的;如果两行值的任意字段为非空并且不相等,则认为两行是不相等的;否则比较结果是未知的(null)。 + +对于<,<=,>,> =的情况下,行中元素从左到右依次比较,直到遇到一对不相等的元素或者一对为空的元素。如果这对元素中存在至少一个null值,则比较结果是未知的(null),否则这对元素的比较结果为最终的结果。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217-\347\256\200\345\215\225\350\241\250\350\276\276\345\274\217\344\270\216\346\235\241\344\273\266\350\241\250\350\276\276\345\274\217.md" "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217-\347\256\200\345\215\225\350\241\250\350\276\276\345\274\217\344\270\216\346\235\241\344\273\266\350\241\250\350\276\276\345\274\217.md" new file mode 100644 index 0000000000000000000000000000000000000000..7fdde747e4be5093f218d947fe4f834c08545044 --- /dev/null +++ "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217-\347\256\200\345\215\225\350\241\250\350\276\276\345\274\217\344\270\216\346\235\241\344\273\266\350\241\250\350\276\276\345\274\217.md" @@ -0,0 +1,71 @@ +# 1.简单表达式 +### 逻辑操作符 +常用的逻辑操作符有AND、OR和NOT,他们的运算结果有三个值,分别为TRUE、FALSE和NULL,其中NULL代表未知。他们运算优先级顺序为:NOT>AND>OR。 +![在这里插入图片描述](https://img-blog.csdnimg.cn/b00416ac399b467687c5b64a2e5a5c61.png) + +### 比较表达式 +常用的比较操作符,请参见[操作符](https://www.bookstack.cn/read/opengauss-1.0-zh/2402b799c38ff067.md)。 + +除比较操作符外,还可以使用以下句式结构: + +``` + - BETWEEN操作符 + + a BETWEEN x_ _ AND y等效于a >= x AND a <= y + a NOT BETWEEN_ _ x AND y等效于a < x OR a > y + +- 检查一个值是不是null,可使用: + + expression_ _IS NULL + expression IS NOT NULL + + 或者与之等价的句式结构,但不是标准的: + + expression_ _ ISNULL + expression NOTNULL + +``` +须知:不要写expression=NULL或expression<>(!=)NULL,因为NULL代表一个未知的值,不能通过该表达式判断两个未知值是否相等。 +# 2.条件表达式 +在执行SQL语句时,可通过条件表达式筛选出符合条件的数据。 + +条件表达式主要有以下几种: + + - CASE + + CASE表达式是条件表达式,类似于其他编程语言中的CASE语句。如图: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/2d83cca13d684bfca25f5b045c2a0fb2.png) +CASE子句可以用于合法的表达式中。condition是一个返回BOOLEAN数据类型的表达式: + - 如果结果为真,CASE表达式的结果就是符合该条件所对应的result。 + - 如果结果为假,则以相同方式处理随后的WHEN或ELSE子句。 + - 如果各WHEN condition都不为真,表达式的结果就是在ELSE子句执行的result。如果省略了ELSE子句且没有匹配的条件,结果为NULL。 +DECODE +![在这里插入图片描述](https://img-blog.csdnimg.cn/fa1a859ff0634ead8b29e15867ecdf20.png) +将表达式base_expr与后面的每个compare(n) 进行比较,如果匹配返回相应的value(n)。如果没有发生匹配,则返回default。 + + - COALESCE + +![在这里插入图片描述](https://img-blog.csdnimg.cn/b4ba3869661a44bd99d21719c7dbcc5f.png) +COALESCE返回它的第一个非NULL的参数值。如果参数都为NULL,则返回NULL。它常用于在显示数据时用缺省值替换NULL。和CASE表达式一样,COALESCE只计算用来判断结果的参数,即在第一个非空参数右边的参数不会被计算。 + + - NULLIF +![在这里插入图片描述](https://img-blog.csdnimg.cn/0c9133f62b194feca8cff209f6376322.png) + +只有当value1和value2相等时,NULLIF才返回NULL。否则它返回value1。 + + + - GREATEST(最大值),LEAST(最小值) + +![在这里插入图片描述](https://img-blog.csdnimg.cn/0b845b2290ad42f4b585344c0ea76760.png) +从一个任意数字表达式的列表里选取最大的数值。 +![在这里插入图片描述](https://img-blog.csdnimg.cn/2582dc302a344e2e823fd24d4e29d00a.png) +从一个任意数字表达式的列表里选取最小的数值。 + +以上的数字表达式必须都可以转换成一个普通的数据类型,该数据类型将是结果类型。 + +列表中的NULL值将被忽略。只有所有表达式的结果都是NULL的时候,结果才是NULL。 + + - NVL + ![在这里插入图片描述](https://img-blog.csdnimg.cn/415b91a4f08742998bf00cd088c4e304.png) +如果value1为NULL则返回value2,如果value1非NULL,则返回value1。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\200\273\347\273\223.md" "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\200\273\347\273\223.md" new file mode 100644 index 0000000000000000000000000000000000000000..e1cb98a6da81b470653587399b6e909a6c0f15ba --- /dev/null +++ "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\200\273\347\273\223.md" @@ -0,0 +1,487 @@ +# 表达式篇总结 +openguss数据库表达式求值的对应代码源文件是“execQual.cpp”。openguss在处理SQL语句中的函数调用、表达式和条件表达式时需要表达式计算。表达式分为简单表达式、条件表达式、子查询表达式、数组表达式、行表达式。 +## 表达式计算 +表达式的表示方式类似于查询计划树中的计划节点:生成表达式计划,并计算每个表达式节点。表达式继承层次结构中的公共根类是Expr节点,所有其他表达式节点都继承自它。表达式状态的公共根类是ExprState类,它记录表达式的类型和实现表达式节点的函数指针。表达式内存上下文类是ExprContext。ExprContext在计划树节点中充当Estate。表达式求值期间使用的参数和表达式使用的内存上下文存储在此结构中。 +表达式计算对应的主要结构体代码如下: +```cpp +typedef struct Expr +{ + NodeTag type; /*表达式节点类型*/ +} Expr; + +struct ExprState +{ + NodeTag type; + Expr* expr; /*关联的表达式节点*/ + ExprStateEvalFunc evalfunc; /*表达式运算的函数指针*/ + VectorExprFun vecExprFun; + exprFakeCodeGenSig exprCodeGen; /*运行LLVM汇编函数的指针*/ + ScalarVector tmpVector; + Oid resultType; +}; +``` +表达式计算的过程分为3个部分:初始化、执行和清理。初始化的过程使用统一接口ExecInitExpr,根据表达式的类型选择不同的处理方式,生成表达式节点树。执行过程使用统一接口宏ExecEvalExpr,执行过程类似于计划节点的递归方式。表达式又分为简单表达式、条件表达式、子查询表达式、数组表达式、行表达式。这些都将在以后依次介绍。 +### 1.初始化阶段 +ExecInitExpr函数的作用是在执行的初始化阶段,准备要执行的表达式树。根据传入的表达式node tree,来创建并返回ExprState tree。在真正的执行阶段会根据ExprState tree中记录的处理函数,递归地执行每个节点。ExecInitExpr函数的核心代码如下图: +![在这里插入图片描述](https://img-blog.csdnimg.cn/15e2be9629374ee7a5f7d9e87b1d4f1c.png) + + +```cpp +if (node == NULL) { /* 判断输入是否为空 */ + gstrace_exit(GS_TRC_ID_ExecInitExpr); + return NULL;} +switch (nodeTag(node)) { /* 根据节点类型初始化节点内容 */ + case T_Var: + case T_Const: + case T_Param: + switch (((Param*)node)->paramkind) { + case PARAM_EXEC: + case PARAM_EXTERN: + default: + case T_CoerceToDomainValue: + case T_CaseTestExpr: + case T_Aggref: + case T_GroupingFunc: + case T_GroupingId: + ...... +default:} +return state; /* 返回表达式节点树 */ + +``` +ExecInitExpr函数主要执行流程如下。 + + 1. 判断输入的node节点是否为空,若为空,则直接返回NULL,表示没有表达式限制。 + 2. 根据输入的node节点的类型初始化变量evalfunc即node节点对应的执行函数,若节点存在参数或者表达式,则递归调用ExecInitExpr函数,最后生成ExprState tree。 + 3. 返回ExprState tree,在执行表达式的时候会根据ExprState tree来递归执行。 + +ExecInitExpr函数流程如图所示。 +![在这里插入图片描述](https://img-blog.csdnimg.cn/806807c4bbea4c4f9616136f57a9b7b0.png) +### 2.执行阶段 +执行阶段主要是根据宏定义ExecEvalExpr递归调用执行函数。在计算时的核心函数包括ExecMakeFunctionResult和ExecMakeFunctionResultNoSets,通过这两个函数计算出表达式的结果并返回。其他的表达式计算函数还包括ExecEvalFunc、ExecEvalOper、ExecEvalScalarVar、ExecEvalConst、ExecQual、ExecProject等,这些函数分别对应不同的表达式的类型或者参数类型,通过不同的逻辑来处理获取的计算结果。 +执行过程就是上层函数调用下层函数。首先下层函数根据参数类型获取相应的数据,然后上层函数通过处理数据得到最后的结果,最后根据表达式逻辑返回结果。 +通过一个简单的SQL语句介绍一下表达式计算的函数调用过程,每种SQL语句的执行流程不完全一致,此示例仅供参考。例句:“SELECT * FROM s WHERE s.a<3 or s.b<3;”。具体流程如下。 +(1) 根据表达式“s.a<3 or s.b<3”确认第一步调用ExecQual函数。 +(2) 由于本次表达式是or语句,所以需要将表达式传入到ExecEvalOr函数计算,在ExecEvalOr函数中采用for循环依次对子表达式“s.a<3”和“s.b<3”计算,将子表达式传入到下一层函数中。 +(3) ExecEvalOper函数根据子表达式的返回值是否为set集来调用下一层函数,计算子表达式的结果。 +(4) ExecMakeFunctionResultNoSets函数中获取子表达式中的参数的值,“s.a”和“3”分别通过ExecEvalScalarVar函数和ExecEvalConst函数来获取,获取到参数之后计算表达式结果,若s.a<3本次计算返回true,否则返回false,并依次向上层返回结果。 +![函数调用流程图如图7-13所示。](https://img-blog.csdnimg.cn/4a98474f35984a6fb2ebf214cfe5d2bc.png) + +执行阶段所有函数都共享此调用约定,相关代码如下: + +> 输入: +> expression:需要计算的表达式状态树。 +> econtext:评估上下文信息。 +> 输出: +> return value:Datum类型的返回值。 +> *isNull:如果结果为NULL,则设置为TRUE(实际返回值无意义);如果结果非空,则设置为FALSE。 +> *isDone:设置为set-result状态的指标。 + +只能接受单例(非集合)结果的调用方应该传递isDone为NULL,如果表达式计算得到集合结果(set-result),则返回错误将通过ereport报告。如果调用者传递的isDone指针不为空,需要将*isDone设置为以下3种状态之一: + 1. ExprSingleResult 返回单例结果(非集合)。 + 2. ExprMultipleResult 返回值是集合的一个元素。 + 3. ExprEndResult 集合中没有其他元素。 + +当返回ExprMultipleResult时,调用者应该重复调用并执行ExecEvalExpr函数,直到返回ExprEndResult。 +下面将依次详细介绍每个函数的功能、核心代码和执行流程。 + +```cpp +--ExecMakeFunctionResultNoSets +表达式计算(非集合) + +--ExecMakeFunctionResult +表达式计算(集合) + +--ExecEvalFunc/ExecEvalOper +调用表达式计算函数 + +--ExecQual +检查条件表达式 + +--ExecEvalOr +处理or表达式 + +--ExecTargetList +计算targetlist中的所有表达式 + +--ExecProject +计算投影信息 + +--ExecEvalParamExec +获取Exec类型参数 + +--ExecEvalParamExtern +获取Extern类型参数 +``` + +ExecMakeFunctionResult函数和ExecMakeFunctionResultNoS函数是表达式计算的核心函数,主要作用是通过获取表达式的参数来计算出表达式结果。ExecMakeFunctionResultNoSets函数是ExecMakeFunctionResult函数的简化版,只能处理返回值是非集合情况。 +### ExecMakeFunctionResult +ExecMakeFunctionResult函数核心代码如下: +![在这里插入图片描述](https://img-blog.csdnimg.cn/445fdf0bfb0f4659a7f4b0ab69c2668a.png) +```cpp +fcinfo = &fcache->fcinfo_data; /* 声明fcinfo */ +InitFunctionCallInfoArgs(*fcinfo, list_length(fcache->args), 1); /*初始化fcinfo */ +econtext->is_cursor = false; + foreach (arg, fcache->args) { /* 遍历获取参数值 */ + ExprState* argstate = (ExprState*)lfirst(arg); + fcinfo->argTypes[i] = argstate->resultType; + fcinfo->arg[i] = ExecEvalExpr(argstate, econtext, &fcinfo->argnull[i], NULL); +if (fcache->func.fn_strict) /* 判断参数是否存在空值 */ +…… +result = FunctionCallInvoke(fcinfo); /* 计算表达式结果 */ +return result; + +``` +### ExecMakeFunctionResultNoSets +ExecMakeFunctionResultNoSets函数的执行流程如下。 +(1) 声明fcinfo来存储表达式需要的参数信息,通过InitFunctionCallInfoArgs函数初始化fcinfo中的字段。 +(2) 遍历表达式中的参数args,通过ExecEvalExpr宏调用接口获取每一个参数的值,存储到“fcinfo->arg[i]”中。 +(3) 根据func.fn_strict函数来判断是否需要检查参数空值情况。如果不需要检查,则通过“FunctionCalllv-oke”宏将参数传入表达式并计算出表达式的结果。否则进行判空处理,若存在空值则直接返回空,若不存在空值则通过FunctionCalllvoke宏计算表达式结果。 +(4) 返回计算结果。 + +![流程如图7-14所示。 +流程如图7-14所示。](https://img-blog.csdnimg.cn/9a5fa9a7c90c45e7b2e8338efa60b848.png) + + + + 1. 判断funcResultStore是否存在,如果存在则从中获取结果返回(注:如果下文(3)中的模式是SFRM_Materialize,则会直接跳到此处)。 + 2. 计算出参数值存入到fcinfo中。 + 3. 把参数传入到表达式函数中计算表达式,首先判断参数args是否存在空,然后判断返回集合的函数的返回模式,SFRM_ValuePerCall模式是每次调用返回一个值,SFRM_Materialize模式是在Tuplestore中实例化的结果集。 + 4. 根据不同的模式进行计算并返回结果。 + + ![](https://img-blog.csdnimg.cn/45fc6777516b4d4fbfb1515a343508b4.png) + +### ExecEvalFunc +ExecEvalFunc和ExecEvalOper这两个函数的功能类似。通过调用结果处理函数来获取结果。如果函数本身或者它的任何输入参数都可以返回一个集合,那么就会调ExecMakeFunctionResult函数来计算结果,否则调用ExecMakeFunctionResultNoSets函数来计算结果。核心代码如下: + +```cpp +init_fcache(func->funcid,func->inputcollid,fcache, econtext->ecxt_per_query_memory, true); /* 初始化fcache */ +if (fcache->func.fn_retset) { /* 判断返回结果类型 */ + …… +return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); +} else if (expression_returns_set((Node*)func->args)) { +…… +return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); +} else { +…… +return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone); +} + +``` +ExecEvalFunc函数的执行流程如下。 +(1) 是通过init_fcache函数初始化FuncExprState节点,包括初始化参数、内存管理等等。 +(2) 根据FuncExprState函数中的数据判断返回结果是否为set类型,并调用相应的函数计算结果。 + +### ExecQual +ExecQual函数的作用是检查slot结果是否满足表达式中的子表达式,如果子表达式为false,则返回false否则返回true,表示该结果符合预期,需要输出。核心代码如下: + +```cpp +foreach (l, qual) { /* 遍历qual中的子表达式并计算 */ +expr_value = ExecEvalExpr(clause, econtext, &isNull, NULL); +if (isNull) { /* 判断计算结果 */ +if (resultForNull == false) { +result = false; +break; +} + } else { + if (!DatumGetBool(expr_value)) { + result = false; + …… + return result; /* 返回结果是否满足表达式 */ + +``` +ExecQual函数的主要执行流程如下。 +(1) 遍历qual中的子表达式,根据ExecEvalExpr函数计算结果是否满足该子表达式,若满足则expr_value为1,否则为0。 +(2) 判断结果是否为空,若为空,则根据resultForNull参数得到返回值信息。若不为空,则根据expr_value判断返回true或者false。 +(3) 返回result。 + +### ExecEvalOr +ExecEvalOr函数的作用是计算通过or连接的bool表达式(布尔表达式,最终只有true(真)和false(假)两个取值),检查slot结果是否满足表达式中的or表达式。如果结果符合or表达式中的任何一个子表达式,则直接返回true,否则返回false。如果获取的结果为null,则记录isNull为true。核心代码如下: + +```cpp +foreach (clause, clauses) { /* 遍历子表达式 */ + ExprState* clausestate = (ExprState*)lfirst(clause); + Datum clause_value; + clause_value = ExecEvalExpr(clausestate, econtext, isNull, NULL); /* 执行表达式 */ + /* 如果得到不空且ture的结果,直接返回结果 */ +if (*isNull) +/* 记录存在空值 */ + AnyNull = true; + else if (DatumGetBool(clause_value)) +/* 一次结果为true就返回 */ + return clause_value; /* 返回执行结果 */ + } +*isNull = AnyNull; +return BoolGetDatum(false); + +``` +ExecEvalOr函数主要执行流程如下。 +(1) 遍历子表达式clauses。 +(2) 通过ExecEvalExpr函数来调用clause中的表达式计算函数,计算出结果。 +(3) 对结果进行判断,or表达式中若有一个结果满足条件,就会跳出循环直接返回。 + +### ExecTargetList +ExecTargetList函数的作用是根据给定的表达式上下文计算targetlist中的所有表达式,将计算结果存储到元组中。主要结构体代码如下: + +```cpp +typedef struct GenericExprState { + ExprState xprstate; + ExprState* arg; /*子节点的状态*/ +} GenericExprState; +typedef struct TargetEntry { + Expr xpr; + Expr* expr; /*要计算的表达式*/ + AttrNumber resno; /*属性号*/ + char* resname; /*列的名称*/ + Index ressortgroupref; /*如果被sort/group子句引用,则为非零*/ + Oid resorigtbl; /*列的源表的OID */ + AttrNumber resorigcol; /*源表中的列号*/ + bool resjunk; /*设置为true可从最终目标列表中删除该属性*/ +} TargetEntry; + +``` + +ExecTargetList函数主要执行流程如下。 +(1) 遍历targetlist中的表达式。 +(2) 计算表达式结果。 +(3) 判断结果中itemIsDone[resind]参数并生成最后的元组。 + + + +### ExecProject +ExecProject函数的作用是进行投影操作,投影操作是一种属性过滤过程,该操作将对元组的属性进行精简,把在上层计划节点中不需要用的属性从元组中去掉,从而构造一个精简版的元组。投影操作中被保留下来的那些属性被称为投影属性。主要结构体代码如下: + +```cpp +typedef struct ProjectionInfo { + NodeTag type; + List* pi_targetlist; /*目标列表*/ + ExprContext* pi_exprContext; /*内存上下文*/ + TupleTableSlot* pi_slot; /*投影结果*/ + ExprDoneCond* pi_itemIsDone; /*ExecProject的工作区数组*/ + bool pi_directMap; + int pi_numSimpleVars; /*在原始tlist(查询目标列表)中找到的简单变量数*/ + int* pi_varSlotOffsets; /*指示变量来自哪个slot(槽位)的数组*/ + int* pi_varNumbers; /*包含变量的输入属性数的数组*/ + int* pi_varOutputCols; /*包含变量的输出属性数的数组*/ + int pi_lastInnerVar; /*内部参数*/ + int pi_lastOuterVar; /*外部参数*/ + int pi_lastScanVar; /*扫描参数*/ + List* pi_acessedVarNumbers; + List* pi_sysAttrList; + List* pi_lateAceessVarNumbers; + List* pi_maxOrmin; /*列表优化,指示获取此列的最大值还是最小值*/ + List* pi_PackTCopyVars; /*记录需要移动的列*/ + List* pi_PackLateAccessVarNumbers; /*记录cstore(列存储)扫描中移动的内容的列*/ + bool pi_const; + VectorBatch* pi_batch; + vectarget_func jitted_vectarget; /* LLVM函数指针*/ + VectorBatch* pi_setFuncBatch; +} ProjectionInfo; + +``` +ExecProject函数的主要执行流程如下。 +(1) 取ProjectionInfo需要投影的信息。按照执行的偏移获取原属性所在的元组,通过偏移量获取该属性,并通过目标属性的序号找到对应的新元组属性位置进行赋值。 +(2) 对pi_targetlist进行运算,将结果赋值给对应元组中的属性。 +(3)产生一个行记录结果,对slot做标记处理,slot包含一个有效的虚拟元组。 + +### ExecEvalParamExec +ExecEvalParamExec函数的作用是获取并返回PARAM_EXEC类型的参数。PARAM_EXEC参数是指内部执行器参数,是需要执行子计划来获取的结果,最后需要将结果返回到上层计划中。核心代码如下: + +```cpp +prm = &(econtext->ecxt_param_exec_vals[thisParamId]); /* 获取econtext中参数 */ +if (prm->execPlan != NULL) { /* 判断是否需要生成参数 */ + /* 参数还未计算执行此函数*/ + ExecSetParamPlan((SubPlanState*)prm->execPlan, econtext); + /*参数计算完计划重置为空*/ + Assert(prm->execPlan == NULL); + prm->isConst = true; + prm->valueType = expression->paramtype; +} +*isNull = prm->isnull; +prm->isChanged = true; +return prm->value; /* 返回生成的参数 */ + +``` +ExecEvalParamExec函数的主要执行流程如下。 +(1) 获取econtext中的ecxt_param_exec_vals参数。 +(2) 判断子计划是否为空,若不为空则调用ExecSetParamPlan函数执行子计划获取结果,并把计划置为空,当再次执行此函数时,不需要重新执行计划,直接返回已经获取过结果。 +(3) 将结果prm->value返回。 + +### ExecEvalParamExtern +ExecEvalParamExtern函数的作用是获取并返回PARAM_EXTERN类型的参数。该参数是指外部传入参数,例如在PBE执行时,PREPARE的语句中的参数,在需要execute语句执行时传入。核心代码如下: + +```cpp +if (paramInfo && thisParamId > 0 && thisParamId <= paramInfo->numParams) {/* 判断参数 */ +ParamExternData* prm = ¶mInfo->params[thisParamId - 1]; + if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) /* 获取动态参数 */ + (*paramInfo->paramFetch)(paramInfo, thisParamId); + if (OidIsValid(prm->ptype)) { /*检查参数并返回 */ +if (prm->ptype != expression->paramtype) +ereport(……); + *isNull = prm->isnull; + if (econtext->is_cursor && prm->ptype == REFCURSOROID) { + CopyCursorInfoData(&econtext->cursor_data, &prm->cursor_data); + econtext->dno = thisParamId - 1; + } + return prm->value; + } +} + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("no value found for parameter %d", thisParamId))); + return (Datum)0; + +``` +ExecEvalParamExtern函数主要执行流程如下。 +(1) 判断PARAM_EXTERN类型的参数否存在,若存在则从ecxt_param_list_info中获取该参数,否则直接报错。 +(2) 判断参数是否是动态的,若是动态的则再次获取参数。 +(3) 判断参数类型是否符合要求,若符合要求直接返回该参数。 + +## 表达式分类 +### 1.简单表达式 +#### 逻辑操作符 +常用的逻辑操作符有AND、OR和NOT,他们的运算结果有三个值,分别为TRUE、FALSE和NULL,其中NULL代表未知。他们运算优先级顺序为:NOT>AND>OR。 +![在这里插入图片描述](https://img-blog.csdnimg.cn/b00416ac399b467687c5b64a2e5a5c61.png) + +#### 比较表达式 +常用的比较操作符,请参见[操作符](https://www.bookstack.cn/read/opengauss-1.0-zh/2402b799c38ff067.md)。 + +除比较操作符外,还可以使用以下句式结构: + +``` + - BETWEEN操作符 + + a BETWEEN x_ _ AND y等效于a >= x AND a <= y + a NOT BETWEEN_ _ x AND y等效于a < x OR a > y + +- 检查一个值是不是null,可使用: + + expression_ _IS NULL + expression IS NOT NULL + + 或者与之等价的句式结构,但不是标准的: + + expression_ _ ISNULL + expression NOTNULL + +``` +须知:不要写expression=NULL或expression<>(!=)NULL,因为NULL代表一个未知的值,不能通过该表达式判断两个未知值是否相等。 +### 2.条件表达式 +在执行SQL语句时,可通过条件表达式筛选出符合条件的数据。 + +条件表达式主要有以下几种: + + - CASE + + CASE表达式是条件表达式,类似于其他编程语言中的CASE语句。如图: + +![在这里插入图片描述](https://img-blog.csdnimg.cn/2d83cca13d684bfca25f5b045c2a0fb2.png) +CASE子句可以用于合法的表达式中。condition是一个返回BOOLEAN数据类型的表达式: + - 如果结果为真,CASE表达式的结果就是符合该条件所对应的result。 + - 如果结果为假,则以相同方式处理随后的WHEN或ELSE子句。 + - 如果各WHEN condition都不为真,表达式的结果就是在ELSE子句执行的result。如果省略了ELSE子句且没有匹配的条件,结果为NULL。 +DECODE +![在这里插入图片描述](https://img-blog.csdnimg.cn/fa1a859ff0634ead8b29e15867ecdf20.png) +将表达式base_expr与后面的每个compare(n) 进行比较,如果匹配返回相应的value(n)。如果没有发生匹配,则返回default。 + + - COALESCE + +![在这里插入图片描述](https://img-blog.csdnimg.cn/b4ba3869661a44bd99d21719c7dbcc5f.png) +COALESCE返回它的第一个非NULL的参数值。如果参数都为NULL,则返回NULL。它常用于在显示数据时用缺省值替换NULL。和CASE表达式一样,COALESCE只计算用来判断结果的参数,即在第一个非空参数右边的参数不会被计算。 + + - NULLIF +![在这里插入图片描述](https://img-blog.csdnimg.cn/0c9133f62b194feca8cff209f6376322.png) + +只有当value1和value2相等时,NULLIF才返回NULL。否则它返回value1。 + + + - GREATEST(最大值),LEAST(最小值) + +![在这里插入图片描述](https://img-blog.csdnimg.cn/0b845b2290ad42f4b585344c0ea76760.png) +从一个任意数字表达式的列表里选取最大的数值。 +![在这里插入图片描述](https://img-blog.csdnimg.cn/2582dc302a344e2e823fd24d4e29d00a.png) +从一个任意数字表达式的列表里选取最小的数值。 + +以上的数字表达式必须都可以转换成一个普通的数据类型,该数据类型将是结果类型。 + +列表中的NULL值将被忽略。只有所有表达式的结果都是NULL的时候,结果才是NULL。 + + - NVL + ![在这里插入图片描述](https://img-blog.csdnimg.cn/415b91a4f08742998bf00cd088c4e304.png) +如果value1为NULL则返回value2,如果value1非NULL,则返回value1。 +### 3.子查询表达式 + +子查询表达式主要有以下几种: + + - EXISTS/NOT EXISTS +![在这里插入图片描述](https://img-blog.csdnimg.cn/b917de94c55841d99078af508afa96b1.png) + +EXISTS的参数是一个任意的SELECT语句,或者说子查询。系统对子查询进行运算以判断它是否返回行。如果它至少返回一行,则EXISTS结果就为”真”;如果子查询没有返回任何行, EXISTS的结果是”假”。 + +这个子查询通常只是运行到能判断它是否可以生成至少一行为止,而不是等到全部结束。 + + - IN/NOT IN +![在这里插入图片描述](https://img-blog.csdnimg.cn/4580ff19335741f6a83c643ae77538c5.png) + +右边是一个圆括弧括起来的子查询,它必须只返回一个字段。左边表达式对子查询结果的每一行进行一次计算和比较。如果找到任何相等的子查询行,则IN结果为”真”。如果没有找到任何相等行,则结果为”假”(包括子查询没有返回任何行的情况)。 + +表达式或子查询行里的NULL遵照SQL处理布尔值和NULL组合时的规则。如果两个行对应的字段都相等且非空,则这两行相等;如果任意对应字段不等且非空,则这两行不等;否则结果是未知(NULL)。如果每一行的结果都是不等或NULL ,并且至少有一个NULL ,则IN的结果是NULL 。 + + - ANY/SOME +![在这里插入图片描述](https://img-blog.csdnimg.cn/cb075d4be9fd49bd9c55f275296f9357.png) + +右边是一个圆括弧括起来的子查询,它必须只返回一个字段。左边表达式使用operator对子查询结果的每一行进行一次计算和比较,其结果必须是布尔值。如果至少获得一个真值,则ANY结果为“真”。如果全部获得假值,则结果是“假”(包括子查询没有返回任何行的情况)。SOME是ANY的同义词。IN与ANY可以等效替换 。 + + - ALL +![在这里插入图片描述](https://img-blog.csdnimg.cn/d2c6bbd3f4694eceb59a2dd198298c2b.png) + +右边是一个圆括弧括起来的子查询,它必须只返回一个字段。左边表达式使用operator对子查询结果的每一行进行一次计算和比较,其结果必须是布尔值。如果全部获得真值,ALL结果为”真”(包括子查询没有返回任何行的情况)。如果至少获得一个假值,则结果是”假”。 +### 4.数组表达式 +##### lN +_expression \*IN ***(value [, …])_ + +右侧括号中的是一个表达式列表。左侧表达式的结果与表达式列表的内容进行比较。如果列表中的内容符合左侧表达式的结果,则IN的结果为true。如果没有相符的结果,则IN的结果为false。 + +> 说明: +> 如果表达式结果为null,或者表达式列表不符合表达式的条件且右侧表达式列表返回结果至少一处为空,则IN的返回结果为null,而不是false。这样的处理方式和SQL返回空值的布尔组合规则是一致的。 +#### NOT IN +_expression NOT IN (value [, …])_ + +右侧括号中的是一个表达式列表。左侧表达式的结果与表达式列表的内容进行比较。如果在列表中的内容没有符合左侧表达式结果的内容,则NOT IN的结果为true。如果有符合的内容,则NOT IN的结果为false。 + + +> 如果查询语句返回结果为空,或者表达式列表不符合表达式的条件且右侧表达式列表返回结果至少一处为空,则NOT +> IN的返回结果为null,而不是false。这样的处理方式和SQL返回空值的布尔组合规则是一致的。 +> 提示:在所有情况下X NOT IN Y等价于NOT(X IN Y)。 + +#### ANY/SOME (array) +_expression operator \*ANY ***(array expression)_ + +_expression operator \*SOME ***(array expression)_ + +右侧括号中的是一个数组表达式,它必须产生一个数组值。左侧表达式的结果使用操作符对数组表达式的每一行结果都进行计算和比较,比较结果必须是布尔值。 + + - 如果对比结果至少获取一个真值,则ANY的结果为true。 + - 如果对比结果没有真值,则ANY的结果为false。 + + +> 如果结果没有真值,并且数组表达式生成至少一个值为null,则ANY的值为NULL,而不是false。这样的处理方式和SQL返回空值的布尔组合规则是一致的。 + +SOME是ANY的同义词。 +#### ALL (array) +_expression operator \*ALL ***(array expression)_ + +右侧括号中的是一个数组表达式,它必须产生一个数组值。左侧表达式的结果使用操作符对数组表达式的每一行结果都进行计算和比较,比较结果必须是布尔值。 + + - 如果所有的比较结果都为真值(包括数组不含任何元素的情况),则ALL的结果为true。 + - 如果存在一个或多个比较结果为假值,则ALL的结果为false。 + +如果数组表达式产生一个NULL数组,则ALL的结果为NULL。如果左边表达式的值为NULL ,则ALL的结果通常也为NULL(某些不严格的比较操作符可能得到不同的结果)。另外,如果右边的数组表达式中包含null元素并且比较结果没有假值,则ALL的结果将是NULL(某些不严格的比较操作符可能得到不同的结果), 而不是真。这样的处理方式和SQL返回空值的布尔组合规则是一致的。 + +### 3.行表达式 +语法: + +row_constructor operator row_constructor + +两边都是一个行构造器,两行值必须具有相同数目的字段,每一行都进行比较,行比较允许使用=,<>,<,<=,>=等操作符,或其中一个相似的语义符。 + +\=<>和别的操作符使用略有不同。如果两行值的所有字段都是非空并且相等,则认为两行是相等的;如果两行值的任意字段为非空并且不相等,则认为两行是不相等的;否则比较结果是未知的(null)。 + +对于<,<=,>,> =的情况下,行中元素从左到右依次比较,直到遇到一对不相等的元素或者一对为空的元素。如果这对元素中存在至少一个null值,则比较结果是未知的(null),否则这对元素的比较结果为最终的结果。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\272\220\347\240\201\350\247\243\350\257\273\357\274\210\344\270\200\357\274\211.md" "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\272\220\347\240\201\350\247\243\350\257\273\357\274\210\344\270\200\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git "a/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\272\220\347\240\201\350\247\243\350\257\273\357\274\210\344\270\211\357\274\211.md" "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\272\220\347\240\201\350\247\243\350\257\273\357\274\210\344\270\211\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..af371946c9c892f4c231f2371a107a90d368fa50 --- /dev/null +++ "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\272\220\347\240\201\350\247\243\350\257\273\357\274\210\344\270\211\357\274\211.md" @@ -0,0 +1,94 @@ +### ExecEvalFunc +ExecEvalFunc和ExecEvalOper这两个函数的功能类似。通过调用结果处理函数来获取结果。如果函数本身或者它的任何输入参数都可以返回一个集合,那么就会调ExecMakeFunctionResult函数来计算结果,否则调用ExecMakeFunctionResultNoSets函数来计算结果。核心代码如下: + +```cpp +init_fcache(func->funcid,func->inputcollid,fcache, econtext->ecxt_per_query_memory, true); /* 初始化fcache */ +if (fcache->func.fn_retset) { /* 判断返回结果类型 */ + …… +return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); +} else if (expression_returns_set((Node*)func->args)) { +…… +return ExecMakeFunctionResult(fcache, econtext, isNull, isDone); +} else { +…… +return ExecMakeFunctionResultNoSets(fcache, econtext, isNull, isDone); +} + +``` +ExecEvalFunc函数的执行流程如下。 +(1) 是通过init_fcache函数初始化FuncExprState节点,包括初始化参数、内存管理等等。 +(2) 根据FuncExprState函数中的数据判断返回结果是否为set类型,并调用相应的函数计算结果。 + +### ExecQual +ExecQual函数的作用是检查slot结果是否满足表达式中的子表达式,如果子表达式为false,则返回false否则返回true,表示该结果符合预期,需要输出。核心代码如下: + +```cpp +foreach (l, qual) { /* 遍历qual中的子表达式并计算 */ +expr_value = ExecEvalExpr(clause, econtext, &isNull, NULL); +if (isNull) { /* 判断计算结果 */ +if (resultForNull == false) { +result = false; +break; +} + } else { + if (!DatumGetBool(expr_value)) { + result = false; + …… + return result; /* 返回结果是否满足表达式 */ + +``` +ExecQual函数的主要执行流程如下。 +(1) 遍历qual中的子表达式,根据ExecEvalExpr函数计算结果是否满足该子表达式,若满足则expr_value为1,否则为0。 +(2) 判断结果是否为空,若为空,则根据resultForNull参数得到返回值信息。若不为空,则根据expr_value判断返回true或者false。 +(3) 返回result。 + +### ExecEvalOr +ExecEvalOr函数的作用是计算通过or连接的bool表达式(布尔表达式,最终只有true(真)和false(假)两个取值),检查slot结果是否满足表达式中的or表达式。如果结果符合or表达式中的任何一个子表达式,则直接返回true,否则返回false。如果获取的结果为null,则记录isNull为true。核心代码如下: + +```cpp +foreach (clause, clauses) { /* 遍历子表达式 */ + ExprState* clausestate = (ExprState*)lfirst(clause); + Datum clause_value; + clause_value = ExecEvalExpr(clausestate, econtext, isNull, NULL); /* 执行表达式 */ + /* 如果得到不空且ture的结果,直接返回结果 */ +if (*isNull) +/* 记录存在空值 */ + AnyNull = true; + else if (DatumGetBool(clause_value)) +/* 一次结果为true就返回 */ + return clause_value; /* 返回执行结果 */ + } +*isNull = AnyNull; +return BoolGetDatum(false); + +``` +ExecEvalOr函数主要执行流程如下。 +(1) 遍历子表达式clauses。 +(2) 通过ExecEvalExpr函数来调用clause中的表达式计算函数,计算出结果。 +(3) 对结果进行判断,or表达式中若有一个结果满足条件,就会跳出循环直接返回。 + +### ExecTargetList +ExecTargetList函数的作用是根据给定的表达式上下文计算targetlist中的所有表达式,将计算结果存储到元组中。主要结构体代码如下: + +```cpp +typedef struct GenericExprState { + ExprState xprstate; + ExprState* arg; /*子节点的状态*/ +} GenericExprState; +typedef struct TargetEntry { + Expr xpr; + Expr* expr; /*要计算的表达式*/ + AttrNumber resno; /*属性号*/ + char* resname; /*列的名称*/ + Index ressortgroupref; /*如果被sort/group子句引用,则为非零*/ + Oid resorigtbl; /*列的源表的OID */ + AttrNumber resorigcol; /*源表中的列号*/ + bool resjunk; /*设置为true可从最终目标列表中删除该属性*/ +} TargetEntry; + +``` + +ExecTargetList函数主要执行流程如下。 +(1) 遍历targetlist中的表达式。 +(2) 计算表达式结果。 +(3) 判断结果中itemIsDone[resind]参数并生成最后的元组。 \ No newline at end of file diff --git "a/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\272\220\347\240\201\350\247\243\350\257\273\357\274\210\344\272\214\357\274\211.md" "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\272\220\347\240\201\350\247\243\350\257\273\357\274\210\344\272\214\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..dfe2badcafc57d186c888db0a1f92cf0954956af --- /dev/null +++ "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\272\220\347\240\201\350\247\243\350\257\273\357\274\210\344\272\214\357\274\211.md" @@ -0,0 +1,94 @@ +### 2.执行阶段 +执行阶段主要是根据宏定义ExecEvalExpr递归调用执行函数。在计算时的核心函数包括ExecMakeFunctionResult和ExecMakeFunctionResultNoSets,通过这两个函数计算出表达式的结果并返回。其他的表达式计算函数还包括ExecEvalFunc、ExecEvalOper、ExecEvalScalarVar、ExecEvalConst、ExecQual、ExecProject等,这些函数分别对应不同的表达式的类型或者参数类型,通过不同的逻辑来处理获取的计算结果。 +执行过程就是上层函数调用下层函数。首先下层函数根据参数类型获取相应的数据,然后上层函数通过处理数据得到最后的结果,最后根据表达式逻辑返回结果。 +通过一个简单的SQL语句介绍一下表达式计算的函数调用过程,每种SQL语句的执行流程不完全一致,此示例仅供参考。例句:“SELECT * FROM s WHERE s.a<3 or s.b<3;”。具体流程如下。 +(1) 根据表达式“s.a<3 or s.b<3”确认第一步调用ExecQual函数。 +(2) 由于本次表达式是or语句,所以需要将表达式传入到ExecEvalOr函数计算,在ExecEvalOr函数中采用for循环依次对子表达式“s.a<3”和“s.b<3”计算,将子表达式传入到下一层函数中。 +(3) ExecEvalOper函数根据子表达式的返回值是否为set集来调用下一层函数,计算子表达式的结果。 +(4) ExecMakeFunctionResultNoSets函数中获取子表达式中的参数的值,“s.a”和“3”分别通过ExecEvalScalarVar函数和ExecEvalConst函数来获取,获取到参数之后计算表达式结果,若s.a<3本次计算返回true,否则返回false,并依次向上层返回结果。 +![函数调用流程图如图7-13所示。](https://img-blog.csdnimg.cn/4a98474f35984a6fb2ebf214cfe5d2bc.png) + +执行阶段所有函数都共享此调用约定,相关代码如下: + +> 输入: +> expression:需要计算的表达式状态树。 +> econtext:评估上下文信息。 +> 输出: +> return value:Datum类型的返回值。 +> *isNull:如果结果为NULL,则设置为TRUE(实际返回值无意义);如果结果非空,则设置为FALSE。 +> *isDone:设置为set-result状态的指标。 + +只能接受单例(非集合)结果的调用方应该传递isDone为NULL,如果表达式计算得到集合结果(set-result),则返回错误将通过ereport报告。如果调用者传递的isDone指针不为空,需要将*isDone设置为以下3种状态之一: + 1. ExprSingleResult 返回单例结果(非集合)。 + 2. ExprMultipleResult 返回值是集合的一个元素。 + 3. ExprEndResult 集合中没有其他元素。 + +当返回ExprMultipleResult时,调用者应该重复调用并执行ExecEvalExpr函数,直到返回ExprEndResult。 +下面将依次详细介绍每个函数的功能、核心代码和执行流程。 + +```cpp +--ExecMakeFunctionResultNoSets +表达式计算(非集合) + +--ExecMakeFunctionResult +表达式计算(集合) + +--ExecEvalFunc/ExecEvalOper +调用表达式计算函数 + +--ExecQual +检查条件表达式 + +--ExecEvalOr +处理or表达式 + +--ExecTargetList +计算targetlist中的所有表达式 + +--ExecProject +计算投影信息 + +--ExecEvalParamExec +获取Exec类型参数 + +--ExecEvalParamExtern +获取Extern类型参数 +``` + +ExecMakeFunctionResult函数和ExecMakeFunctionResultNoS函数是表达式计算的核心函数,主要作用是通过获取表达式的参数来计算出表达式结果。ExecMakeFunctionResultNoSets函数是ExecMakeFunctionResult函数的简化版,只能处理返回值是非集合情况。 +### ExecMakeFunctionResult +ExecMakeFunctionResult函数核心代码如下: +![在这里插入图片描述](https://img-blog.csdnimg.cn/445fdf0bfb0f4659a7f4b0ab69c2668a.png) +```cpp +fcinfo = &fcache->fcinfo_data; /* 声明fcinfo */ +InitFunctionCallInfoArgs(*fcinfo, list_length(fcache->args), 1); /*初始化fcinfo */ +econtext->is_cursor = false; + foreach (arg, fcache->args) { /* 遍历获取参数值 */ + ExprState* argstate = (ExprState*)lfirst(arg); + fcinfo->argTypes[i] = argstate->resultType; + fcinfo->arg[i] = ExecEvalExpr(argstate, econtext, &fcinfo->argnull[i], NULL); +if (fcache->func.fn_strict) /* 判断参数是否存在空值 */ +…… +result = FunctionCallInvoke(fcinfo); /* 计算表达式结果 */ +return result; + +``` +### ExecMakeFunctionResultNoSets +ExecMakeFunctionResultNoSets函数的执行流程如下。 +(1) 声明fcinfo来存储表达式需要的参数信息,通过InitFunctionCallInfoArgs函数初始化fcinfo中的字段。 +(2) 遍历表达式中的参数args,通过ExecEvalExpr宏调用接口获取每一个参数的值,存储到“fcinfo->arg[i]”中。 +(3) 根据func.fn_strict函数来判断是否需要检查参数空值情况。如果不需要检查,则通过“FunctionCalllv-oke”宏将参数传入表达式并计算出表达式的结果。否则进行判空处理,若存在空值则直接返回空,若不存在空值则通过FunctionCalllvoke宏计算表达式结果。 +(4) 返回计算结果。 + +![流程如图7-14所示。 +流程如图7-14所示。](https://img-blog.csdnimg.cn/9a5fa9a7c90c45e7b2e8338efa60b848.png) + + + + 1. 判断funcResultStore是否存在,如果存在则从中获取结果返回(注:如果下文(3)中的模式是SFRM_Materialize,则会直接跳到此处)。 + 2. 计算出参数值存入到fcinfo中。 + 3. 把参数传入到表达式函数中计算表达式,首先判断参数args是否存在空,然后判断返回集合的函数的返回模式,SFRM_ValuePerCall模式是每次调用返回一个值,SFRM_Materialize模式是在Tuplestore中实例化的结果集。 + 4. 根据不同的模式进行计算并返回结果。 + + ![](https://img-blog.csdnimg.cn/45fc6777516b4d4fbfb1515a343508b4.png) + diff --git "a/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\272\220\347\240\201\350\247\243\350\257\273\357\274\210\345\233\233\357\274\211.md" "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\272\220\347\240\201\350\247\243\350\257\273\357\274\210\345\233\233\357\274\211.md" new file mode 100644 index 0000000000000000000000000000000000000000..68d6e1d62ddffa8ec2240fb6150b0b027550a49b --- /dev/null +++ "b/TestTasks/wang-luyang/\350\241\250\350\276\276\345\274\217\346\272\220\347\240\201\350\247\243\350\257\273\357\274\210\345\233\233\357\274\211.md" @@ -0,0 +1,89 @@ + + +### ExecProject +ExecProject函数的作用是进行投影操作,投影操作是一种属性过滤过程,该操作将对元组的属性进行精简,把在上层计划节点中不需要用的属性从元组中去掉,从而构造一个精简版的元组。投影操作中被保留下来的那些属性被称为投影属性。主要结构体代码如下: + +```cpp +typedef struct ProjectionInfo { + NodeTag type; + List* pi_targetlist; /*目标列表*/ + ExprContext* pi_exprContext; /*内存上下文*/ + TupleTableSlot* pi_slot; /*投影结果*/ + ExprDoneCond* pi_itemIsDone; /*ExecProject的工作区数组*/ + bool pi_directMap; + int pi_numSimpleVars; /*在原始tlist(查询目标列表)中找到的简单变量数*/ + int* pi_varSlotOffsets; /*指示变量来自哪个slot(槽位)的数组*/ + int* pi_varNumbers; /*包含变量的输入属性数的数组*/ + int* pi_varOutputCols; /*包含变量的输出属性数的数组*/ + int pi_lastInnerVar; /*内部参数*/ + int pi_lastOuterVar; /*外部参数*/ + int pi_lastScanVar; /*扫描参数*/ + List* pi_acessedVarNumbers; + List* pi_sysAttrList; + List* pi_lateAceessVarNumbers; + List* pi_maxOrmin; /*列表优化,指示获取此列的最大值还是最小值*/ + List* pi_PackTCopyVars; /*记录需要移动的列*/ + List* pi_PackLateAccessVarNumbers; /*记录cstore(列存储)扫描中移动的内容的列*/ + bool pi_const; + VectorBatch* pi_batch; + vectarget_func jitted_vectarget; /* LLVM函数指针*/ + VectorBatch* pi_setFuncBatch; +} ProjectionInfo; + +``` +ExecProject函数的主要执行流程如下。 +(1) 取ProjectionInfo需要投影的信息。按照执行的偏移获取原属性所在的元组,通过偏移量获取该属性,并通过目标属性的序号找到对应的新元组属性位置进行赋值。 +(2) 对pi_targetlist进行运算,将结果赋值给对应元组中的属性。 +(3)产生一个行记录结果,对slot做标记处理,slot包含一个有效的虚拟元组。 + +### ExecEvalParamExec +ExecEvalParamExec函数的作用是获取并返回PARAM_EXEC类型的参数。PARAM_EXEC参数是指内部执行器参数,是需要执行子计划来获取的结果,最后需要将结果返回到上层计划中。核心代码如下: + +```cpp +prm = &(econtext->ecxt_param_exec_vals[thisParamId]); /* 获取econtext中参数 */ +if (prm->execPlan != NULL) { /* 判断是否需要生成参数 */ + /* 参数还未计算执行此函数*/ + ExecSetParamPlan((SubPlanState*)prm->execPlan, econtext); + /*参数计算完计划重置为空*/ + Assert(prm->execPlan == NULL); + prm->isConst = true; + prm->valueType = expression->paramtype; +} +*isNull = prm->isnull; +prm->isChanged = true; +return prm->value; /* 返回生成的参数 */ + +``` +ExecEvalParamExec函数的主要执行流程如下。 +(1) 获取econtext中的ecxt_param_exec_vals参数。 +(2) 判断子计划是否为空,若不为空则调用ExecSetParamPlan函数执行子计划获取结果,并把计划置为空,当再次执行此函数时,不需要重新执行计划,直接返回已经获取过结果。 +(3) 将结果prm->value返回。 + +### ExecEvalParamExtern +ExecEvalParamExtern函数的作用是获取并返回PARAM_EXTERN类型的参数。该参数是指外部传入参数,例如在PBE执行时,PREPARE的语句中的参数,在需要execute语句执行时传入。核心代码如下: + +```cpp +if (paramInfo && thisParamId > 0 && thisParamId <= paramInfo->numParams) {/* 判断参数 */ +ParamExternData* prm = ¶mInfo->params[thisParamId - 1]; + if (!OidIsValid(prm->ptype) && paramInfo->paramFetch != NULL) /* 获取动态参数 */ + (*paramInfo->paramFetch)(paramInfo, thisParamId); + if (OidIsValid(prm->ptype)) { /*检查参数并返回 */ +if (prm->ptype != expression->paramtype) +ereport(……); + *isNull = prm->isnull; + if (econtext->is_cursor && prm->ptype == REFCURSOROID) { + CopyCursorInfoData(&econtext->cursor_data, &prm->cursor_data); + econtext->dno = thisParamId - 1; + } + return prm->value; + } +} + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("no value found for parameter %d", thisParamId))); + return (Datum)0; + +``` +ExecEvalParamExtern函数主要执行流程如下。 +(1) 判断PARAM_EXTERN类型的参数否存在,若存在则从ecxt_param_list_info中获取该参数,否则直接报错。 +(2) 判断参数是否是动态的,若是动态的则再次获取参数。 +(3) 判断参数类型是否符合要求,若符合要求直接返回该参数。 + diff --git "a/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\345\210\240\351\231\244\350\247\222\350\211\262.md" "b/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\345\210\240\351\231\244\350\247\222\350\211\262.md" new file mode 100644 index 0000000000000000000000000000000000000000..375a21667229084e7bb052717d6ae70f0d1b5627 --- /dev/null +++ "b/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\345\210\240\351\231\244\350\247\222\350\211\262.md" @@ -0,0 +1,105 @@ + +## 命令 + +```cpp +DROP ROLE +``` + +## 相关数据结构和函数 + +```cpp +typedef struct DropRoleStmt { + NodeTagtype; + List*roles; /* 要删除的角色列表 */ + boolmissing_ok; /* 判断角色是否存在 */ + boolis_user; /* 要删除的是角色还是用户 */ + boolinherit_from_parent; /* 是否继承自父角色*/ + DropBehavior behavior; /* 是否级联删除依赖对象 */ +} DropRoleStmt; +``` + +流程图如下: + +![](/api/attachments/397773) + +角色删除的执行流程为:首先判断当前操作者是否有权限执行该操作,若没有则报错退出;然后检查待删除的角色是否存在,若不存在,则根据missing_ok选择返回ERROR或NOTICE提示信息;再通过扫描系统表pg_authid和pg_auth_members,删除所有涉及待删除角色的元组执行;若behavior取值DROP_CASCADE,则级联删除该角色所拥有的所有数据库对象;最后删除该角色在系统表pg_auth_history和pg_user_status中对应的信息。具体的实现过程代码如下 + + +```cpp +void DropRole(DropRoleStmt* stmt) +{ + . . . +/* 检查执行者是否有权限删除角色 */ + if (!have_createrole_privilege()) + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied to drop role."))); +/* 循环处理要删除的角色 */ + foreach (item, stmt->roles) { +. . . +/* 检查要删除的角色是否存在,若不存在则提示报错 */ + HeapTuple tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(role)); + if (!HeapTupleIsValid(tuple)) { + if (!stmt->missing_ok) { + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", role))); + } else { + ereport(NOTICE, (errmsg("role \"%s\" does not exist, skipping", role))); + } + continue; + } + . . . +/* 当前用户不允许删除 */ + if (roleid == GetUserId()) + ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("current user cannot be dropped"))); + if (roleid == GetOuterUserId()) + ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("current user cannot be dropped"))); + if (roleid == GetSessionUserId()) + ereport(ERROR, (errcode(ERRCODE_OBJECT_IN_USE), errmsg("session user cannot be dropped"))); + /* 校验执行者和被删除角色的权限,如系统管理员才有权限删除其他系统管理员 */ + if((((Form_pg_authid)GETSTRUCT(tuple))->rolsuper|| ((Form_pg_authid)GETSTRUCT(tuple))->rolsystemadmin) && + !isRelSuperuser()) + ereport(ERROR,(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied."))); + if ((((Form_pg_authid)GETSTRUCT(tuple))->rolauditadmin) && + g_instance.attr.attr_security.enablePrivilegesSeparate && !isRelSuperuser()) + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied."))); + . . . + /* 针对CASCADE(级联)的情况,删除该角色拥有的对象 */ + if (stmt->behavior == DROP_CASCADE) { + char* user = NULL; + CancelQuery(role); + user = (char*)palloc(sizeof(char) * strlen(role) + 1); + errno_t errorno = strncpy_s(user, strlen(role) + 1, role, strlen(role)); + securec_check(errorno, "\0", "\0"); + drop_objectstmt.behavior = stmt->behavior; + drop_objectstmt.type = T_DropOwnedStmt; + drop_objectstmt.roles = list_make1(makeString(user)); + + DropOwnedObjects(&drop_objectstmt); + list_free_deep(drop_objectstmt.roles); + } + + /* 检查是否有对象依赖于该角色,若还存在依赖,则提示报错 */ + if (checkSharedDependencies(AuthIdRelationId, roleid, &detail, &detail_log)) + ereport(ERROR, + (errcode(ERRCODE_DEPENDENT_OBJECTS_STILL_EXIST), + errmsg("role \"%s\" cannot be dropped because some objects depend on it", role), + errdetail_internal("%s", detail), + errdetail_log("%s", detail_log))); + /* 从相关系统表中删除涉及待删除角色的元组 */ + simple_heap_delete(pg_authid_rel, &tuple->t_self); +. . . + while (HeapTupleIsValid(tmp_tuple = systable_getnext(sscan))) { + simple_heap_delete(pg_auth_members_rel, &tmp_tuple->t_self); + } + + systable_endscan(sscan); + DropAuthHistory(roleid); + DropUserStatus(roleid); + DeleteSharedComments(roleid, AuthIdRelationId); + DeleteSharedSecurityLabel(roleid, AuthIdRelationId); + DropSetting(InvalidOid, roleid); +. . . + heap_close(pg_auth_members_rel, NoLock); + heap_close(pg_authid_rel, NoLock); +} + +``` + diff --git "a/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\350\247\222\350\211\262\345\210\233\345\273\272.md" "b/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\350\247\222\350\211\262\345\210\233\345\273\272.md" new file mode 100644 index 0000000000000000000000000000000000000000..d3988b372a5f1116f4959234b1eadf1a2fd8b2bc --- /dev/null +++ "b/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\350\247\222\350\211\262\345\210\233\345\273\272.md" @@ -0,0 +1,213 @@ + +## 简介 + +角色是访问数据库的对象,可具有不同的数据库访问权限。 + +角色管理包含了角色的创建、修改、删除、权限授予和回收操作。 + +## 角色创建 + +### 创建命令 + +```c++ +CREATE ROLE role_name [ [ WITH ] option [ ... ] ] [ ENCRYPTED | UNENCRYPTED ] { PASSWORD | IDENTIFIED BY } { 'password' | DISABLE }; +``` + +### 相关的数据结构和函数 + +#### 函数 + +```cpp +void CreateRole(CreateRoleStmt* stmt) +``` + +#### 数据结构 + +```cpp +typedef struct CreateRoleStmt { + NodeTag type; + RoleStmtType stmt_type; /* 将要创建的角色类型 ROLE/USER/GROUP */ + char* role; /* 角色名 */ + List* options; /* 角色属性列表 */ +} CreateRoleStmt; +``` + +字段stmt_type是枚举类型,数据结构如下: + +```cpp +typedef enum RoleStmtType { +ROLESTMT_ROLE, /* 代表创建角色 */ +ROLESTMT_USER, /* 代表创建用户 */ +ROLESTMT_GROUP, /* 代表创建组用户 */ +} RoleStmtType; +``` + +字段option用来存储角色的属性信息,如下: + +```cpp +typedef struct DefElem { + NodeTag type; + char* defnamespace; /* 节点对应的命名空间 */ + char* defname; /* 节点对应的角色属性名 */ + Node* arg; /* 表示值或类型名 */ + DefElemAction defaction; /* SET/ADD/DROP 等其他未指定的行为 */ +} DefElem; +``` + +### 创建流程 + +![](/api/attachments/397771) + +### 具体代码说明 + +创建角色时先判断所要创建的角色类型。如果是创建用户,则设置其canlogin属性为true,因为用户默认具有登录权限。而创建角色和创建组时,若角色属性参数没有声明的话,则canlogin属性默认为false。 + +```cpp +/* 默认值可能因原始语句类型而异 */ +switch (stmt->stmt_type) { +case ROLESTMT_ROLE: + break; + case ROLESTMT_USER: + canlogin = true; + break; + case ROLESTMT_GROUP: + break; + default: + break; +} +``` + +检查完所要创建的角色类型以后,开始循环获取角色属性options中的内容,并将其转换成对应的角色属性值类型。 + + +```cpp +/* 从node tree中获取option */ +foreach (option, stmt->options) { + DefElem* defel = (DefElem*)lfirst(option); + + if (strcmp(defel->defname, "password") == 0 || strcmp(defel->defname, "encryptedPassword") == 0 || + strcmp(defel->defname, "unencryptedPassword") == 0) { + if (dpassword != NULL) { + clean_role_password(dpassword); + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + dpassword = defel; + if (strcmp(defel->defname, "encryptedPassword") == 0) + encrypt_password = true; + else if (strcmp(defel->defname, "unencryptedPassword") == 0) { + clean_role_password(dpassword); + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("Permission denied to create role with option UNENCRYPTED."))); + } + } else if (strcmp(defel->defname, "sysid") == 0) { + ereport(NOTICE, (errmsg("SYSID can no longer be specified"))); + } else if (strcmp(defel->defname, "inherit") == 0) { + if (dinherit != NULL) { + clean_role_password(dpassword); + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + dinherit = defel; + } else if (strcmp(defel->defname, "createrole") == 0) { + if (dcreaterole != NULL) { + clean_role_password(dpassword); + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + dcreaterole = defel; + } else if (strcmp(defel->defname, "createdb") == 0) { + if (dcreatedb != NULL) { + clean_role_password(dpassword); + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + dcreatedb = defel; + } else if (strcmp(defel->defname, "useft") == 0) { + if (duseft != NULL) { + clean_role_password(dpassword); + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + duseft = defel; +…… +``` + +根据对应的参数信息转换需要的角色属性值类型 + +```cpp + if (dissuper != NULL) + issuper = intVal(dissuper->arg) != 0; + if (dinherit != NULL) + inherit = intVal(dinherit->arg) != 0; + if (dcreaterole != NULL) + createrole = intVal(dcreaterole->arg) != 0; + if (dcreatedb != NULL) + createdb = intVal(dcreatedb->arg) != 0; + …… + +``` + +在完成了转换以后,将角色属性值以及角色的信息一起构建一个pg_authid的元组,再写回系统表并更新索引。 + +```cpp +/* 检查pg_authid relation,确认该角色没有存在*/ +Relation pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock); + TupleDesc pg_authid_dsc = RelationGetDescr(pg_authid_rel); + + if (OidIsValid(get_role_oid(stmt->role, true))) { + str_reset(password); + ereport(ERROR, (errcode(ERRCODE_DUPLICATE_OBJECT), errmsg("role \"%s\" already exists", stmt->role))); +} +…… + /* 创建一个插入的tuple */ + errno_t errorno = memset_s(new_record, sizeof(new_record), 0, sizeof(new_record)); + securec_check(errorno, "\0", "\0"); + errorno = memset_s(new_record_nulls, sizeof(new_record_nulls), false, sizeof(new_record_nulls)); + securec_check(errorno, "\0", "\0"); + + new_record[Anum_pg_authid_rolname - 1] = DirectFunctionCall1(namein, CStringGetDatum(stmt->role)); + + new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper); + new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit); + new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole); + new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb); + + new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper); + new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin); + new_record[Anum_pg_authid_rolreplication - 1] = BoolGetDatum(isreplication); + new_record[Anum_pg_authid_rolauditadmin - 1] = BoolGetDatum(isauditadmin); + new_record[Anum_pg_authid_rolsystemadmin - 1] = BoolGetDatum(issystemadmin); +new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit); +…… + HeapTuple tuple = heap_form_tuple(pg_authid_dsc, new_record, new_record_nulls); + + if (u_sess->proc_cxt.IsBinaryUpgrade && OidIsValid(u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid)) { + HeapTupleSetOid(tuple, u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid); + u_sess->upg_cxt.binary_upgrade_next_pg_authid_oid = InvalidOid; + } + + roleid = simple_heap_insert(pg_authid_rel, tuple); + + if (IsUnderPostmaster) { + if (OidIsValid(rpoid) && (rpoid != DEFAULT_POOL_OID)) + recordDependencyOnRespool(AuthIdRelationId, roleid, rpoid); + + u_sess->wlm_cxt->wlmcatalog_update_user = true; +} +…… + +``` + +完成更新以后,将新创建的角色加入指定存在的父角色中。 + +```cpp + /* 将新角色添加到指定的现有角色中 */ + foreach (item, addroleto) { + char* oldrolename = strVal(lfirst(item)); + Oid oldroleid = get_role_oid(oldrolename, false); + + AddRoleMems( + oldrolename, oldroleid, list_make1(makeString(stmt->role)), list_make1_oid(roleid), GetUserId(), false); + } + + AddRoleMems(stmt->role, roleid, adminmembers, roleNamesToIds(adminmembers), GetUserId(), true); + AddRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), GetUserId(), false); +``` + diff --git "a/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\350\247\222\350\211\262\345\261\236\346\200\247\347\232\204\344\277\256\346\224\271.md" "b/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\350\247\222\350\211\262\345\261\236\346\200\247\347\232\204\344\277\256\346\224\271.md" new file mode 100644 index 0000000000000000000000000000000000000000..c131bd23e1f24a70bfb6a5e2e5a5dff49c664073 --- /dev/null +++ "b/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\350\247\222\350\211\262\345\261\236\346\200\247\347\232\204\344\277\256\346\224\271.md" @@ -0,0 +1,185 @@ + +## 命令 + +```cpp +ALTER ROLE +``` + +## 函数和主要流程 + +属性的修改调用的函数为AlterRole,该函数接受一个AlterRoleStmt类型的参数: + +```cpp +typedef struct AlterRoleStmt { + NodeTag type; + char* role; /* 角色的名称 */ + List* options; /* 需要修改的属性列表 */ + int action; /* +1增加成员关系, -1删除成员关系 */ + RoleLockType lockstatus; /* 角色锁定状态 */ +} AlterRoleStmt; +``` + +主要流程如下: + +![](/api/attachments/397772) + +调用函数AlterRole修改用户角色属性时,首先循环判断options,依次提取要修改的角色属性;然后查看系统表pg_authid判断是否已存在该角色,如果不存在则提示报错;再进行相应的权限判断,检查执行者是否有权限去更改该角色的属性;最后构建一个新的元组,将要更改的属性更新到新元组中,存入系统表pg_authid。同时AlterRole函数也可以用来调整角色的成员关系,结构体中的action字段值设置为1和-1分别表示增加和删除成员关系. + +AlterRole函数的代码如下: + +```cpp +void AlterRole(AlterRoleStmt* stmt) +{ + . . . + /* 循环提取角色的属性options */ + foreach (option, stmt->options) { + DefElem* defel = (DefElem*)lfirst(option); + + if (strcmp(defel->defname, "password") == 0 || strcmp(defel->defname, "encryptedPassword") == 0 || + strcmp(defel->defname, "unencryptedPassword") == 0) { + if (dpassword != NULL) { + clean_role_password(dpassword); + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + dpassword = defel; + if (strcmp(defel->defname, "encryptedPassword") == 0) + encrypt_password = true; + else if (strcmp(defel->defname, "unencryptedPassword") == 0) { + clean_role_password(dpassword); + ereport(ERROR, + (errcode(ERRCODE_INVALID_ROLE_SPECIFICATION), + errmsg("Permission denied to create role with option UNENCRYPTED."))); + } + } else if (strcmp(defel->defname, "createrole") == 0) { + if (dcreaterole != NULL) { + clean_role_password(dpassword); + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + dcreaterole = defel; + } else if (strcmp(defel->defname, "inherit") == 0) { + if (dinherit != NULL) { + clean_role_password(dpassword); + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + } + dinherit = defel; + } +. . . + else { + clean_role_password(dpassword); + ereport(ERROR, + (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("option \"%s\" not recognized", defel->defname))); + } + } +/* 将提取的属性赋值给对应的变量 */ + if (dpassword != NULL && dpassword->arg != NULL) { + head = list_head((List*)dpassword->arg); + if (head != NULL) { + pwdargs = (A_Const*)linitial((List*)dpassword->arg); + if (pwdargs != NULL) { + password = strVal(&pwdargs->val); + } + if (lnext(head)) { + pwdargs = (A_Const*)lsecond((List*)dpassword->arg); + if (pwdargs != NULL) { + replPasswd = strVal(&pwdargs->val); + } + } + } + } + if (dinherit != NULL) + inherit = intVal(dinherit->arg); + if (dcreaterole != NULL) + createrole = intVal(dcreaterole->arg); + . . . + /* 查看要修改的角色是否存在,不存在则提示报错 */ + Relation pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock); + + HeapTuple tuple = SearchSysCache1(AUTHNAME, PointerGetDatum(stmt->role)); + if (!HeapTupleIsValid(tuple)) { + str_reset(password); + str_reset(replPasswd); + + if (!have_createrole_privilege()) + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied."))); + else + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", stmt->role))); + } +roleid = HeapTupleGetOid(tuple); +. . . +/* 检查是否有权限更改相应角色的属性,权限不足则提示报错 */ + if (roleid == BOOTSTRAP_SUPERUSERID) { + if (!(issuper < 0 && inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && isreplication < 0 && + isauditadmin < 0 && issystemadmin < 0 && isvcadmin < 0 && useft < 0 && dconnlimit == NULL && + rolemembers == NULL && validBegin == NULL && validUntil == NULL && drespool == NULL && + dparent == NULL && dnode_group == NULL && dspacelimit == NULL && dtmpspacelimit == NULL && + dspillspacelimit == NULL)) { + str_reset(password); + str_reset(replPasswd); + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("Permission denied to change privilege of the initial account."))); + } + } + if (dpassword != NULL && roleid == BOOTSTRAP_SUPERUSERID && GetUserId() != BOOTSTRAP_SUPERUSERID) { + str_reset(password); + str_reset(replPasswd); + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("Permission denied to change password of the initial account."))); + } + . . . + } else if (!have_createrole_privilege()) { + if (!(inherit < 0 && createrole < 0 && createdb < 0 && canlogin < 0 && isreplication < 0 && isauditadmin < 0 && + issystemadmin < 0 && isvcadmin < 0 && useft < 0 && dconnlimit == NULL && rolemembers == NULL && + validBegin == NULL && validUntil == NULL && !*respool && !OidIsValid(parentid) && dnode_group == NULL && + !spacelimit && !tmpspacelimit && !spillspacelimit && + /* if not superuser or have createrole privilege, permission of lock and unlock is denied */ + stmt->lockstatus == DO_NOTHING && + /* if alter password, it will be handled below */ + roleid == GetUserId()) || + (roleid != GetUserId() && dpassword == NULL)) { + str_reset(password); + str_reset(replPasswd); + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied."))); + } +} +... + /* 将要更改的角色属性值分别更新到新元组中,再将新元组替代旧元组存入系统表pg_authid中 */ + if (issuper >= 0) { + new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper > 0); + new_record_repl[Anum_pg_authid_rolsuper - 1] = true; + + new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper > 0); + new_record_repl[Anum_pg_authid_rolcatupdate - 1] = true; + } + if (inherit >= 0) { + new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit > 0); + new_record_repl[Anum_pg_authid_rolinherit - 1] = true; + } + . . . + HeapTuple new_tuple = heap_modify_tuple(tuple, pg_authid_dsc, new_record, new_record_nulls, new_record_repl); + simple_heap_update(pg_authid_rel, &tuple->t_self, new_tuple); + + CatalogUpdateIndexes(pg_authid_rel, new_tuple); + . . . +/* 判断成员关系,增加或删除成员 */ + if (stmt->action == 1) + AddRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), GetUserId(), false); + else if (stmt->action == -1) /* drop members FROM role */ + DelRoleMems(stmt->role, roleid, rolemembers, roleNamesToIds(rolemembers), false); + + . . . + heap_close(pg_authid_rel, NoLock); +} + +``` + + + + + + + + + + diff --git "a/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\350\247\222\350\211\262\347\232\204\346\216\210\344\272\210\345\222\214\345\233\236\346\224\266.md" "b/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\350\247\222\350\211\262\347\232\204\346\216\210\344\272\210\345\222\214\345\233\236\346\224\266.md" new file mode 100644 index 0000000000000000000000000000000000000000..17c636c23ee51287abcb697ca6257b791eeeef0b --- /dev/null +++ "b/TestTasks/wang-luyang/\350\247\222\350\211\262\347\256\241\347\220\206\344\271\213\350\247\222\350\211\262\347\232\204\346\216\210\344\272\210\345\222\214\345\233\236\346\224\266.md" @@ -0,0 +1,140 @@ + +## 命令 + +### 授予 + +```cpp +GRANT +``` + +### 回收 + +```cpp +REVOKE +``` + +如果使用了`WITH ADMIN OPTION`选项,那么被加入的成员角色还可以将其他角色加入到父角色中。 + +## 相关函数和数据结构 + +GrantRole函数完成角色的授予和回收功能,该函数接受一个GrantRoleStmt类型的参数。 + +```cpp +typedef struct GrantRoleStmt { + NodeTag type; + List* granted_roles;/* 被授予或回收的角色集合 */ + List* grantee_roles;/* 从granted_roles中增加或删除的角色集合 */ + Bool is_grant;/* true代表授权,false代表回收 */ + Bool admin_opt;/* 是否带有with admin option选项 */ + char* grantor;/* 授权者 */ + Drop Behaviorbehavior;/* 是否级联回收角色 */ +} GrantRoleStmt; +``` + +授予角色时,grantee_roles中的角色将被添加到granted_roles,通过调用函数AddRoleMems实现;回收角色时,将grantee_roles中的角色从granted_roles中删除,通过调用函数DelRoleMems实现。 + +AddRoleMems的执行流程如下: + +![](/api/attachments/397774) + +AddRoleMems的具体代码如下: + +```cpp +static void AddRoleMems( + const char* rolename, Oid roleid, const List* memberNames, List* memberIds, Oid grantorId, bool admin_opt) +{ +. . . + /* 校验执行者的权限 */ + if (superuser_arg(roleid)) { + if (!isRelSuperuser()) + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied."))); + . . . + } +. . . + if (grantorId != GetUserId() && !superuser()) + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to set grantor"))); +/* 循环处理要添加的角色 */ + pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock); + pg_authmem_dsc = RelationGetDescr(pg_authmem_rel); + + forboth(nameitem, memberNames, iditem, memberIds) + { + /* 针对角色和成员信息创建pg_auth_members元组,再将新元组插入到系统表中 */ +. . . + new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid); + new_record[Anum_pg_auth_members_member -1] = ObjectIdGetDatum(memberid); + new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId); + new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt); + + if (HeapTupleIsValid(authmem_tuple)) { + new_record_repl[Anum_pg_auth_members_grantor - 1] = true; + new_record_repl[Anum_pg_auth_members_admin_option - 1] = true; + tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc, new_record, new_record_nulls, new_record_repl); + simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple); + CatalogUpdateIndexes(pg_authmem_rel, tuple); + ReleaseSysCache(authmem_tuple); + } else { + tuple = heap_form_tuple(pg_authmem_dsc, new_record, new_record_nulls); + (void)simple_heap_insert(pg_authmem_rel, tuple); + CatalogUpdateIndexes(pg_authmem_rel, tuple); + } + } +. . . + heap_close(pg_authmem_rel, NoLock); +} + +``` + +在上面的代码中 +* rolename和roleid分别表示要被加入成员的角色的名称和OID。 +* memberNames和memberIds分别是要添加的角色名称和OID的列表。 +* grantorId表示授权者的OID。 +* admin_opt表示是否带有with admin option选项。 + +```cpp +static void AddRoleMems( + const char* rolename, Oid roleid, const List* memberNames, List* memberIds, Oid grantorId, bool admin_opt) +{ +. . . + /* 校验执行者的权限 */ + if (superuser_arg(roleid)) { + if (!isRelSuperuser()) + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("Permission denied."))); + . . . + } +. . . + if (grantorId != GetUserId() && !superuser()) + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to set grantor"))); +/* 循环处理要添加的角色 */ + pg_authmem_rel = heap_open(AuthMemRelationId, RowExclusiveLock); + pg_authmem_dsc = RelationGetDescr(pg_authmem_rel); + + forboth(nameitem, memberNames, iditem, memberIds) + { + /* 针对角色和成员信息创建pg_auth_members元组,再将新元组插入到系统表中 */ +. . . + new_record[Anum_pg_auth_members_roleid - 1] = ObjectIdGetDatum(roleid); + new_record[Anum_pg_auth_members_member -1] = ObjectIdGetDatum(memberid); + new_record[Anum_pg_auth_members_grantor - 1] = ObjectIdGetDatum(grantorId); + new_record[Anum_pg_auth_members_admin_option - 1] = BoolGetDatum(admin_opt); + + if (HeapTupleIsValid(authmem_tuple)) { + new_record_repl[Anum_pg_auth_members_grantor - 1] = true; + new_record_repl[Anum_pg_auth_members_admin_option - 1] = true; + tuple = heap_modify_tuple(authmem_tuple, pg_authmem_dsc, new_record, new_record_nulls, new_record_repl); + simple_heap_update(pg_authmem_rel, &tuple->t_self, tuple); + CatalogUpdateIndexes(pg_authmem_rel, tuple); + ReleaseSysCache(authmem_tuple); + } else { + tuple = heap_form_tuple(pg_authmem_dsc, new_record, new_record_nulls); + (void)simple_heap_insert(pg_authmem_rel, tuple); + CatalogUpdateIndexes(pg_authmem_rel, tuple); + } + } +. . . + heap_close(pg_authmem_rel, NoLock); +} + +``` + +函数DelRoleMems的实现过程类似。首先对执行者的相关权限进行校验,然后循环处理要删除的角色,删除系统表pg_auth_member中相关的元组。 diff --git "a/TestTasks/wang-luyang/\350\256\244\350\257\201\346\265\201\347\250\213\350\247\243\346\236\220.md" "b/TestTasks/wang-luyang/\350\256\244\350\257\201\346\265\201\347\250\213\350\247\243\346\236\220.md" new file mode 100644 index 0000000000000000000000000000000000000000..331b513c7cd2cc745229028516589674fdadf18b --- /dev/null +++ "b/TestTasks/wang-luyang/\350\256\244\350\257\201\346\265\201\347\250\213\350\247\243\346\236\220.md" @@ -0,0 +1,202 @@ + +## 流程图 + +![](/api/attachments/397764) + +## 认证流程解析 + +### 第一步 + +客户端将username发送给服务端 + +### 第二步 + +服务端收到用户民后,返回盐值salt、iteration-count(迭代次数)、ServerSignature以及随机生成的字符串token给客户端。token是随机生成字符串。服务端通过计算得到的ServerSignature返回给客户端,计算代码如下。 + +```c++ +ServerSignature := HMAC(ServerKey, token) +``` + +### 第三步 + +客户端认证服务端并发送认证响应。响应信息包含客户端认证信息ClientProof。ClientProof证明客户端拥有ClientKey。 + +客户端利用salt和iteration-count,从password计算得到SaltedPassword,然后计算得到ClientKey、StoryKey和ServerKey。 + +客户端通过StoredKey和token进行哈希计算得到ClientSignature:`ClientSignature := HMAC(StoredKey,token)`,通过将ClientKey和ClientSignature进行异或得到ClientProof:`ClientProof := ClientKey XOR ClientSignature` + +客户端将计算得到的ClientProof和接收的token发送给服务端进行认证。 + +### 第四步 + +服务端接收并校验客户端信息。 + +服务端使用其保存的StoredKey和token通过HMAC算法进行计算,然后与客户端传来的ClientProof进行异或,恢复ClientKey;再对ClientKey进行哈希计算,得到的结果与服务端保存的StoredKey进行比较。如果相等则服务端对客户端的认证通过,否则认证失败。 + +```c++ +ClientSignature := HMAC(StoredKey, token) +HMAC(ClientProof XOR ClientSignature ) = StoredKey +``` + +ClientAuthentication函数接受一个类型Port的参数,完成客户端认证的过程,Port结构中存储着客户端相关信息,代码如下: + +```c++ +void ClientAuthentication(Port* port) +{ + int status = STATUS_ERROR; + char details[PGAUDIT_MAXLENGTH] = {0}; + char token[TOKEN_LENGTH + 1] = {0}; + errno_t rc = EOK; + GS_UINT32 retval = 0; +hba_getauthmethod(port); +…… + switch (port->hba->auth_method) { + case uaReject: +…… +case uaImplicitReject: + …… +/* 使用MD5口令认证 */ +case uaMD5: + sendAuthRequest(port, AUTH_REQ_MD5); + status = recv_and_check_password_packet(port); + break; +/* 使用sha256认证方法 */ +case uaSHA256: + /* 禁止使用初始用户进行远程连接 */ + if (isRemoteInitialUser(port)) { + ereport(FATAL, + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("Forbid remote connection with initial user."))); + } + rc = memset_s(port->token, TOKEN_LENGTH * 2 + 1, 0, TOKEN_LENGTH * 2 + 1); + securec_check(rc, "\0", "\0"); + HOLD_INTERRUPTS(); + /* 生成随机数token */ + retval = RAND_priv_bytes ((GS_UCHAR*)token, (GS_UINT32)TOKEN_LENGTH); + RESUME_INTERRUPTS(); + CHECK_FOR_INTERRUPTS(); + if (retval != 1) { + ereport(ERROR, (errmsg("Failed to Generate the random number,errcode:%u", retval))); + } + sha_bytes_to_hex8((uint8*)token, port->token); + port->token[TOKEN_LENGTH * 2] = '\0'; + /* 发送认证请求到前端,认证码为AUTH_REQ_SHA256 */ + sendAuthRequest(port, AUTH_REQ_SHA256); + /* 接收并校验客户端的信息 */ + status = recv_and_check_password_packet(port); + break; +…… +} +…… +if (status == STATUS_OK) + sendAuthRequest(port, AUTH_REQ_OK); +else { + auth_failed(port, status); +} + +/* 完成认证,关闭参数ImmediateInterruptOK */ +t_thrd.int_cxt.ImmediateInterruptOK = false; +} + +``` + +ClientAuthentication函数调用hba_getauthmethod函数、check_hba函数,检查客户端地址、所连接数据库、用户名在文件HBA中是否有能匹配的HBA记录。如果能够找到匹配的HBA记录,则将Port结构中相关认证方法的字段设置为HBA记录中的参数,同时状态值为STATUS_OK。然后根据不同的认证方法,进行相应的认证过程。 + +在认证过程中可能需要和客户端进行多次交互。最后返回如果为STAUS_OK,则表示认证成功,并将认证成功的信息发送回客户端,否则发送认证失败的信息。 + +认证方法如下: + +![](/api/attachments/397765) + +客户端认证服务端并发送认证响应。 + +客户端根据不同的认证方法进行不同的处理过程,如方法为AUTH_REQ_SHA256时,通过调用函数pg_password_sendauth完成对服务端的认证,代码如下: + +```c++ +static int pg_password_sendauth(PGconn* conn, const char* password, AuthRequest areq) +{ +int ret; +/* 初始化变量 */ +…… + char h[HMAC_LENGTH + 1] = {0}; + char h_string[HMAC_LENGTH * 2 + 1] = {0}; + char hmac_result[HMAC_LENGTH + 1] = {0}; + char client_key_bytes[HMAC_LENGTH + 1] = {0}; + switch (areq) { + case AUTH_REQ_MD5: +/* pg_md5_encrypt()通过MD5Salt进行MD5加密 */ +…… +case AUTH_REQ_MD5_SHA256: +…… + case AUTH_REQ_SHA256: { + char* crypt_pwd2 = NULL; + if (SHA256_PASSWORD == conn->password_stored_method || PLAIN_PASSWORD == conn->password_stored_method) { + /* 通过SHA256方式加密密码 */ + if (!pg_sha256_encrypt( + password, conn->salt, strlen(conn->salt), (char*)buf, client_key_buf, conn->iteration_count)) + return STATUS_ERROR; + + rc = strncpy_s(server_key_string, + sizeof(server_key_string), + &buf[SHA256_LENGTH + SALT_STRING_LENGTH], + sizeof(server_key_string) - 1); + securec_check_c(rc, "\0", "\0"); + rc = strncpy_s(stored_key_string, + sizeof(stored_key_string), + &buf[SHA256_LENGTH + SALT_STRING_LENGTH + HMAC_STRING_LENGTH], + sizeof(stored_key_string) - 1); + securec_check_c(rc, "\0", "\0"); + server_key_string[sizeof(server_key_string) - 1] = '\0'; + stored_key_string[sizeof(stored_key_string) - 1] = '\0'; + + sha_hex_to_bytes32(server_key_bytes, server_key_string); + sha_hex_to_bytes4(token, conn->token); +/* 通过server_key和token调用HMAC算法计算,得到client_server_signature_bytes,通过该变量转为字符串变量,用来验证与服务端传来的server_signature是否相等。 */ + CRYPT_hmac_ret1 = CRYPT_hmac(NID_hmacWithSHA256, + (GS_UCHAR*)server_key_bytes, + HMAC_LENGTH, + (GS_UCHAR*)token, + TOKEN_LENGTH, + (GS_UCHAR*)client_server_signature_bytes, + (GS_UINT32*)&hmac_length); + if (CRYPT_hmac_ret1) { + return STATUS_ERROR; + } + sha_bytes_to_hex64((uint8*)client_server_signature_bytes, client_server_signature_string); + +/* 调用函数strncmp判断计算的client_server_signature_string和服务端传来的server_signature值是否相等 */ + if (PG_PROTOCOL_MINOR(conn->pversion) < PG_PROTOCOL_GAUSS_BASE && + 0 != strncmp(conn->server_signature, client_server_signature_string, HMAC_STRING_LENGTH)) { + pwd_to_send = fail_info; /* 不相等则认证失败 */ + } else { + sha_hex_to_bytes32(stored_key_bytes, stored_key_string); + /* 通过stored_key和token计算得到hmac_result */ + CRYPT_hmac_ret2 = CRYPT_hmac(NID_hmacWithSHA256, + (GS_UCHAR*)stored_key_bytes, + STORED_KEY_LENGTH, + (GS_UCHAR*)token, + TOKEN_LENGTH, + (GS_UCHAR*)hmac_result, + (GS_UINT32*)&hmac_length); + + if (CRYPT_hmac_ret2) { + return STATUS_ERROR; + } + + sha_hex_to_bytes32(client_key_bytes, client_key_buf); +/* hmac_result和client_key_bytes异或得到h,然后将其发送给服务端,用于验证客户端 */ + if (XOR_between_password(hmac_result, client_key_bytes, h, HMAC_LENGTH)) { + return STATUS_ERROR; + } + + sha_bytes_to_hex64((uint8*)h, h_string); + pwd_to_send = h_string; /* 设置要发送给服务端的值 */ + } + } +…… + break; +/* 清空变量 */ +…… + return ret; +} +``` + diff --git "a/TestTasks/wang-luyang/\350\277\233\347\250\213\345\206\205\345\244\232\347\272\277\347\250\213\347\256\241\347\220\206\346\234\272\345\210\266\344\271\213\344\272\213\345\212\241\344\277\241\346\201\257\347\256\241\347\220\206.md" "b/TestTasks/wang-luyang/\350\277\233\347\250\213\345\206\205\345\244\232\347\272\277\347\250\213\347\256\241\347\220\206\346\234\272\345\210\266\344\271\213\344\272\213\345\212\241\344\277\241\346\201\257\347\256\241\347\220\206.md" new file mode 100644 index 0000000000000000000000000000000000000000..d0bbf9e39bae849146c9411aa18c9edee3d46317 --- /dev/null +++ "b/TestTasks/wang-luyang/\350\277\233\347\250\213\345\206\205\345\244\232\347\272\277\347\250\213\347\256\241\347\220\206\346\234\272\345\210\266\344\271\213\344\272\213\345\212\241\344\277\241\346\201\257\347\256\241\347\220\206.md" @@ -0,0 +1,150 @@ + +## 介绍 + +数据库启动时候维护了一段共享内存,每个线程初始化的时候会从这个共享内存中获取一个槽位并将其线程信息记录到槽位中。 + +获取快照时,需要在共享内存数组中更新槽位信息,事务结束时,需要从槽位中将其事务信息清除。 + +计算快照时,通过遍历该全局数组,获取当前所有并发线程的事务信息,并计算出快照信息(xmin、xmax、snapshotcsn等) + +## 数据结构 + +```cpp +typedef struct PGXACT { + GTM_TransactionHandle handle; /* 单机模式无用参数 */ + TransactionId xid; /* 该线程持有的xid号,如果没有则为0 */ + TransactionId prepare_xid; /* 准备阶段的xid号*/ + +TransactionId xmin; /* 当前事务开启时最小的活跃xid,vaccum操作不会删除那些xid大于等于xmin的元组。 */ + CommitSeqNo csn_min; /* 当前事务开启时最小的活跃CSN号*/ + TransactionId next_xid; /* 单机模式无用参数*/ + int nxids; /* 子事物个数*/ + uint8 vacuumFlags; /* vacuum操作相关的flag */ + +bool needToSyncXid; /* 单机模式无用参数*/ +bool delayChkpt; /* 如果该线程需要checkpoint线程延迟等待,此值为true +#ifdef __aarch64__ */ + char padding[PG_CACHE_LINE_SIZE - PGXACT_PAD_OFFSET]; /* 为了性能考虑的结构体对齐*/ +#endif +} PGXACT; + +struct PGPROC { + SHM_QUEUE links; /* 链表中的指针 */ + + PGSemaphoreData sem; /* 休眠等待的信号量 */ + int waitStatus; /* 等待状态 */ + + Latch procLatch; /* 线程的通用闩锁 */ + + LocalTransactionId lxid; /* 当前线程本地顶层事务ID */ + ThreadId pid; /* 线程的PID */ + + ThreadId sessMemorySessionid; + uint64 sessionid; /* 线程池模式下当前的会话ID */ + int logictid; /* 逻辑线程ID */ + TransactionId gtt_session_frozenxid; /* 会话级全局临时表的冻结XID */ + + int pgprocno; + int nodeno; + + /* 线程启动时下面这些数据结构为0 */ + BackendId backendId; /* 线程的后台ID */ + Oid databaseId; /* 当前访问数据库的OID */ + Oid roleId; /* 当前用户的OID */ + + /* 版本号,用于升级过程中新老版本的判断 */ + uint32 workingVersionNum; + + /*热备模式下,标记当前事务是否收到冲突信号。设置该值时需要持有ProcArray锁。 */ + bool recoveryConflictPending; + + /* 线程等待的轻量级锁信息. */ + bool lwWaiting; /* 当等待轻量级锁时,为真 */ + uint8 lwWaitMode; /* 预获取锁的模式 */ + bool lwIsVictim; /* 强制放弃轻量级锁 */ + dlist_node lwWaitLink; /* 等待在相同轻量级锁对象的下一个等待者 */ + +/* 线程等待的常规锁信息 */ + LOCK* waitLock; /* 等待的常规锁对象 */ + PROCLOCK* waitProcLock; /* 等待常规锁对象的持有者 */ + LOCKMODE waitLockMode; /* 预获取常规锁对象的模式 */ + LOCKMASK heldLocks; /* 本线程获取锁对象模式的位掩码 */ + + /* 等待主备机回放日志同步的信息 */ + XLogRecPtr waitLSN; /* 等待的lsn*/ + int syncRepState; /* 等待主备同步的状态 */ + bool syncRepInCompleteQueue; /* 是否等待在完成队列中 */ + SHM_QUEUE syncRepLinks; /* 指向同步队列的指针 */ + + DataQueuePtr waitDataSyncPoint; /* 数据页复制的数据同步点 */ + int dataSyncRepState; /* 数据页复制的同步状态 */ + SHM_QUEUE dataSyncRepLinks; /* 指向数据页同步队列的指针*/ + + MemoryContext topmcxt; /* 本线程的顶层内存上下文 */ + char myProgName[64]; + pg_time_t myStartTime; + syscalllock deleMemContextMutex; + + SHM_QUEUE myProcLocks[NUM_LOCK_PARTITIONS]; + + /* 以下结构为了实现XID批量提交 */ + /* 是否为XID批量提交中的成员 */ + bool procArrayGroupMember; + /* XID批量提交中的下一个成员 */ + pg_atomic_uint32 procArrayGroupNext; + /* 父事务XID和子事物XID中的最大者 */ + TransactionId procArrayGroupMemberXid; + + /* 提交序列号 */ + CommitSeqNo commitCSN; + + /* 以下结构为了实现CLOG批量提交 */ + bool clogGroupMember; /* 是否为CLOG批量提交中的成员*/ + pg_atomic_uint32 clogGroupNext; /* CLOG批量提交中的下一个成员*/ + TransactionId clogGroupMemberXid; /* CLOG批量提交的事务ID */ + CLogXidStatus clogGroupMemberXidStatus; /* CLOG批量提交的事务状态 */ + int64 clogGroupMemberPage; /* CLOG批量提交对应的CLOG页面 */ + XLogRecPtr clogGroupMemberLsn; /* CLOG批量提交成员的提交回放日志位置 */ +#ifdef __aarch64__ + /* 以下结构体是为了实现ARM架构下回放日志批量插入 */ + bool xlogGroupMember; + pg_atomic_uint32 xlogGroupNext; + XLogRecData* xlogGrouprdata; + XLogRecPtr xlogGroupfpw_lsn; + XLogRecPtr* xlogGroupProcLastRecPtr; + XLogRecPtr* xlogGroupXactLastRecEnd; + void* xlogGroupCurrentTransactionState; + XLogRecPtr* xlogGroupRedoRecPtr; + void* xlogGroupLogwrtResult; + XLogRecPtr xlogGroupReturntRecPtr; + TimeLineID xlogGroupTimeLineID; + bool* xlogGroupDoPageWrites; + bool xlogGroupIsFPW; + uint64 snap_refcnt_bitmap; +#endif + + LWLock* subxidsLock; +struct XidCache subxids; /* 子事物XID */ + + LWLock* backendLock; /* 每个线程的轻量级锁,用于保护以下数据结构的并发访问 */ + + /* Lock manager data, recording fast-path locks taken by this backend. */ + uint64 fpLockBits; /* 快速路径锁的持有模式 */ + FastPathTag fpRelId[FP_LOCK_SLOTS_PER_BACKEND]; /* 表对象的槽位 */ + bool fpVXIDLock; /* 是否获得本地XID的快速路径锁 */ + LocalTransactionId fpLocalTransactionId; /* 本地的XID */ +}; +``` + +![](/api/attachments/397838) + +如上图所示,proc_base_all_procs以及proc_base_all_xacts为全局的共享区域,每个线程启动的时候会在这个共享区域中注册一个槽位,并且将线程级指针变量t_thrd.proc以及t_thrd.pgxact指向该区域。当该线程有事务开始时,会将对应事务的xmin、xid等信息填写到pgxact结构体中。 + +关键函数及接口如下。 + +* GetOldestXmin:返回当前多版本快照缓存的oldestXmin。(多版本快照机制见后续章节) +* ProcArrayAdd:线程启动时在共享区域中注册一个槽位。 +* ProcArrayRemove:将当前线程从ProcArray数组中移除。 +* TransactionIdIsInProgress:判断xid是否还在运行之中。 + + diff --git "a/TestTasks/wang-luyang/\350\277\233\347\250\213\345\206\205\345\244\232\347\272\277\347\250\213\347\256\241\347\220\206\346\234\272\345\210\266\344\271\213\345\244\232\347\211\210\346\234\254\345\277\253\347\205\247\346\234\272\345\210\266.md" "b/TestTasks/wang-luyang/\350\277\233\347\250\213\345\206\205\345\244\232\347\272\277\347\250\213\347\256\241\347\220\206\346\234\272\345\210\266\344\271\213\345\244\232\347\211\210\346\234\254\345\277\253\347\205\247\346\234\272\345\210\266.md" new file mode 100644 index 0000000000000000000000000000000000000000..4624f21efe10e7df1eb81d2c3265517aa7fc6fae --- /dev/null +++ "b/TestTasks/wang-luyang/\350\277\233\347\250\213\345\206\205\345\244\232\347\272\277\347\250\213\347\256\241\347\220\206\346\234\272\345\210\266\344\271\213\345\244\232\347\211\210\346\234\254\345\277\253\347\205\247\346\234\272\345\210\266.md" @@ -0,0 +1,197 @@ + +## 简介 + +因为openGauss使用一段共享内存来实现快照的获取以及各线程事务信息的管理,计算快照持有共享锁以及事务结束持有排他锁有严重的锁争抢问题。 + +为了解决该冲突,openGauss引入了多版本快照机制解决锁冲突。每当事务结束时,持有排他锁、计算快照的一个版本,记录到一个环形缓冲区队列内存里;当别的线程获取快照时,并不持有共享锁去重新计算,而是通过原子操作到该环形队列顶端获取最新快照并将其引用计数加1;待拷贝完了快照信息后,将引用计数减1;当槽位引用计数为0时,表示可以被新的快照复用。 + +## 多版本快照数据结构 + +```cpp +typedef struct _snapxid { + TransactionId xmin; + TransactionId xmax; + CommitSeqNo snapshotcsn; + TransactionId localxmin; + bool takenDuringRecovery; + ref_cnt_t ref_cnt[NREFCNT]; /* 该快照的引用计数,如果为0则可复用 */ +} snapxid_t; /*多版本快照内容,在openGauss CSN方案下,仅需要记录xmin、xmax、snapshotcsn等关键信息即可。*/ + +static snapxid_t* g_snap_buffer = NULL; /* 缓冲区队列内存区指针 */ +static snapxid_t* g_snap_buffer_copy = NULL; /* 缓冲区队列内存的浅拷贝 */ +static size_t g_bufsz = 0; +static bool g_snap_assigned = false; /*多版本快照buffer队列是否已初始化 */ + +#define SNAP_SZ sizeof(snapxid_t) /* 每一个多版本快照的size大小 */ +#define MaxNumSnapVersion 64 /* 多版本快照队列的大小,64个版本 */ + +static volatile snapxid_t* g_snap_current = NULL; /* 当前的快照指针 */ +static volatile snapxid_t* g_snap_next = NULL; /* 下一个可用槽位的快照指针 */ +``` + +## buffer队列创建流程 + +在创建共享内存时,根据MaxNumSnapVersion函数的size生成“MaxNumSnapVersion * SNAP_SZ”大小的共享内存区。并将g_snap_current置为0号偏移,g_snap_next置为“1 * SNAP_SZ”偏移。 + +## 多版本快照的计算 + +1. 获取当前g_snap_next。 +2. 保证当前已持有Proc数组的排他锁,进行xmin、xmax、CSN等关键结构的计算,并存放到g_snap_next中。 +3. 寻找下一个refcount为0可复用的槽位,将g_snap_current赋值为g_snap_next,g_snap_next赋值为可复用的槽位偏移。 + +## 多版本快照的获取 +1. 获取g_snap_current指针并将当前快照槽位的引用计数加1,防止并发更新快照时被复用。 +2. 将当前快中的信息拷贝到当前连接的静态快照内存中。 +3. 释放当前多版本快照,并将当前快照槽位的引用计数减1。 + +## 相关函数及作用 + +### CreateSharedRingBuffer + +作用:**创建多版本快照共享内存信息** + +### GetNextSnapXid + +作用:**获取下一个多版本快照位置** + +```cpp +static inline snapxid_t* GetNextSnapXid() +{ +return g_snap_buffer ? (snapxid_t*)g_snap_next : NULL; +} +``` + +### SetNextSnapXid + +作用:**获取下一个可用的槽位,并且将当前多版本快照最新更新** + +```cpp +static void SetNextSnapXid() +{ + if (g_snap_buffer != NULL) { + g_snap_current = g_snap_next; /* 将最新的多版本快照更新到最新。*/ + pg_write_barrier(); /* 此处是防止buffer ring初始化时的ARM乱序问题。*/ + g_snap_assigned = true; + snapxid_t* ret = (snapxid_t*)g_snap_current; + size_t idx = SNAPXID_INDEX(ret); + loop: /* 主循环,整体思路是不停遍历多版本槽位信息,一直找到一个refcout为0的可重用槽位。*/ + do { + ++idx; + /* 如果发生回卷,那么重头再找 */ + if (idx == g_bufsz) + idx = 0; + ret = SNAPXID_AT(idx); + if (IsZeroRefCount(ret)) { + g_snap_next = ret; + return; + } + } while (ret != g_snap_next); + ereport(WARNING, (errmsg("snapshot ring buffer overflow."))); +/* 当前多版本快照个数为64个,理论上可能是会出现槽位被占满,如果没有空闲槽位,重新遍历即可。 */ + goto loop; + } +} + +``` + +### CalculateLocalLatestSnapshot + +作用:**计算多版本快照信息** + +```cpp +void CalculateLocalLatestSnapshot(bool forceCalc) +{ + …/* 初始化变量 */ + + snapxid_t* snapxid = GetNextSnapXid(); /*设置下一个空闲多版本快照槽位信息 */ + + /* 初始化xmax为 latestCompletedXid + 1 */ + xmax = t_thrd.xact_cxt.ShmemVariableCache->latestCompletedXid; + TransactionIdAdvance(xmax); + + /*并不是每个事务提交都会重新计算xmin和oldestxmin,只有每1000个事务或者每隔1s才会计算,此时xmin及oldestxmin一般偏小,但是不影响可见性判断。 */ + currentTimeStamp = GetCurrentTimestamp(); + if (forceCalc || ((++snapshotPendingCnt == MAX_PENDING_SNAPSHOT_CNT) || + (TimestampDifferenceExceeds(snapshotTimeStamp, currentTimeStamp, CALC_SNAPSHOT_TIMEOUT)))) { + snapshotPendingCnt = 0; + snapshotTimeStamp = currentTimeStamp; + + /* 初始化xmin */ + globalxmin = xmin = xmax; + + int* pgprocnos = arrayP->pgprocnos; + int numProcs; + + /* + 循环遍历proc并计算快照相应值 + */ + numProcs = arrayP->numProcs; + /*主要流程,遍历proc_base_all_xacts,将其中pgxact->xid的最小值记为xmin,其中pgxact->xmin的最小值记为oldestxmin。 */ + for (index = 0; index < numProcs; index++) { + int pgprocno = pgprocnos[index]; + volatile PGXACT* pgxact = &g_instance.proc_base_all_xacts[pgprocno]; + TransactionId xid; + + if (pgxact->vacuumFlags & PROC_IN_LOGICAL_DECODING) + continue; + + /* 对于autovacuum的xmin,跳过,避免长VACUUM阻塞脏元组回收 */ + if (pgxact->vacuumFlags & PROC_IN_VACUUM) + continue; + + /* 用最小的xmin来更新globalxmin */ + xid = pgxact->xmin; + + if (TransactionIdIsNormal(xid) && TransactionIdPrecedes(xid, globalxmin)) + globalxmin = xid; + + xid = pgxact->xid; + + if (!TransactionIdIsNormal(xid)) + xid = pgxact->next_xid; + + if (!TransactionIdIsNormal(xid) || !TransactionIdPrecedes(xid, xmax)) + continue; + + if (TransactionIdPrecedes(xid, xmin)) + xmin = xid; + } + + if (TransactionIdPrecedes(xmin, globalxmin)) + globalxmin = xmin; + + t_thrd.xact_cxt.ShmemVariableCache->xmin = xmin; + t_thrd.xact_cxt.ShmemVariableCache->recentLocalXmin = globalxmin; + } + /* 此处给多版本快照信息赋值,xmin、oldestxmin因为不是及时计算故可能偏小,xmax、CSN号都是当前的准确值,注意计算快照的时候必须持有排他锁。 */ + snapxid->xmin = t_thrd.xact_cxt.ShmemVariableCache->xmin; + snapxid->xmax = xmax; + snapxid->localxmin = t_thrd.xact_cxt.ShmemVariableCache->recentLocalXmin; + snapxid->snapshotcsn = t_thrd.xact_cxt.ShmemVariableCache->nextCommitSeqNo; + snapxid->takenDuringRecovery = RecoveryInProgress(); + SetNextSnapXid(); /*设置当前多版本快照 */ +} +(5) GetLocalSnapshotData:获取最新的多版本快照供事务使用。函数代码如下: +Snapshot GetLocalSnapshotData(Snapshot snapshot) +{ + /* 检查是否有多版本快照。在recover启动之前,是没有计算出多版本快照的,此时直接返回。 */ + if (!g_snap_assigned || (g_snap_buffer == NULL)) { + ereport(DEBUG1, (errmsg("Falling back to origin GetSnapshotData: not assigned yet or during shutdown\n"))); + return NULL; + } + pg_read_barrier(); /*为了防止ringBuffer初始化时的ARM乱序问题*/ + snapxid_t* snapxid = GetCurrentSnapXid(); /* 将当前的多版本快照refcount++,避免被并发计算新快照的事务重用。 */ + + snapshot->user_data = snapxid; + + … /* 此处将多版本快照snapxid中的信息赋值给快照,注意此处是深拷贝,因为多版本快照仅有几个变量的关键信息,直接赋值即可,之后就可以将相应的多版本快照refcount释放。 */ + u_sess->utils_cxt.RecentXmin = snapxid->xmin; + snapshot->xmin = snapxid->xmin; + snapshot->xmax = snapxid->xmax; + snapshot->snapshotcsn = snapxid->snapshotcsn; + … + ReleaseSnapshotData(snapshot); /* 将多版本快照的refcount释放,以便可以被重用。 */ + return snapshot; +} +``` + diff --git "a/TestTasks/wang-luyang/\351\224\201\346\234\272\345\210\266\344\271\213\345\270\270\350\247\204\351\224\201.md" "b/TestTasks/wang-luyang/\351\224\201\346\234\272\345\210\266\344\271\213\345\270\270\350\247\204\351\224\201.md" new file mode 100644 index 0000000000000000000000000000000000000000..d65bcfc447928313865bca7b98c566c17502713d --- /dev/null +++ "b/TestTasks/wang-luyang/\351\224\201\346\234\272\345\210\266\344\271\213\345\270\270\350\247\204\351\224\201.md" @@ -0,0 +1,131 @@ + +## 简介 + +常规锁是使用**哈希表**实现的。 + +常规锁支持多种锁模式(lock modes),这些锁模式之间的语义和冲突是通过冲突表来定义的。 + +常规锁主要用于业务访问的数据库对象加锁。常规锁的加锁遵守数据库的两阶段加锁协议,即访问过程中加锁,事务提交时释放锁。 + +常规锁有等待队列并提供了死锁检测机制,当检测到死锁发生时选择一个事务进行回滚。 + +## 级别 + +openGauss提供了8个锁级别分别用于不同的语句并发: + +* 1级锁一般用于SELECT查询操作 +* 3级锁一般用于基本的INSERT、UPDATE、DELETE操作 +* 4级锁用于VACUUM、analyze等操作 +* 8级锁一般用于各类DDL语句 + +具体宏定义及命名代码如下: + +```cpp +#define AccessShareLock 1 /* SELECT语句 */ +#define RowShareLock 2 /* SELECT FOR UPDATE/FOR SHARE语句 */ +#define RowExclusiveLock 3 /* INSERT, UPDATE, DELETE语句 */ +#define ShareUpdateExclusiveLock 4 /* VACUUM (non-FULL),ANALYZE, CREATE INDEX CONCURRENTLY语句 */ +#define ShareLock 5 /* CREATE INDEX (WITHOUT CONCURRENTLY)语句 */ +#define ShareRowExclusiveLock 6 /* 类似于独占模式, 但是允许ROW SHARE模式并发 */ +#define ExclusiveLock 7 /* 阻塞ROW SHARE,如SELECT...FOR UPDATE语句 */ +#define AccessExclusiveLock 8 /* ALTER TABLE, DROP TABLE, VACUUM FULL, LOCK TABLE语句 */ +``` + +## 锁冲突及并发控制 + +![](/api/attachments/397887) + +## 相关数据结构 + + +### 加锁对象数据结构 + +通过对field1->field5赋值标识不同的锁对象,使用locktag_type标识锁对象类型,如relation表级对象、tuple行级对象、事务对象等 + +```cpp +typedef struct LOCKTAG { + uint32 locktag_field1; /* 32比特位*/ + uint32 locktag_field2; /* 32比特位*/ + uint32 locktag_field3; /* 32比特位*/ + uint32 locktag_field4; /* 32比特位*/ + uint16 locktag_field5; /* 32比特位*/ + uint8 locktag_type; /* 详情见枚举类LockTagType*/ + uint8 locktag_lockmethodid; /* 锁方法类型*/ +} LOCKTAG; + +typedef enum LockTagType { + LOCKTAG_RELATION, /* 表关系*/ + /* LOCKTAG_RELATION的ID信息为所属库的OID+表OID;如果库的OID为0表示此表是共享表,其中OID为openGauss内核通用对象标识符 */ + LOCKTAG_RELATION_EXTEND, /* 扩展表的优先权*/ + /* LOCKTAG_RELATION_EXTEND的ID信息 */ + LOCKTAG_PARTITION, /* 分区*/ + LOCKTAG_PARTITION_SEQUENCE, /* 分区序列*/ + LOCKTAG_PAGE, /* 表中的页*/ + /* LOCKTAG_PAGE的ID信息为RELATION信息+BlockNumber(页面号)*/ + LOCKTAG_TUPLE, /* 物理元组*/ + /* LOCKTAG_TUPLE的ID信息为PAGE信息+OffsetNumber(页面上的偏移量) */ + LOCKTAG_TRANSACTION, /* 事务ID (为了等待相应的事务结束) */ + /* LOCKTAG_TRANSACTION的ID信息为事务ID号 */ + LOCKTAG_VIRTUALTRANSACTION, /* 虚拟事务ID */ + /* LOCKTAG_VIRTUALTRANSACTION的ID信息为它的虚拟事务ID号 */ + LOCKTAG_OBJECT, /* 非表关系的数据库对象 */ + /* LOCKTAG_OBJECT的ID信息为数据OID+类OID+对象OID+子ID */ + LOCKTAG_CSTORE_FREESPACE, /* 列存储空闲空间 */ + LOCKTAG_USERLOCK, /* 预留给用户锁的锁对象 */ + LOCKTAG_ADVISORY, /* 用户顾问锁 */ + LOCK_EVENT_NUM +} LockTagType; + +``` + +### 常规锁LOCK结构 + +tag是常规锁对象的唯一标识,procLocks是将该锁所有的持有、等待线程串联起来的结构体指针。 + +```cpp +typedef struct LOCK { + /* 哈希键 */ + LOCKTAG tag; /* 锁对象的唯一标识 */ + + /* 数据 */ + LOCKMASK grantMask; /* 已经获取锁对象的位掩码 */ + LOCKMASK waitMask; /* 等待锁对象的位掩码 */ + SHM_QUEUE procLocks; /* 与锁关联的PROCLOCK对象链表 */ + PROC_QUEUE waitProcs; /* 等待锁的PGPROC对象链表 */ + int requested[MAX_LOCKMODES]; /* 请求锁的计数 */ + int nRequested; /* requested数组总数 */ + int granted[MAX_LOCKMODES]; /* 已获取锁的计数 */ + int nGranted; /* granted数组总数 */ +} LOCK; + +``` + +### PROCLOCK结构 + +主要是将同一锁对象等待和持有者的线程信息串联起来的结构体 + +```cpp +typedef struct PROCLOCK { + /* 标识 */ + PROCLOCKTAG tag; /* proclock对象的唯一标识 */ + + /* 数据 */ + LOCKMASK holdMask; /* 已获取锁类型的位掩码 */ + LOCKMASK releaseMask; /* 预释放锁类型的位掩码 */ + SHM_QUEUE lockLink; /* 指向锁对象链表的指针 */ + SHM_QUEUE procLink; /* 指向PGPROC链表的指针 */ +} PROCLOCK; + +``` + +t_thrd.proc结构体里waitLock字段记录了该线程等待的锁,该结构体中procLocks字段将所有跟该锁有关的持有者和等着串起来,其队列关系如下 + +![](/api/attachments/397890) + +## 主要函数及作用 + +* LockAcquire:对锁对象加锁。 +* LockRelease:对锁对象释放锁。 +* LockReleaseAll:释放所有锁资源。 + + diff --git "a/TestTasks/wang-luyang/\351\224\201\346\234\272\345\210\266\344\271\213\350\207\252\346\227\213\351\224\201\345\222\214LWLock\350\275\273\351\207\217\347\272\247\351\224\201.md" "b/TestTasks/wang-luyang/\351\224\201\346\234\272\345\210\266\344\271\213\350\207\252\346\227\213\351\224\201\345\222\214LWLock\350\275\273\351\207\217\347\272\247\351\224\201.md" new file mode 100644 index 0000000000000000000000000000000000000000..64055dfe0fefa66a587588396603e54cebdf150a --- /dev/null +++ "b/TestTasks/wang-luyang/\351\224\201\346\234\272\345\210\266\344\271\213\350\207\252\346\227\213\351\224\201\345\222\214LWLock\350\275\273\351\207\217\347\272\247\351\224\201.md" @@ -0,0 +1,100 @@ + +## 简介 + +数据库对公共资源的并发控制是通过锁来实现。 + +根据锁的用途不同,通常可以分为3种: + +* 自旋锁(spinlock) + +* 轻量级锁(LWLock,light weight lock) + +* 常规锁 + +* 或者上面三种锁的封装 + +使用锁的一般操作流程可以简述为3步: + +1. 加锁 +2. 临界区操作 +3. 放锁 + +## 自旋锁 + +### 实现与特点 + +自旋锁一般是使用CPU的原子指令TAS(test-and-set)实现的。 + +自旋锁仅有2种状态:锁定和解锁。 + +自旋锁最多只能被一个进程持有。 + +### 与信号量的区别 + +当进程无法得到资源时,信号量使进程处于睡眠阻塞状态,而自旋锁使进程处于忙等待状态。 + +### 应用场景 + +自旋锁主要用于加锁时间非常短的场合,比如修改标志或者读取标志字段,在几十个指令之内。在编写代码时,自旋锁的加锁和解锁要保证在一个函数内。自旋锁由编码保证不会产生死锁,没有死锁检测,并且没有等待队列。由于自旋锁消耗CPU,当使用不当长期持有时会触发内核core dump(核心转储),openGauss中将许多32/64/128位变量的更新改用CAS原子操作,避免或减少使用自旋锁。 + +### 相关操作: + +* SpinLockInit:自旋锁的初始化。 +* SpinLockAcquire:自旋锁加锁。 +* SpinLockRelease:自旋锁释放锁。 +* SpinLockFree:自旋锁销毁并清理相关资源。 + +## LWLock轻量级锁 + +### 实现原理 + +由原子操作、等待队列和信号量实现 + +### 类型 + +* 共享锁 +* 排他锁 + +多个进程可以同时获取共享锁,但排他锁只能被一个进程拥有。 + +当进程无法得到资源时,轻量级锁会使进程处于睡眠阻塞状态。 + +### 应用场景及注意事项 + +主要用于内部临界区操作比较久的场合,加锁和解锁的操作可以跨越函数,但使用完后要立即释放。 + +轻量级锁应由编码保证不会产生死锁。但是由于代码复杂度及各类异常处理,openGauss提供了LWLock的死锁检测机制,避免各类异常场景产生的LWLock死锁问题。 + +### 相关函数及其作用 + +* LWLockAssign:申请一个LWLock。 +* LWLockAcquire:加锁。 +* LWLockConditionalAcquire:条件加锁,如果没有获取锁则返回false,并不一直等待。 +* LWLockRelease:释放锁。 +* LWLockReleaseAll:释放拥有的所有锁。当事务过程中出错了,会将持有的所有LWLock全部回滚释放,避免残留阻塞后续操作。 + +### 相关数据结构 + +```cpp +#define LW_FLAG_HAS_WAITERS ((uint32)1 << 30) +#define LW_FLAG_RELEASE_OK ((uint32)1 << 29) +#define LW_FLAG_LOCKED ((uint32)1 << 28) + +#define LW_VAL_EXCLUSIVE ((uint32)1 << 24) +#define LW_VAL_SHARED 1 /* 用于标记LWLock的state状态,实现锁的获取和释放*/ + +typedef struct LWLock { + uint16 tranche; /* 轻量级锁的ID标识 */ + pg_atomic_uint32 state; /* 锁的状态位 */ + dlist_head waiters; /* 等锁线程的链表 */ +#ifdef LOCK_DEBUG + pg_atomic_uint32 nwaiters; /* 等锁线程的个数 */ + struct PGPROC *owner; /* 最后独占锁的持有者 */ +#endif +#ifdef ENABLE_THREAD_CHECK + pg_atomic_uint32 rwlock; + pg_atomic_uint32 listlock; +#endif +} LWLock; +``` +