1、 数据库普通模式启动过程做了哪些事情
a) vb_ctl start后 初始化状态PM_init :很长一段时间都会是这个状态。做了的事情是应用程序环境检查、资源初始化(比如内存、信号量等)、加载配置(vb是通过GUC参数控制数据库状态的)。然后启动startup线程(处于normal或主库状态下的话startup线程完成任务后就会退出,加入是备库的话startup线程会一直保留),使得数据库进入PM_STARTUP状态
b) PM_STARTUP:读取checkpoint日志,目的是做数据恢复(chekpoint 和redopoint 之间的的内容就是要进行恢复的东西如果正常退出是不需要进行数据恢复的,异常退出或者PITR恢复时会进入)。当数据库开始做数据恢复时,数据库将进入PM_RECOVERY状态。
c) PM_RECOVERY:告诉主线程postmaster线程,要开始处理恢复开始的信号,把脏页落盘
d) 一旦startup线程正常退出,则表示数据库正常启动,进入PM_RUN状态
2、 数据库普通模式正常Fast模式停止过程中的状态变化
a) 收到停库信号,从正常运行的PM_RUN状态进入等待状态,PM_WAIT_BACKEND状态,断开客户端连接,告诉一些客户端终止正在运行的事务,该回滚的就回滚,不太关键的线程(特定的辅助线程会保留)尽快退出,终止一些。关键线程是不会退出的(如checkpoint。syslog,pgwriter)。
b) 退完之后进入停库的准备阶段,PM_SHUTDOWN,此时无任何的客户端连接,(即数据的状态已经不再可能发生变化了,该写的事务日志已经写入磁盘了)此时就会告诉checkpoint做一个停库检查点,完成之后checkpoint线程就会退出。此时检查点是全量,已经没有脏页了,pagewriter也退出了。此时就会进入PM_SHUTDOWN_2
c) PM_SHUTDOWN_2,如果有主备,则需要将停库产生的wal日志发送到备库,此时退出walsender/归档等线程,进入PM_WAIT_DEAD_END,
d) PM_WAIT_DEAD_END 此时会拒绝所有的连接,线程的关闭是一个持续的过程,当完成所有的子线程的关闭,只剩下postmaster线程时,那么进入no_childeren状态
e) PM_NO_CHILDREN 已经没有子进程,当停止了postmoster
3、 vastbase多线程之间是如何利用信号通信的,为什么这么做
原因:
多线程架构下,信号处理有两种情况,①面向进程,如果进程是一个多线程的架构,那来处理这个信号的线程是不确定的②面向线程,那一定是此线程来处理信号。
由于面向进程的信号,如停库,是面向进程的信号,希望是由postmaster线程来处理,但操作系统实现时无法保证;多线程里面每个信号的处理函数只能有一个,不能有多个,就算安装了多个处理函数,也会由后面的覆盖前面的。
在数据库里,即使是同一个信号对于不同的线程来说有不同的行为。
如何:
通过模拟信号机制
(1) 单独的线程signal monitor用于接收外部面向进程的信号,其他线程都屏蔽这个信号。signal monitor屏蔽了siguser2
(2) 针对每一个线程都维护一个数组,每个数组用来记录接收的信号和发送此信号的线程,
(3) 其中vastbase内部的多线程之间是用SIGUSER2这个信号来通信的,所有线程都只向操作系统注册siguser2的信号处理函数
过程
1给2发送一个信号,先记录到2的数组中,向操作系统线程发siguser2信号,操作系统接收到信号后就调用siguser2信号的处理函数,遍历数组找到对应信号的处理函数)
4、 QueryTree
from后的子句,对对象进行拆解,包括表和连接信息
输出列信息,c1和c2
jointree包含了,左参右参的信息,操作表达式的操作类型和参数,对应了rtable中涉及的对象
5、 列举常见的逻辑优化手段并给出示例
常见的逻辑优化手段包含:常见的优化手段包含:子查询/连接提升、条件化简、HAVING和WHERE子句合并、外连接消除、谓词下推、连接顺序交换和等价类推理等。
【要做的事情是否有别的等价的方式】保证语义不变的前提下,做QUERY Tree的变形。即基于关系代数,找出SQL语句等价的变换形式,使得SQL执行更高效。
也就是:原则:结果要保持一致,效率要得到提升
【常见的逻辑优化手段】:
子查询/连接提升
条件简化
HAVING和WHERE子句合并
外连接消除
谓词下推
连接顺序交换
等价类推理
①ANY子连接提升:
课件中主要介绍了比较谓词EXISTS/ANY ,它们可以改写成 SEMI JOIN。这类谓词主要是判断是否存在一条记录满足过滤条件。
【示例】对示例改写后,先对t2的扫描结果进行了过滤,把得到的数据和t1进行连接,left join就是在右边(t2表)没找到的话会补充空值NULL,在这里把右边t2的一些结果过滤掉是不影响最终结果的。
改写前,t2表会被反复扫描多遍,扫描代价可能会很高。改写后减少了扫描次数,提升了执行效率。
EXISTS的改写和ANY差不多。
②条件化简比如常量化简,在不改变语句含义的前提下可以对SQL语句进行改写、优化。
③对于多个OR出现的查询语句来说,这一连串的条件构造出的树状结构,它的执行逻辑是要递归的,很耗时。我们优化它的思路是把它展平,or就是说这一连串有一种I情况是真的就为真。对于多个Or的优化就是改写语句来实现减少判断条件。
④HAVING子句合并:
如果没有使用group by 或者聚合函数(count(),min().max()),having子句会被合并到where 子句中。
⑤外连接消除:为什么要做外连接消除?因为在逻辑优化的过程中,经常会采用谓词下推、连接顺序交换等手段,而外连接会阻碍这些操作。
⑥谓词下推:把能够过滤数据的条件,把他放到树的越下层越好,因为越靠下,我就可以提前把一些数据过滤掉。这样使树的上层,输入会变少,少处理一些元组。
⑦等价类推理则是期望通过推导产生更丰富的约束条件。如A = B AND B = C,如果A、C在不同的表,那么可以推导出A = C,在连接时就多了一种选择,可能产生更优的计划。
select * from t1, t2 where t1.c1 = t2.c1 and t1.c1 > 1;
where后面的语句可以推导出:t1.c1>1那么t2.c1也>1,那么这个执行计划就是对t1,t2进行了过滤。
6、 列举常见的物理算子并描述工作流程
物理算子也就是指执行器的各种算法。
物理优化则是在逻辑优化的基础上构建一棵路径树。树中每个路径都是一个物理算子(对应逻辑算子的实现),分为两类:扫描路径和连接路径。扫描路径是对基表(RELOPT_BASEREL)而言,包含顺序扫描(seqscan)、索引扫描(indexscan)、仅索引扫描(indexonlyscan)、位图扫描(bitmapscan)和tidscan。连接路径则是记录了表之间的物理连接关系,大体上包含嵌套连接(nested loop)、归并连接(merge join)和哈希连接(hash join),还有些变种如outer join、semi join等。
顺序扫描:对于函数来说就是拿到堆表的物理文件,然后根据他物理的块一个一个去遍历,这个属于顺序IO。
②索引扫描:如果这个表上有索引,那么它会先打开这个索引(一般就是btree),索引那边其实也是一个个的block,如果有索引条件,他就会按照索引条件在btree里找对应的记录。每找到一条满足条件的索引,会根据索引里记录的tid 去堆表里找数据。
③仅索引扫描:就是说你SQL语句里需要的信息在索引里面已经全部包含了,这个时候就不需要再去表中找了,只需要把索引中的那个信息读出来就可以了。
④位图扫描:为了解决索引扫描的这个随机IO的问题,因为索引扫描每找到一条记录都要回表,解决办法:找到后先不回表,先把找到的这个tid存起来,全部找到之后按照块的编号排个序,在按顺序逐个访问这些索引对应的数据。这样大概率就是顺序IO,而不是随机IO。
tidscan:行存表,每个元组都有一个tid,tidscan就是按照tid去扫描。
⑥嵌套连接:(是循环,内存消耗小)两个表在做表连接时依靠两层嵌套循环(分别为外层循环和内存循环)来得到连接结果集的表连接方法。即外层循环对应的驱动结果集有多少条记录,遍历被驱动表的内层循环就要做多少次,这就是所谓的“嵌套循环”的含义。
⑦归并连接:(内存消耗小,结果有序)1. 先比较两张关系表的第一个元素。
- 如果第一个元素相等,将这两条数据放到结果集中。然后比较下一个元素。
- 如果不相等,取小的元素所在表的下一个元素,下一个元素可能就匹配了
- 重复前面的几步操作,直到其中一张表的数据比较完了为止。
这个算法能正常运作的前提是,两张关系表的数据都已经排好序,在比价的过程中不需要回溯前面的元素。
⑧哈希连接:(速度快,消耗内存较前两者更多)指的是两个表连接时, 先利用两表中记录较少的表在内存中建立 hash 表, 然后扫描记录较多的表并探測 hash 表, 找出与 hash 表相匹配的行来得到结果集的表连接方法。
7、 为什么引入HOT机制,带来了什么问题,怎么解决的
原因:
每一个元组都有一个ctid(页内块编号,块内偏移量)可以唯一的定位一个元组。
更新操作会在页面上产生一个新的元组,新的元组在是否要维护为新的记录是一个问题,常规的做法是在索引里新增一条记录,则带来了两个问题:①索引需要维护,②索引字段没有变化,索引树字段值相同
(如果在c1列上创建主键,则索引里面会记录字段c1的值、c1的ctid(找到对应堆表中的记录))
HOT机制就是,如果当前页面有空闲的空间,即可以放新的元组,而且没有更新索引字段,那么就会更新就元组的tid(指向新的元组,如果元组被update了,直到找到当前快照是可见的,查询操作才会完成),设置相关的bit位,这样就可以不用维护索引。当索引扫描时根据索引条目找到对应记录的tid,然后找到第一天元组,顺着用tid串起来的HOT链遍历扫描。
问题:
如果表开始没有索引,后续才建立索引,则当没有建索引的时候会遵循HOT的原则,形成HOT链,旧元组的ctid会被记为下一个更新它的ctid,更新元组就找不全
创建索引后扫描,顺着hot链扫描就会出现问题,索引中只能记最后一条更新内容,因为此时不满足hot规则。
如果有时会出现索引里记录了最后一条,但前面其中一条是可重复读记录。索引里就看不见可重复读的这条记录,此时需要把索引对可重复读的快照不可见,即不能用索引扫描来处理它
解决办法:
建索引的时候有一个标志位:broken hot chain 来记录索引的hotchain是否被破坏了。
系统表pg_index中有一个字段indcheckxmin,值为真时,查询必须要保证查询快照的xmin必须要小于事务快照的transactionXmin(事务里所有快照都不能看到更新操作导致的这些事务之后再去用索引扫描才是安全的。
8、 描述为什么引入TOAST机制,触发TOAST机制时内核是如何处理的,某个字段值被存储到TOAST表中,原表中记录了哪些内容。
原因:
一个页面8K无法存入太长元组,不允许元组跨页,且建表时无法要求长度。所以引入了TOAST。当发现元组太大时,就会触发TOAST机制,把元组的大小尽可能的变小,如果操作后还是无法变得太小,那么就会把很长的字段放入toast表里。
触发后处理步骤(从最长的开始)
①判断元组的大小是否超过了toast_tuple_target值-元组头大小(24字节)的值,超过则进行后续处理,如果字段类型为x的能不能压缩,能则进行压缩,压缩后再进判断,如果比目标值小则不会触发toast,结束放入
②当压缩后还大于目标值,数据要放入一个新的元组,此时字段类型x和e的放入toast表中,放完x e字段后如果不超过目标值,则结束
③放完x e字段后如果还超过目标值,则处理字符类型为m的能不能压缩,能则进行压缩,压缩后再进判断,如果比目标值小则不会触发toast,结束放入
④当压缩后还大于目标值,数据要放入一个新的元组,此时字段类型m的放入toast表
记录内容
原表中记录的内容包括了 指向toast表中的字段,物理大小 压缩前的大小,解压算法
9、 描述Vastbase中有哪些快照类型,具体含义是什么
快照是所有不同元组的可见版本组成的,一个快照包含了xmin最早活跃(运行)的事务ID;xmax最近提交事务的ID+1;CSN最近提交事务的序列号
snapshot_mvcc:当且仅当元组对于MVCC快照是有效的,有效:所有事物在当前快照获取的时刻,所有已提交的事务。(当前事务中,之前的数据是可见的)通过事务号是否在xmin和xmax之间,如果在就要查cid
snapshot_version_mvcc:通过csn号来判断事务可见性
snapshot_now:一个元组当前时刻有效(所有已提交的事务是当前实例的;当前事务之前提交的)
snapshot_any:都可见
snapshot_toast:如果元组作为toast行可见,那就是可见的
snapshot_dirty:脏读
mvcc和now的区别
对于MVCC快照是在快照开始的时刻就已经确定了哪些事务对快照是可见的了,对于NOW快照,需要看判断的时机,在生存周期范围内提交了事务,那么事务才是可见的。
10、 MVCC快照
xmax修改后已经提交了的,则一定不可见
一定可见,修改的内容是在snapshot范围外,不影响这次可见
在snapshot之前已经提交了,
不一定可见:超出了snapshot的范围了
11、 描述常见的XLOG类型及含义
在事务中进行一些操作后,会自动的写XLOG,可用于宕机之后的回放、主备恢复等。