我正在参加「掘金·启航计划」
原文: https://dsf.berkeley.edu/papers/fntdb07-architecture.pdf
本文主要讨论DBMS的体系结构,包括进程模型、并行架构、存储系统设计、事物系统实现、查询处理器和优化器架构,以及常见的共享组件和工具。
1 介绍
数据库系统是最早广泛部署的在线服务器系统之一,因此,它开创了不仅跨越数据管理,而且跨越应用程序、操作系统和网络服务的设计解决方案。早期的dbms是计算机科学中最具影响力的软件系统之一,为dbms开创的思想和实现问题被广泛复制和重新发明。
1.0 DBMS的架构——DBMS的主要组件
DBMS的主要组件
DBMS的5大组件:
- 客户端通信管理器(Client Communication Manager)
- 进程管理器(Process Manger)
- 查询处理器(Relation Query Processor)
- 事物性存储管理器(Transanctional Stroage Manager)
- 共享组件和工具(Shared Components and Utilities)
1.1 关系型系统:一条查询的执行过程
-
建立连接。调用者调用客户端API,客户端通过网络与服务端(的Client Communications Manager)通信.
-
Client Communication Manager
的主要功能:- 为调用者建立连接并维护调用者的状态;
- 响应调用者的SQL命令,然后返回正确的数据和控制信息;
-
-
分配计算线程。在接收到客户端的第一个SQL命令之后,DBMS必须分配一个“计算线程”来执行SQL命令。还需要保证线程的数据和控制输出能够通过 Communication Manger 发送给客户端。
-
Process Manager
的主要功能:- 为收到的SQL指令分配计算线程。DBMS在此阶段要做的最重要的决定是关于准入控制( admission control)的——即系统是否应该立即处理查询,或延迟执行直到有足够的系统资源可以用于此查询。
-
-
执行查询。一旦确定并分配了一个计算线程,就可以执行查询了。
-
Relation Query Processor
:-
检查用户是否有权限执行查询,
-
然后将用户的SQL编译为内部的 查询计划(internal query plan) 。
-
一旦编译完成,**计划执行器(plan executor)**会执行该计划。
计划执行器由一组
operators
组成,它可以执行任何查询。通常operators
实现的关系型查询处理任务包括joins,selection,projection,aggregation,sorting等,以及从系统的较低层请求数据记录的调用。
-
-
-
从数据库中获取数据。需要通过 Transaction Storage Manager 才能获取数据
-
Transanctional Storage Manager
:-
它管理所有的**数据访问(read) 和操控(create,update,delete)**调用。operators从Transactional Storage Manager获取数据。
存储系统包括组织和访问磁盘上数据(access methods)的算法和数据结构,
- 包括像表和索引这样的基础数据结构
- 它还包括缓存管理模块( buffer manager)。
在访问数据之前,需要先从 lock manager 获取锁,以保证并发查询时的正确执行。
如果查询需要更新数据库,还需要 log manger 交互,以保证事物在提交后是持久的,在取消后是可撤销的。
-
-
-
返回数据,事务结束,关闭连接。访问数据记录,计算最终结果并返回给客户端。
- access methods 将控制返回给查询执行器的 operators,它编排数据库数据的计算;当结果生成后,会被放到 client communications manager 缓存中,它会将结果返回给调用者。
上面的例子涉及了RDBMS的多个核心组件,但不是所有。catalog 和 memory managers 会在事物中被调用。catalog在认证、解析和查询优化时会被查询处理器使用。memory manager 只要在DBMS需要动态分配或取消分配内存时就会使用。
1.2 讨论范围和概览
本文主要关注的是支持数据库核心功能的基础架构:
- 进程架构
- DBMS特定领域的组件
- 存储架构和事物存储架构设计
- 常见DBMS中的共享组件和工具
2 进程模型
当设计任何一个多用户服务时,早期要决定 如何执行并发用户请求以及 如何将这些请求映射到操作系统进程或线程。
我们先从一个简单的框架开始,假设操作系统对于线程的支持很好,且只关注单处理器系统。接下来的讨论基于下面的定义:
- 一个操作系统进程由多个操作系统线程和进程私有地址空间组成。为进程维护的状态包括操作系统资源和安全的上下文。线程由操作系统内核调度,每个进程有其唯一的地址空间。
- 操作系统线程是操作系统程序的执行单元,没有私有操作系统上下文和私有地址空间。操作系统线程由内核调度。
- Lightweight Thread (轻量级线程,LWT)支持在单个进程中存在多个线程。不像操作系统线程由内核调度,这些线程由应用程序调度。LWT在用户空间中调用,操作系统线程在内核空间中调度。
- DBMS Client是一个软件,实现了用于应用程序与DBMS通信的API。
- DBMS worker 是DBMS中执行的线程,它代表DBMS客户端工作。DBMS worker和DBMS 客户端之间是一一对应的:DBMS worker处理所有来自单个DBMS 客户端的SQL请求。
2.1 单处理器(Uniprocessors)和轻量级线程(LWT)
我们以两个简单的假设开始(后面会放宽假设):
- 操作系统线程支持:假设操作系统对内核线程提供了高效的支持,且一个进程可以有大量线程。我们还假设每个线程的内存开销很小,且上下文切换和便宜。
- 单处理器硬件: 假设我们是为只有单CPU的单个机器设计的。
在此简化的环境下,DBMS有三个自然而然的进程模型可选。总最简单到最复杂,依次是:
- 每个(DBMS)worker一个进程
- 每个(DBMS) worker一个线程
- 进程池
尽管这些模型都被简化了,它们三个都在商业DBMS中有应用。
2.1.1 每个worker一个进程(Process per DBMS Worker)
Fig. 2.1 Process per DBMS worker model: each DBMS worker is implemented as an OS process.
由操作系统对DBMS workers进行管理,DBMS程序员可以依靠操作系统的保护设施来隔离标准错误,如内存超限。
优点:
- 易于实现。workers 直接映射到操作系统进程。
- 各种编程工具可以使用。如调试器、内存检查器。
缺点:
- 多个DBMS连接之间需要 共享内存数据结构。包括lock table 和 buffer pool。共享的数据结构必须由操作系统显示的分配,需要操纵系统支持和一些特殊DBMS编码。在实践中,共享内存降低了地址空间分离的优势。
- 对于大量并发连接的 扩展性不好。进程需要维护的信息很多,因此大量进程需要消耗更多的内存。
2.1.2 每个worker一个线程
Fig. 2.2 Thread per DBMS worker model: each DBMS worker is implemented as an OS thread
单个多线程进程管理所有的DBMS worker 活动。调度器线程监听新的DBMS client连接。每个连接分配一个新的线程。
优点:
- 对于并发的扩展性好。
缺点:
- 多线程编程的缺点它都有。难于调试,竞争条件;各个操作系统的线程接口不同,跨平台有问题。
2.1.3 进程池
此模型是“每个worke一个进程”的一个变体。有了进程池,不是每个worker分配一个进程,进程由进程池管理。每个client分配一个进程。当SQL执行完成后,客户端收到结果,进程会回收到进程池,等待分配给下一次请求。如果一个请求来了,但是进程池空了,那么该请求需要等待有进程可用。
进程池的大小通常是动态的,通常其大小与并发请求数有关。
优点:
- “每个worker一个进程” 有的它都有
- 需要的内存更小,更高效
2.1.4 共享数据和进程边界
以上介绍的模型的目的都是 尽可能独立地执行并发请求。然而, DBMS worker完全的独立和隔离是不可能的,因为它们需要操作同一个数据库。在这三个模型中,数据需要从DBMS移动到客户端。这暗指,所有的SQL请求需要移动到服务端进行,且所有的返回的结果需要从服务端移回客户端。如何移动?简单来说就是使用各种 缓冲。两种重要的缓冲类型是:
-
* 磁盘I/O缓冲(disk I/O buffers) :*最常见的跨worker数据依赖是对共享数据存储的读取和写入。于是,worker之间的I/O交互很常见。有两种独立的磁盘I/O场景需要考虑:
-
数据库I/O请求(Database I/O Requests): 缓冲池(The Buffer Pool)。 所有持久化的数据库数据都需要通过 DBMS buffer pool暂存。
- 在 “每个worker一个线程 ” 模型中,缓冲池分配在堆上,DBMS地址空间中的所有的线程都可以访问
- 在另外两种模型中,缓冲池分配在所有进程可以访问的共享内存中。
所有模型中的最终结果是,缓冲池是一个大的共享数据结构,所有数据库线程或进程都可以访问。
缓冲“读”
-
日志请求(Log I/O Requests):
The Log Tail
。数据库日志是存在一个或多个磁盘上的一组条目。所有日志条目都是在事物处理过程中生成的,它们暂存在内存队列中,被周期性的按FIFO顺序写入到日志磁盘中。这个队列通常叫做log tail
。在很多系统中,有一个分离的进程或线程负责周期性地将log tail
写入到磁盘中。-
在 “每个worker一个线程” 模型中,
log tail
分配在堆上 -
在另外两个模型中,有两种常见的设计方法:
-
使用一个独立的进程管理日志。日志记录通过共享内存或其他高效的进程间通信协议与日志管理器通信。
-
与上面处理缓冲池的方式类似,
log tail
在共享内存中分配。关键的一点是,所有执行数据库客户端请求的线程和/或进程都需要能够请求写入日志记录并刷新log tail
。一种重要的 log flush 类型是
commnit transaction flush
。事务只有在提交日志记录被写入到日志设备之后才能被报告为成功提交。
-
缓冲“写”
-
-
-
* 客户端通信缓冲(client communication buffers) *:SQL通常以“pull”模式使用:客户通过反复发出SQL FETCH请求,从查询游标中消费结果记录,每次请求检索一个或多个记录。大多数DBMS试图在FETCH请求流之前工作,以便在客户端请求之前排定结果。
为了支持这种预取行为,DBMS worker可以使用客户端通信socket作为结果集队列。更复杂的方法是实现客户端游标缓存,并使用DBMS客户端来存储可能在不久的将来被获取的结果,而不是依赖操作系统的通信缓冲区。
其他共享的数据:
- 锁定表(Lock table) 。锁定表由所有DBMS worker 共享,并由**锁定管理器(Lock Manager)**用来实现数据库的锁定语义。共享锁表的技术与缓冲池的技术相同,这些技术也可以用来支持DBMS实现所需的任何其他共享数据结构。
2.2 DBMS 线程
由于遗留、可移植性和可扩展性的原因,大多数DBMS并不基于操作系统线程实现。那些使用“thread per DBMS worker” 模型的,需要一个不使用操作系统线程的解决方案。其中一个方案是:自己实现轻量级的线程。
2.3 标准实践
以上介绍的三种架构(及变体)在现实的DBMS中都有使用。如 IBM DB2支持4种进程模型:
- 如果OS对线程的可扩展性支持的很好,DB2默认使用 thread per DBMS worker 模型,也可以选择 thread pool 模型
- 如果OS对线程的可扩展性支持的不好,DB2默认使用 process per DBMS worker 模型,也可以选择 process pool 模型。
现在来总结一下IBM DB2,MySQL,Oracle , PostgreSQL 和 Microsoft SQL Server 支持的进模型:
-
Process per DBMS worker:
这是最直接的模型,依然被很多数据库使用。
- DB2在对线程扩展性支持不好的OS上的默认模型
- Oracle的默认模型
- PostgreSQL支持此模型
-
Thread per DBMS worker:
此模型的两个主要变体是:
-
OS thread per DBMS worker:
- DB2在对线程扩展性支持很好的OS上的默认模型
- MySQL使用的模型
-
DBMS thread per DBMS worker:
在此模型中,DBMS worker 由调度器调度,要么调度到OS processes上,要么调度到OS threads上。
此模型有两个主要的子类:
-
DBMS threads 被调度到 OS process 上:
- Syhbase 支持此模型
-
DBMS threads 被调度到 OS threads 上:
- Microsoft SQL Server支持此模型
-
-
-
Process/thread pool
-
process pool
- Oracle 支持此模型
-
thread pool
- Microsoft SQL Server在大多数机器上的默认模型
-
大多数现代商用DBMS都支持内部查询并行(intra-query parallelism):将单个查询分成多个部分,在多个处理器上并行执行。内部查询并行就是将单个SQL查询分配给多个DBMS worker去执行。底层的进程模型不会因此受到影响。
2.4 准入控制(Admission Control)
随着吞吐量的增加,DBMS由于内存压力会出现抖动:无法将数据库页的“工作集”保留在缓冲池中,并且所有时间都花在了页替换上。
什么情况下会造成抖动?
- sorting 和 hash joins 消耗大量内存
- 锁争用: 事物之间相互死锁,需要回滚和重新启动
如何防止抖动?
准入控制机制——只有在DBMS资源足够时才接收新的工作。有了一个好的准入控制器,系统在过载的情况下会优雅的退化:事物延迟将与到达率(arrival rate)成比例地增加,但吞吐量将保持在峰值。
DBMS 中的准入控制可以在两层中进行:
-
第一层:dispathcer process 保证客户端连接数在阈值之下。这样可以防止过度消耗网络连接资源。
-
第二层:在DBMS
relational query procecssor
中实现。准入控制在查询被解析和优化后执行,并决定查询是被推迟,或以少量资源执行,还是无约束地执行。优化器会估计查询需要的资源和当前系统中的可用资源,这些信息为提供给准入控制器。
3. 并行架构: 进程和内存协调(Memory Coordination)
本章讨论每个并行架构的中进程模型和内存协调问题
3.1 共享内存(Shared Memory)
共享内存架构:所有处理器可以以大致相同的性能访问同一个RAM和磁盘。
第2章中的3中模型都可以在共享内存架构上很好地运行。在共享内存机器上,OS对于跨处理器分配worker(processes 或 thread)是透明的。
此架构的主要的挑战是修改查询层,使其能够利用多个CPU对单个查询并行执行。
3.2 无共享(Shared-Nothing)
无共享并行系统由一组独立的机器组成,它们之间通过网络进行通信。各个机器之间无法直接访问内存或磁盘。
无共享系统没有提供硬件共享抽象,需要DBMS来调度各个机器。DBMS采用的最常见的技术是在每个机器或节点上运行各自的标准进程模型。每个节点都能够接收客户端的SQL请求,访问必要的元数据,编译SQL请求,并进行数据访问,就像在单个机器上一样。主要的不同的是:集群中的每个系统只存储部分数据。查询请求不只查询本地数据,还会被发送到集群中的其他相关节点上,所有相关节点都需要执行查询。这些表使用水平数据分区分布在集群中的多个系统上,每个处理器可以独立于其他处理器执行。
有哪些数据分区方案?
- hash-based
- range-based
- round-robin
- range-based + hash-based
无共享架构的问题:
-
部分失效:一个处理器失效会导致整个机器失效,因此DBMS也会失效。虽然单个节点失效不会影响其他节点,但会对整体DBMS的行为产生影响。
3种解决方法:
-
一个节点失效,就关闭所有节点。(本质上模仿的是共享内存架构)
-
“Data skip” ——跳过故障节点上的数据
- 适用于: 数据的可用性 > 结果的完整性
-
冗余
-
无共享架构的优点:
- 扩展性好
- 便宜
应用场景:
- 决策支持系统
- 数据仓库
3.3 共享磁盘(Shared Disk)
共享磁盘架构:所有的处理器可以以大致相同的性能访问同一个磁盘,但不能访问各自的RAM。
优点:
- 管理成本低。不用考虑对数据进行分区
- 单个节点失效不会影响其他节点访问数据库
缺点:
-
单点故障。
-
跨机器的数据共享需要显示调度
- 基于分布式锁管理器
- 需要缓存一致性协议来管理分布式缓冲池
这些都是很复杂的组件,易产生竞争,成为系统的瓶颈。
3.4 NUMA
NUMA(* Non-Uniform Memory Access (NUMA,非统一内存访问)*
)在内存独立的集群上提供了一个共享的内存编程模型(把多个独立的机器上的内存看成一个内存)。集群中每个节点可以快速访问本地内存,而远程内存访问需要通过集群中的高速互联通道(存在一些延迟)。此架构名称的来源就是 内存访问时间不统一。
NUMA架构介于无共享和共享内存之间。
NUAM集群已经消失了。
3.5 DBMS线程和多处理器
在“thread per DBMS worker”模型中,所有线程在单个进中运行,单个进程一次只能在单个处理器上执行。因此,在多处理器系统中,DBMS只会使用单个处理器,其他处理器会闲置。
当在多个进程中运行DBMS线程时,有时会出现一个进程承担了大部分工作,而其他进程(也就是处理器)处于空闲状态。为了使这种模式在这些情况下能很好地工作,DBMS必须在进程之间实现 线程迁移(migration) 。从6.0版本开始,Informix在这方面做得很好。
当把DBMS线程映射到多个操作系统进程时,需要决定采用多少个操作系统进程,如何把DBMS线程分配给操作系统线程,以及如何在多个操作系统进程中分配。一个好的经验法则是 每个物理处理器分配一个进程。这可以最大限度地提高硬件中固有的物理并行性,同时最大限度地减少每个进程的内存开销。
3.6 标准实践
关于对并行的支持,流行的方式与上一张类似:大多数主要的DBMS支持多种并行模型。由于共享内存系统(SMP、多核系统和两者的组合)在商业上的流行,所有主要的DBMS供应商都对共享内存的并行性提供了良好的支持。我们看到支持的分歧是在多节点集群并行中,广泛的设计选择是共享磁盘和无共享。
- 共享内存:大多数主要的DBMS都支持,包括:IBM DB2,Oracle 和 Microsoft SQL Server
- 无共享:IBM DB2, Informix, Tandem, 和 NCR Teradata
- 共享磁盘:Oracle RAC, RDB , 和 IBM DB2
4. 关系型查询处理器
查询处理器(query processor)的作用:接收一个SQL语句,验证SQL,将SQL优化为查询计划,然后执行该查询计划。客户端获取(pull/fetch)查询的结果,通常是一次获取或一批一批地获取。
以下讨论的是常见的SQL:DML语句(增删改查)。而不是DDL语句。查询优化器不处理DDL; DDL通常是静态的DBMS逻辑,通过显示调用存储引擎和catalog manager。
查询处理器的主要组件:
- 解析器:解析查询语句、验证执行权限
- 重写器:简化和标准化查询
- 优化器:生成查询计划
- 执行器:执行查询计划
4.1 查询解析和授权
对于一个SQL语句,SQL**解析器(parser)**通常需要执行以下处理:
-
检查表名是否正确
-
解析名称和引用
-
将查询转换为优化器使用的内部格式
-
验证用户是否有权限执行查询
- 验证用户是否对表、用户自定义函数、或查询中引用的其他对象有权限。
- 有些系统会将权限检查放到查询计划执行的时候做。如支持行级安全的系统,因为只有在执行时才能基于值做安全检查。
在解析和验证通过之后,生成查询的内部格式。
4.2 查询重写
查询重写模块(重写器,rewriter) 负责简化和标准化查询,而不改变其语义。大多数重写实际上是对查询的内部表示的操作,而不是对原始SQL语句的。查询重写模块的输出通常是查询的内部表示。
在大多商用数据库中,查询重写是一个逻辑组件,一般在查询解析后,或查询优化前执行。不论如何,将查询重写与其他模块分离是很有用的。
重写器的主要职责有:
-
扩展视图(View expansion): 将视图替换为视图引用的表、谓词或列。
-
简化常量数学表达式。如,将
R.x 重写为
R.x
-
逻辑重写谓词(predicates): 基于WHERE语句中的谓词和常量进行逻辑重写。
如,将 NOT
Emp.Salary > 1000000
重写为Emp.Salary 。
将
Emp.salary 1000000
重写为FALSE
;甚至还可以从一个谓词转换为另一个谓词,
R.x 转换为
AND S.y 。
目的:
- 提升优化器的性能,使其选择更好的查询计划
-
语义优化: 当表上的限制与查询谓词不兼容时,语义优化可以避免执行查询。
例子,消除冗余的join。
SELECT Emp.name, Emp.salary FROM Emp, Dept WHERE Emp.deptno = Dept.dno
重写为:
SELECT Emp.name, Emp.salary FROM Emp
-
子查询展开和其他启发式重写:
优化器是DBMS中最复杂的组件。为了保持这种复杂性的有限,大多数优化器只在单个SELECT-FROM-WHERE 查询块上运行,而不是跨块进行优化。因此,许多系统不会使优化器复杂化,而不是重写查询,使其更符合优化器。这种形式转换有时叫做 查询规范化(query normalization) 。
- 一类例子就是将查询重写为语义上相等的规范形式,语义相等的查询会被优化生成相同的查询计划
- 另一个重要的启发式方式是尽可能地展开嵌套查询,以最大限度地为查询优化器的单块优化提供计划。
4.3 查询优化器
**查询优化器(query optimizer) 的主要任务是将内部的查询表示转换为一个高效的查询计划(query plan)。**一个查询计划可以被认为是一个查询运算符(query operator)组成的图,表数据会流过该图。
查询计划有多种表示方式:
-
机器码
-
可解释(interpretable)的数据结构 (为了跨平台)
- 轻量的对象
- 低级的“操作码”语言。与Java的字节码的思想类似
- 类代数(algebra-like)
所有的DBMS都为Selinger的论文中提到的查询计划做了扩展,主要的扩展有:
- 计划空间(Plan space)
- 选择性估计(Selectivity estimation)
- 搜索算法(Search Algorithms)
- 并行(Parallelism)
- 自动调优(Auto-Tuning)
4.4 查询执行器
**查询执行器(query executor)**用于执行查询计划。查询计划通常是一个数据流的有向图,其中节点是operators(包含要访问的表和各种查询算法)。
在某些系统中,这个图被优化器编译为操作码,这时执行器的作用就是一个运行时 解释器。
在其他系统中,执行器的输入是一个图,它会 递归的调用程序来执行operators。
大多数现代查询执行器采用的是迭代器模型。
执行器的运行模型:
-
迭代器模型(iterator model):迭代器的输入就是数据流图中的边。查询计划中的每个operators都是迭代器的子类。
-
特点
-
面向对象模式
-
迭代器的逻辑与其父亲和孩子相互独立
-
数据流和控制流耦合在一起
-
单线程架构。只需要使用单个线程来执行这个查询图。
- 实现简洁、易于调试
- 在单系统(非集群)中查询效率高
-
-
4.5 访问方法
访问方法(Access Methods)是一个管理程序,管理各种基于磁盘的数据结构的访问。这些通常包括无序的文件("堆"),以及各种索引。所有主要的商业系统都实现了堆和B+树索引。
Access Methods提供的基础API:
- init():接收一个“搜索参数”——SARG,SARG为空表示全表扫描,SARG为列可能会使用索引。
- next():用于获取数据,如果next() 返回NULL,则说明没有满足条件的数据了。
Q: 为什么要向 access method 层传递SARGs?
- 像B+树这样的index access methods需要SARGs来高效执行
- 性能问题:它更适合堆扫描和索引扫描
- 所有查询逻辑都在access methods 层中完成,使存储引擎与关系引擎之间的边界清晰,性能更好
Q: 索引如何与数据表(base 表)中的行关联?
-
RID(row ID):直接指向base表中数据的物理磁盘地址。
-
优点
- 块
-
缺点
- base表的行移动很麻烦,因为需要更新所有的二级索引。查询和更新的成本都很高
-
Q: 如何解决行移动的问题?
- DB2: forwarding pointer:需要多一次IO找到移动后的page,但不用更新二级索引。
- 使用B+树作为主存储:使用主键代替物理地址。损失了二级索引访问base表的性能,但避免了行移动导致的问题。
4.6 数据仓库
数据仓库——用于决策支持的大型历史数据库,定期加载新的数据——需要专门的查询处理支持。
此主题之所以重要的两个原因:
- 数据仓库是DBMS技术的一个重要应用。
- 传统查询优化和执行引擎在数据仓库上效果不佳。因此,需要扩展或修改以提高性能。
Q: 为什么需要数据仓库?
- “商业分析”的需求出现。系型数据库主要是用于解决商业数据处理的需求,而数据仓库主要用于解决“商业分析”需求
Q: 数据仓库与OLTP的区别?
- 数据的本质不同。数据仓库处理的是的历史数据,而OLTP处理的是“现在”的数据
- 数据的schema不同。需要进行数据转换
Q: 数据仓库中的数据从哪里来?
- 从OLTP系统中获取数据,并将这些数据放到数据仓库中。可以使用ELT(extract,transform and load)系统。流行的ELT产品有:Data Stage 和 PowerCenter。
4.6.1 Bitmap Indexes
B+tree对快速插入、删除和更新记录做了优化。相反,数据仓库中存储的都是静态数据,只用加载一次数据即可。此外,数据仓库通常具有包含少量值的列。
Bitmap相对B+树的优点:
- 节省空间。方便存储列值数量较少的数据,如用户的性别,只有两个值。
- 数据过滤。多个bitmap取交集,可以很快地对数据进行过滤
缺点:
- 更新成本高。因此只在数据仓库中使用
在现在的产品中,bitmap一般作为B+树的一个补充存在。
4.6.2 Fast Load
通常数据仓库会在夜里加载白天的交易数据,原因有:
- 白天交易数据,晚上加载是一个很自然的策略
- 避免在用户交互的时候更新数据
Q: 为什么数据仓库的数据不能被并发加载(查询和加载同时存在)?
- 因为数据分析师分析数据时,通常要使用很多查询,这些 查询应该基于同一个数据集,如果允许查询的同时加载最近新生成的数据,那么可能会产生问题。
Q: 如何快速地批量加载数据?
-
批量加载器(bulk loader)。将大量数据以流的形式传到存储中,不会对SQL层造成压力,并且利用像B+中的那样的特殊的批量加载方法来获取数据。
这种方式比SQL插入快一个数量级,所有主要的厂商都提供了高性能的批量加载器。
Q: “实时的”数据仓库(应用: 电商和24小时零售)有哪些问题?
-
插入(来自批量加载器或事物)必须要设置写锁,这些锁会与读锁冲突,并且可能导致数据参仓库“冻结”。
-
跨查询集的兼容性问题。解决方法有:
- 避免就地更新并提供历史查询
- 提供快照隔离这样的MVCC隔离级别
4.6.3 物化视图(Materialized Views)
数据仓库通常非常大,多个大表的join查询很耗时。为了加速常用的查询,大多数厂商提供了物化视图。
与逻辑视图不同,物化视图采取的是可以查询的实际表,但它对应于真正的 "基础 "数据表上的逻辑视图表达。对物化视图的查询可以避免在运行时执行视图表达式中的join。而在数据更新时,物化视图必须保持最新状态。
物化视图使用的三个方面:
-
选择要物化的视图
-
维护视图的“新鲜”。两种方式:
- 更新表的时候更新物化视图
- 周期性地删除并重建物化视图
-
考虑在特别的查询中使用物化视图
需要在运行时开销和物化视图的数据一致性之间进行权衡
4.6.4 OLAP和特殊查询的支持
一些数据仓库有一些可预测的查询。例如,每个月底汇总一下各个部分的销售额。除了这些常规查询外,就是一些特殊的查询了,由业务分析师临时制定。
对于可预测的查询,可以构建物化视图来加速。更一般地说,由于大多数商业分析查询都要求汇总,我们可以计算出一个物化视图,它是每个商店的部门的总销售额。然后,如果指定了上述的区域查询,它可以通过 "滚动 "每个区域的各个商店来满足。
这种聚合通常