数据库是磁盘上的物理文件集合,是实际存储数据的物理实体。在达梦数据库中,数据库包含以下文件:
| 文件类型 | 文件扩展名 | 说明 |
|---|---|---|
| 数据文件 | .DBF | 存储表数据、索引等实际数据 |
| 重做日志文件 | .LOG | 记录所有数据修改操作,用于恢复 |
| 控制文件 | .CTL | 记录数据库的物理结构信息 |
| 临时数据文件 | .TMP | 用于排序、哈希等临时操作 |
| 配置文件 | .INI | 实例参数配置(如 dm.ini) |
相比于实例,数据库保存在磁盘中,是持久的。即使实例关闭数据库文件依然存在。
实例是由一个dmserver进程和一个大型的共享内存组成的,通过实例可以操作数据库的内容。
相比与数据库,实例仅仅在数据库启动后存在于服务器内存中,是动态的、临时的。实例是操作数据库的"手段"或"入口",用户通过实例来访问和修改数据库中的数据。
在一般情况下,一个实例只能和一个数据库进行关联和操作。在DMDSC集群下可以多个实例同时装载并打开一个数据库。
| 线程名称 | 功能说明 |
|---|---|
| 监听线程(dm_lsnr_thd) | 循环监听服务器端口,接收客户端连接请求 |
| 工作线程(dm_wrkgrp_thd) | 核心线程,从任务队列取出任务并处理 SQL 请求 |
| IO 线程 | 负责磁盘读写操作 |
| 日志刷写线程(dm_lgwr_thd) | 将日志缓冲区内容写入磁盘日志文件 |
| 检查点线程 | 定期触发检查点,将脏页写入数据文件 |
| 调度线程 | 定时任务调度 |
| 内存区域 | 作用 |
|---|---|
| 数据缓冲区(BUFFER) | 缓存从磁盘读取的数据页,减少 IO |
| 日志缓冲区(LOG_BUFFER) | 缓存重做日志,批量写入磁盘 |
| SQL 缓冲区 | 缓存 SQL 语句和执行计划 |
| 字典缓冲区 | 缓存数据字典信息 |
| HUGE 缓冲区 | 用于 HUGE 表(列存表)的缓冲 |
DM存储结构分为逻辑存储和物理存储。
物理存储描述的是数据在硬盘上的存储。典型的物理存储结构包括:用于进行功能设置的配置文件;用于记录文件分布的控制文件;用于保存用户实际数据的数据文件、重做日志文件、归档日志文件、备份文件;用来进行问题跟踪的跟踪日志文件等。
逻辑存储描述数据之间的逻辑关系。DM的逻辑存储结构包括表空间、段、簇、页及模式对象。
页->簇->段->模式对象->表空间
DM数据库由多个表空间组成,每个表空间对应一个物理文件。在创建数据库时系统会默认生成5个表空间。
SYSTEM表空间:系统表空间,用来存放DM数据库的字典信息。用户无需进行操作,由系统自动维护。(字典信息是数据库的元数据,包括表定义、索引定义、约束信息、视图定义、存储过程、用户信息、表空间信息、统计信息等)
ROLL表空间:存放事务运行时执行DML操作之前的值,从而为访问该表的其它用户提供表一致性视图。用户无需进行操作,由系统自动维护。
MAIN表空间:存放用户业务数据。是混合表空间,可以存放HUGE表。
TEMP表空间:存放执行SQL过程中产生的临时数据。用户无需进行操作,由系统自动维护。
RLOG表空间:存放日志。用户无需进行操作,由系统自动维护。
数据默认存放在MAIN表空间中,另外,用户可以创建表空间来存放数据。用户创建的表空间默认是普通表空间,无法存放HUGE表,可以在创建时知道HUGE路径或者对普通表增加HUGE路径来使其升级到混合表空间。
除了RLOG表空间为日志表空间外,其他均为数据表空间。为了进行更加高效的存储管理,同时提高磁盘空间的利用率,数据表空间被划分为段、簇和页。数据库中的数据信息就存储在页中,多个页组合成为簇,多个簇组合成为段。
数据库的每一行就是一条记录,存储在页中。
DM数据库的最小存储单位,大小可以是4kb、8kb(默认设置)、16kb、32kb,创建之后页大小无法改变。
页由页头控制信息、数据、空闲空间和行偏移数组组成。页头控制信息包括页类型、页地址等信息;行偏移数组用于标识页上的空间占用情况以便管理数据页自身的空间。
簇是数据库对象分配存储空间的基本单位,由同一个数据文件中16个(默认设置)或32个或64个连续的数据页组成,创建之后簇大小无法改变。
当创建一个表或索引时系统会自动分配至少一个簇,并生成对应数量的空闲空间页,如果需要更大的空间,也会自动进行扩展。在删除表或索引时数据库通过修改数据文件中的位图来释放簇,释放后的簇被视为空闲簇,可以供其他对象使用。
数据库使用32Byte来描述一个簇,由簇链表指针、状态、段id和页标记位图组成。簇链表指针指向前一个和后一个簇的位置,将属于同一段的多个簇串联起来,形成逻辑上的连续空间;状态字段标识当前簇的使用情况,分为空闲簇、半满簇和全满簇;段 ID 用于标识该簇属于哪个段;页标记位图标记对应页是否已被使用。
由一组簇组成。在同一个表空间中,段可以包含来自不同文件的簇,即一个段可以跨越不同的文件。页在簇中是连续的,段中的不同簇在磁盘上不一定连续。
按照数据的使用类型,可以将段分为以下几种类型:
DM 数据库管理系统的内存结构主要包括内存池、缓冲区、排序区、哈希区等。
DMServer 的内存池包括共享内存池和其他一些运行时内存池。
DMServer在启动时会从操作系统申请一大片内存,这就是共享内存池。
数据库在运行时需要频繁申请和释放小片内存。如果每次都直接向操作系统申请/释放,会触发系统调用和线程切换,严重影响性能。所以一次向操作系统申请一片较大内存,作为共享内存池,启动时一次性向操作系统申请一大片内存,后续的小内存分配都在池内进行,用完归还即可。
可以通过修改dm.ini里的相关参数对共享内存池进行设置,以下时一些核心参数配置。
| 参数名 | 默认值 | 说明 |
|---|---|---|
| MEMORY_POOL | 500 MB | 共享内存池初始大小。64位平台范围:64~67108864 MB |
| MEMORY_N_POOLS | 1 | 共享内存池分片数。0/1 表示不分片,最大 2048。分片时每池最小 30MB |
| MEMORY_TARGET | 15000 MB | 共享内存池扩充到此大小后,空闲时会收缩回该值。0 表示不限制 |
| MEMORY_EXTENT_SIZE | 32 MB | 共享内存池每次扩展的增量大小,范围 1~10240 MB |
| MEMORY_BAK_POO | 4 MB | 系统备份内存池大小 |
当 MEMORY_N_POOLS > 1 时,共享内存池被分成多个子池:
除了共享内存池,DMServer的一些功能模块会使用自己的运行时内存池,直接从操作系统申请内存供本模块专用。
| 内存池 | 参数 | 默认值 | 说明 |
|---|---|---|---|
| 虚拟机内存池 | VM_POOL_SIZE | 64 KB | 执行过程中大部分内存从此申请,从OS直接申请 |
| VM_POOL_TARGET | 16384 KB | 虚拟机内存池最大可扩展大小,0=不限制 | |
| VM_MEM_HEAP | 0 | VM内存分配模式:0=MEMORY POOL;1=HEAP;2=混合 | |
| 会话内存池 | SESS_POOL_SIZE | 64 KB | 会话缓冲区大小 |
| SESS_POOL_TARGET | 16384 KB | 会话缓冲区最大扩展大小 |
数据缓冲区是 DM Server 最重要的内存区域之一,用于缓存从磁盘读取的数据页和待写入磁盘的数据页。
DMServer有四种类型的数据缓冲区。
管理链是数据缓冲区内部用于组织和管理数据页的链表结构。DM的数据缓冲区通过三条管理链来实现数据页的高效分配、使用和淘汰。
自由链
| 属性 | 说明 |
|---|---|
| 存放内容 | 尚未被使用的空闲数据页 |
| 初始状态 | 数据库启动时,所有数据页都在自由链上 |
| 分配方式 | 当需要缓存新的磁盘数据时,从自由链头部取一个空闲页 |
| 回收方式 | 数据页被淘汰后,归还到自由链 |
LRU链
| 属性 | 说明 |
|---|---|
| 存放内容 | 已被使用(加载了数据)的数据页 |
| 组织方式 | 按最近访问时间排序,头部是最近访问的,尾部是最久未访问的 |
| 页状态 | 包含两种:干净页(未修改)和脏页(已修改) |
| 淘汰策略 | 当自由链为空时,从 LRU 链尾部淘汰最久未使用的页 |
脏链
| 属性 | 说明 |
|---|---|
| 存放内容 | 已被修改但尚未写入磁盘的数据页 |
| 与 LRU 链的关系 | 脏页同时挂在 LRU 链和脏链上(双向关联) |
| 刷盘机制 | 检查点(Checkpoint)或后台线程扫描脏链,将修改写入磁盘 |
| 刷盘后 | 从脏链移除,变为干净页,仍保留在 LRU 链上 |
用于存放重做日志的内存缓冲区。系统产生的日志不会直接写入磁盘,而是先存入日志缓冲区,以此来避免频繁的刷盘影响性能。
相比与数据缓冲区,日志缓冲区具备连续写特点,IO优先级也更高。
主要存储一些数据字典信息,如模式信息、表信息、列信息、触发器信息等。数据库将将部分数据字典信息加载到缓冲区中,并采用LRU算法进行字典信息的控制。每次对数据库进行操作都要涉及数据字典信息,访问数据字典信息的效率直接影响了语句的执行效率,如果这些信息全在缓冲区中,则可以直接获取,否则需要进行I/O。
提供在执行SQL语句过程中所需要的内存,包括计划、SQL 语句和结果集缓存。很多应用当中都存在反复执行相同SQL语句的情况,此时可以使用缓冲区保存这些语句和它们的执行计划,带来的好处是加快了SQL语句执行效率,但同时给内存也增加了压力。
用于执行排序操作(ORDER BY、GROUP BY、DISTINCT、索引创建等)的专用内存区域。当用户执行SQL语句时,常常需要进行排序,所使用的内存就是排序缓冲区提供的。在每次排序过程中,都首先申请内存,排序结束后再释放内存。
当排序数据量较小,可以完全放入排序区时,可以在内存中完成,不需要磁盘IO,速度极快。
当排序数据量超过单个排序操作符的SORT_BUF_SIZE时,需要将内存内部分排序,然后写入临时文件,被称为归并段,最后从磁盘读取所有归并段,合并输出。需要多次磁盘IO,性能显著低于内存排序。
DM提供了为哈希连接而设定的缓冲区,不过该缓冲区是个虚拟缓冲区。之所以说是虚拟缓冲,是因为系统没有真正创建特定属于哈希缓冲区的内存,而是在进行哈希连接时,对哈希连接的数据量进行了计算。如果计算出的数据量大小超过了哈希缓冲区的大小,则使用DM创新的外存哈希方式;如果没有超过哈希缓冲区的大小,实际上还是使用内存池来进行哈希操作。
重做日志,又叫 REDO 日志,记录了所有物理页的修改,基本信息包括操作类型、表空间号、文件号、页号、页内偏移、实际数据等。数据库中 INSERT、DELETE、UPDATE 等 DML操作以及 CREATE TABLE 等 DDL 操作最终都会转化为对某些数据文件、某些数据页的修改。因此,在系统故障重启时,通过重做 REDO 日志,可以将数据库恢复到故障时的状态。
DM数据库在创建时会默认生成两个日志文件用来保存REDO日志这两个文件循环使用。任何数据页从内存缓冲区写入磁盘之前,必须保证其对应的 REDO 日志已经写入到联机日志文件。
在归档模式下,数据库在将 REDO 日志写入联机日志文件的同时(或之后),会将其复制到归档目录永久保存。当需要恢复时(如故障、误操作等),先还原备份集,然后按 LSN(日志序列号)顺序依次重演归档日志中的 REDO 记录,将数据恢复到目标状态。
归档方式有多种,详情如下:
本地归档
| 属性 | 说明 |
|---|---|
| 触发时机 | REDO 日志写入联机日志文件后,由归档线程执行 |
| 存储位置 | 本地磁盘目录 |
| 文件特性 | 不可覆盖,保留到用户删除或达到空间上限自动删除 |
| 数量限制 | 最多可配置 8 个本地归档路径 |
| 空间风险 | 磁盘空间不足会导致数据库挂起 |
实时归档
| 属性 | 说明 |
|---|---|
| 发送时机 | 主库在 REDO 日志写入联机日志文件之前,通过 MAL 系统发送到备库 |
| 备库响应 | 备库收到后即响应主库(高性能模式),无需等待重演完成 |
| 主库行为 | 收到确认后才将 REDO 写入联机日志文件 |
| 安全性 | 最高——备库不可用或没响应前,主库 REDO 日志不落盘 |
| 性能影响 | 主库性能受备库响应速度影响 |
| 备库数量 | 1~8 个实时备库 |
即时归档
| 属性 | 说明 |
|---|---|
| 发送时机 | 主库将 REDO 日志写入联机日志文件之后,再通过 MAL 系统发送到备库 |
| 备库响应 | 默认模式下,备库收到并重演完成后才响应主库 |
| 与实时归档区别 | 发送时机不同:实时在落盘前发送,即时在落盘后发送 |
| 应用场景 | 读写分离集群的实现基础 |
| 默认模式 | 事务一致模式(ARCH_WAIT_APPLY=1) |
异步归档
| 属性 | 说明 |
|---|---|
| 触发方式 | 通过定时器触发,在固定时间点或间隔一定时间后发送 |
| 前置条件 | 必须有本地归档 |
| 数据延迟 | 存在一定延迟,对系统压力最小 |
| 应用场景 | 异地灾备,对实时性要求不高的场景 |
同步归档
| 属性 | 说明 |
|---|---|
| 发送时机 | 归档日志刷盘后,通过 MAL 系统发送到备库 |
| 备库响应 | 收到后同时开始重演和响应主库,不需要等待重演完成 |
| 特点 | 介于即时归档和异步归档之间:先落盘再发送,但没有时间等待间隔 |
远程归档
| 属性 | 说明 |
|---|---|
| 应用场景 | 专门用于 DMDSC(达梦共享存储集群) 环境 |
| 配置方式 | 双向配置,两个节点互相将远程归档配置在对方机器上 |
| 目的 | 使集群中所有节点都能访问完整的归档日志文件集 |
| 实现方式 | 将当前节点的远程归档目录设置为另一节点的本地归档目录 |
关于数据库的并发控制,如果通过锁机制来实现,会造成阻塞等问题,影响数据库并发性。使用多版本并发控制,允许数据有多个版本,通过事务号来决定当前事务看到的数据。这样的话数据库写操作不会阻塞读操作,读操作永远不会阻塞,并发度大幅提高。解决了脏读、不可重复读、幻读的问题(但是不能解决更新丢失问题)。
DM数据库提供了两种不同的实现方案,可以通过参数TRX_VIEW_MODE控制。
每行物理记录都包含两个额外字段:TID和RPTR。TID为上一个修改该记录的事务号。RPTR指向存储在回滚段中上个的Undo Log记录,以此形成一个链式版本结构。
达梦通过活动事务表来确定事务的可见性。事务隔离等级不同,活动事务表创建的时机不同,在串行化下事务启动创建,读提交是在语句执行时创建。
活动事务表会在创建的那一刻收集当前所有活动的事务,以及此时系统创建的下一个事务号NEXT_TID。
如果当前记录不可见,则按照RPTR找到上一个版本,并再次进行判断,直到找到可见版本为止。
该方法实现较为简单,但是在查找历史版本的时候可能会遍历较长的版本链。
达梦数据库的事务隔离级别:
1.读未提交:事务可以读取其他事务未提交的数据。一般不会使用。
2.读已提交:事务只能读取其他事务提交了的数据。优先保证了并发性能和可用性。
3.串行化:跟读已提交隔离级相比,串行化事务的查询本身不会增加任何代价,但修改数据时,如果此事务开始后被其他事务修改,会引发“串行化事务被打断”错误。
事务默认采用 INI 参数 ISOLATION_LEVEL 指定的隔离级,该参数可取值 1 或 3,1 表示读提交隔离级,3 表示串行化隔离级,默认为 1。可以执行
SET TRANSACTION ISOLATION LEVEL + 隔离等级 来设置后续的第一个事务的隔离等级。
全局维护一个数组CMTARR,其下标是事务ID,数组元素存储的是该事务的提交时间戳,长度由参数TRX_CMTARR_SIZE(单位百万)控制。事务启动时获取一个时间戳SNAP_CMTSEQ,提交时将CMTARR的对应位置的值改为当前时间。通过比较版本的时间戳和事务的快照时间戳来判断可见性。比如SNAP_CMTSEQ >= CMTSEQ,说明当前数据在事务启动时已经提交了,可见,否则不可见。
该模式也有TID和RPTR,如果不可见的话就会根据RPTR找到更早的版本,得到TID后去CMTAARR查看时间戳,再次进行比较,直到找到可见版本为止。
文章
阅读量
获赞
