# 数据模型

在 HBase 中，数据存储在由行列构成的二维表中。这是与一个关系数据库（RDBMS）重叠的术语，但这不是一个有帮助的比喻。相反，将 HBase 表视为多维映射会很有帮助。



*HBase 数据模型术语*



**表**

HBase 表由多行组成。



**行**

HBase 中的一行由一个行键和一个或多个具有与之关联的值的列组成。在行存储时，顺序按照字母进行排序。因此，行键的设计非常重要。其目标是以相关行彼此靠近的方式存储数据。常见的行键模式是网站域名。如果您的行键是域名，则应该反向存储它们（org.apache.www，org.apache.mail，org.apache.jira）。这样，所有 Apache 域名都在表中彼此靠近，而不是基于子域名的第一个字母展开。



**列**

HBase 中的列由列族和列限定符组成，它们由`:`（冒号）字符分隔。



**列族**

出于性能原因，列族通常在物理上拥有一簇列及其值。每个列族都有一组存储属性，例如是否应将其值缓存在内存中，如何压缩其数据或对其行键进行编码等。表中的每一行都具有相同的列族，但给定的行可能不会在给定的列族中存储任何内容。



**列限定符**

列限定符被添加到列族中，以提供给定数据段的索引。给定列族`content`，列限定符可能是`content:html`，另一个可能是`content:pdf`。虽然列族在创建表时是固定的，但列限定符是可变的，并且行之间可能有很大差异。



**单元格**

单元格是行，列族和列限定符的组合，它包含一个值和一个时间戳，时间戳表示值的版本。



**时间戳**

时间戳与每个值一起写入，它是该值给定版本的标识符。默认情况下，时间戳表示写入数据时 RegionServer 上的时间，但是当您将数据放入单元格时，可以指定不同的时间戳值。



## 21.概念视角

在Jim R. Wilson的博客文章[了解 HBase 和 BigTable](http://jimbojw.com/#understanding%20hbase) 中，你可以阅读一篇非常容易理解的关于HBase数据模型的解释。另一个很好的解释可以在 Amandeep Khurana 的 [基本模式设计简介](http://0b4af6cdc2f0c5998459-c0245c5c937c5dedcca3f1764ecc9b2f.r43.cf2.rackcdn.com/9353-login1210_khurana.pdf)中找到。

通过阅读不同的观点可能有助于深入了解 HBase 架构设计。链接的文章所涉及到的内容与本节中的信息相同。

以下示例是 [BigTable](http://research.google.com/archive/bigtable.html) 论文第 2 页上的一个略微修改的形式。有一个名为`webtable`的表包含两行（`com.cnn.www`和`com.example.www`）和三个列族，名为`contents`，`anchor`和`people`。在此示例中，对于第一行（`com.cnn.www`），`anchor`包含两列（`anchor:cssnsi.com`，`anchor:my.look.ca`），`contents`包含一列（`contents:html`）。此示例包含具有行键`com.cnn.www`的行的 5 个版本，以及具有行键`com.example.www`的行的一个版本。 `contents:html`列限定符包含给定网站的整个HTML。 `anchor`列族的限定符每个都包含指向该行所代表的站点的外部站点的链接，以及它在其链接的`anchor`中使用的文本。 `people`列系列表示与该站点关联的人员。

> 列名
>
> 按照惯例，列名由其列族前缀和*限定符*组成。例如，列 *contents:html* 由列族`contents`和`html`限定符组成。冒号字符（`:`）从列族*限定符*中分隔列族。



*表 6. Table* `webtable`

| 行键 | 时间戳 | ColumnFamily `contents` | ColumnFamily `anchor` | ColumnFamily `people` |
| :-- | :-- | :-- | :-- | --- |
| “com.cnn.www” | T9 |  | `anchor:cnnsi.com = "CNN"` |  |
| "com.cnn.www" | T8 |  | `anchor:my.look.ca = "CNN.com"` |  |
| "com.cnn.www" | T6 | `contents:html = "<html>…"` |  |  |
| "com.cnn.www" | T5 | `contents:html = "<html>…"` |  |  |
| "com.cnn.www" | T3 | `contents:html = "<html>…"` |  |  |
| “com.example.www” | t5 | `contents:html = "<html>…"` |  | `people:author = "John Doe"` |



此表中看起来为空的单元格在 HBase 中不占用空间，或实际上不存在。这就是HBase“稀疏”的原因。表格视图不是查看 HBase 中数据的唯一方法，甚至也不是最准确的方法。以下表示与多维映射相同的信息。这只是一个出于演示目的的模型，可能并不完全准确。

```
{
  "com.cnn.www": {
    contents: {
      t6: contents:html: "<html>..."
      t5: contents:html: "<html>..."
      t3: contents:html: "<html>..."
    }
    anchor: {
      t9: anchor:cnnsi.com = "CNN"
      t8: anchor:my.look.ca = "CNN.com"
    }
    people: {}
  }
  "com.example.www": {
    contents: {
      t5: contents:html: "<html>..."
    }
    anchor: {}
    people: {
      t5: people:author: "John Doe"
    }
  }
} 
```

------

## 22.物理视角

虽然在概念级别，表可以看作一组稀疏的行，但在物理意义上它们是按照列族存储的。可以随时将新的列限定符（column_family:column_qualifier）添加到现有列族中。



*表 7. ColumnFamily*  `webtable`

| Row Key | Time Stamp | 列族`anchor` |
| --- | --- | --- |
| `"com.cnn.www"` | t9 | `anchor:cnnsi.com = "CNN"` |
| `"com.cnn.www"` | t8 | `anchor:my.look.ca = "CNN.com"` |

*表 8. ColumnFamily* `contents`

| Row Key | Time Stamp | ColumnFamily `contents:` |
| --- | --- | --- |
| `"com.cnn.www"` | t6 | `contents:html = "…​"` |
| `"com.cnn.www"` | t5 | `contents:html = "…​"` |
| `"com.cnn.www"` | t3 | `contents:html = "…​"` |

概念视角中显示的空单元格不占据物理存储空间。因此，在时间戳`t8`处对`contents:html`列的值的请求将不返回任何值。类似地，在时间戳`t9`处对`anchor:my.look.ca`值的请求将不返回任何值。但是，如果未提供时间戳，则将返回特定列的最新值。给定多个版本，最新版本也是第一个版本，因为时间戳按降序存储。因此，如果没有指定时间戳，则对行`com.cnn.www`中所有列的值的请求将是：来自时间戳`t6`的`contents:html`的值，来自时间戳`t9`的`anchor:cnnsi.com`的值，来自时间戳`t8`的`anchor:my.look.ca`。

有关 Apache HBase 如何存储数据的内部结构的更多信息，请参见 [regions.arch](#regions.arch) 。

-----

## 23.命名空间

命名空间是与关系数据库系统中的数据库类似的表的逻辑分组。这种抽象为即将出现的多租户相关功能奠定了基础：

*   配额管理（ [HBASE-8410](https://issues.apache.org/jira/browse/HBASE-8410) ） - 限制命名空间可以使用的资源数量（即区域，表）。

*   命名空间安全管理（ [HBASE-9206](https://issues.apache.org/jira/browse/HBASE-9206) ） - 为用户提供另一级别的安全管理。

*   区域服务器组（ [HBASE-6721](https://issues.apache.org/jira/browse/HBASE-6721) ） - 可以将命名空间/表固定到 RegionServers 的子集上，从而保证粗粒度的隔离级别。

### 23.1.命名空间管理

一个空间可以被创建，被删除或被更改。通过指定表单的完全限定表名，在表创建期间确定命名空间成员资格：

```xml
<table namespace>:<table qualifier> 
```



*例 7*

```
#Create a namespace
create_namespace 'my_ns' 
```

```
#create my_table in my_ns namespace
create 'my_ns:my_table', 'fam' 
```

```
#drop namespace
drop_namespace 'my_ns' 
```

```
#alter namespace
alter_namespace 'my_ns', {METHOD => 'set', 'PROPERTY_NAME' => 'PROPERTY_VALUE'} 
```

### 23.2.预定义的名称空间

有两个预定义的特殊命名空间：

*   hbase - 系统命名空间，用于包含 HBase 内部表
*   default - 没有明确指定名称空间的表将自动落入此名称空间



*例 8*

```
#namespace=foo and table qualifier=bar
create 'foo:bar', 'fam'

#namespace=default and table qualifier=bar
create 'bar', 'fam' 
```

----

## 24.表

表在模式定义时预先声明。

----

## 25.行

行键是无解释的字节。行按字典顺序排序，最低顺序首先出现在表中。空字节数组用于表示表的名称空间的开始和结束。

-----

## 26.列族

Apache HBase 中的列分组为列族。列族的所有列成员都具有相同的前缀。例如，列 *courses:history*和*courses:math*都是*courses* 列族的成员。冒号字符（`:`）从列族限定符中分隔列族。列族前缀必须由可打印字符组成。限定符的右部(列族限定符)可以由任意字节组成。列族必须在模式定义时预先声明，而列不需要在模式定义时定义，且可以在表启动和运行时动态变化。

从物理上讲，所有列族成员都存储在文件系统中。由于调优和存储规范是在列族级别完成的，因此建议所有列族成员具有相同的一般访问模式和大小特征。

-----

## 27.单元格

_{row，column，version}_ 它是一个元组并确切地指定了 HBase 中的`cell`。单元格内容是未解释的字节

---

## 28.数据模型操作

四个主要的数据模型操作是 Get，Put，Scan 和 Delete。通过[表](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Table.html)实例应用操作。

### 28.1.Get

[Get](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Get.html) 操作返回指定行的属性。Get操作通过 [Table.get](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Table.html#get-org.apache.hadoop.hbase.client.Get-) 执行

### 28.2.Put

[Put](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Put.html) 操作添加到表中的新行（如果键是新的）或者可以更新现有行（如果键已存在）。 Puts 通过 [Table.put](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Table.html#put-org.apache.hadoop.hbase.client.Put-) （非 writeBuffer）或 [Table.batch](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Table.html#batch-java.util.List-java.lang.Object:A-) （非 writeBuffer）执行

### 28.3.Scan 

[Scan](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Scan.html) 允许对指定属性的多行进行迭代。

下面是对表进行扫描的示例。假设一个表填充了具有键“row1”，“row2”，“row3”的行，然后另一组是具有键“abc1”，“abc2”和“abc3”的行。以下示例将展示如何设置 Scan 实例以返回以“row”开头的行。

```java
public static final byte[] CF = "cf".getBytes();
public static final byte[] ATTR = "attr".getBytes();
...

Table table = ...      // instantiate a Table instance

Scan scan = new Scan();
scan.addColumn(CF, ATTR);
scan.setRowPrefixFilter(Bytes.toBytes("row"));
ResultScanner rs = table.getScanner(scan);
try {
  for (Result r = rs.next(); r != null; r = rs.next()) {
    // process result...
  }
} finally {
  rs.close();  // always close the ResultScanner!
} 
```

请注意，通常，为扫描指定特定停止点的最简单方法是使用 [InclusiveStopFilter](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/filter/InclusiveStopFilter.html) 类。

### 28.4.Delete

[Delete](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Delete.html) 操作从表中删除一行。删除通过 [Table.delete](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Table.html#delete-org.apache.hadoop.hbase.client.Delete-) 执行。

HBase 不会修改现有数据，所以删除的操作是通过创建名为 *tombstones* 的新标记来处理删除操作。这些*tombstones* 标记以及删除的值在主要的压缩（major compactions）中得到了清理。

有关删除列版本的更多信息，请参阅 [version.delete](#version.delete) ，有关压缩的详细信息，请参阅[compaction](#compaction)。

----

## 29.版本

*{row，column，version}* 元组确切地指定了 HBase 中的`cell`。可以有一个无限数量的单元格，其中行和列相同但单元格地址仅在其版本维度上有所不同。

虽然行和列键表示为字节，但版本则使用长整数（long integer）类型指定。通常这个长时间类型包含时间实例，例如`java.util.Date.getTime()`或`System.currentTimeMillis()`返回的时间实例，即：*当前时间与 UTC 时间 1970 年 1 月 1 日午夜之间的差异（以毫秒为单位）*。

HBase版本维度按递减顺序存储，以便在从存储文件中读取时，首先找到最近的值。

在 HBase 中，`cell`版本的语义存在很多混淆。特别是：

*   如果对单元格的多次写入具有相同的版本，则只能读取最后写入的内容。

*   可以按非增加版本顺序写入单元格。

下面我们将介绍 HBase 中的版本维度当前是如何工作的。有关 HBase 版本的讨论，请参见 [HBASE-2406](https://issues.apache.org/jira/browse/HBASE-2406) 。HBase 中的[弯曲时间](https://www.ngdata.com/bending-time-in-hbase/)可以很好地读取 HBase 中的版本或时间维度。它提供了比这里更多的版本控制细节。

在撰写本文时，文章中提到的限制*覆盖现有时间戳的值*不再适用于 HBase。本节基本上是由 Bruno Dumon 撰写的概要。

### 29.1.指定要存储的版本数

为给定列存储的最大版本数是列架构的一部分，该值在表创建时指定，或通过`alter`命令，或通过`HColumnDescriptor.DEFAULT_VERSIONS`指定。在 HBase 0.96 之前，保留的默认版本数为`3`，但是在 0.96 中，更新版本已更改为`1`。



例 9.修改列族的最大版本数

> 此示例使用 HBase Shell 在列族`f1`中保留所有列的最多 5 个版本。您也可以使用 [HColumnDescriptor](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/HColumnDescriptor.html) 。
>
> ```shell
> hbase> alter ‘t1′, NAME => ‘f1′, VERSIONS => 5 
> ```
>



例 10.修改列族的最小版本数

>您还可以指定每个列族存储的最小版本数。默认情况下，此值设置为 0，表示该功能已禁用。以下示例通过 HBase Shell 将列族`f1`中所有列的最小版本数设置为`2`。您也可以使用 [HColumnDescriptor](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/HColumnDescriptor.html) 。
>
>```shell
>hbase> alter ‘t1′, NAME => ‘f1′, MIN_VERSIONS => 2 
>```
>



从 HBase 0.98.2 开始，您可以通过在 *hbase-site.xml* 中设置`hbase.column.max.version`，为所有新创建的列指定的最大版本数指定全局默认值。参见 [hbase.column.max.version](#hbase.column.max.version) 。

### 29.2.版本和 HBase 操作

在本节中，我们将查看每个HBase 核心操作的版本维度的行为。

#### 29.2.1.Get/Scan

Get是在 Scans 之上实现的。以下对 [Get](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Get.html) 的讨论同样适用于[扫描](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Scan.html)。

默认情况下，即如果未指定显式版本，则在执行`get`时，将返回版本具有最大值的单元格（可能是也可能不是最新编写的单元格，请参阅后面的内容）。可以通过以下方式修改默认行为：

*   要返回多个版本，请参阅 [Get.setMaxVersions（）](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Get.html#setMaxVersions--)

*   要返回最新版本以外的其他版本，请参见 [Get.setTimeRange（）](https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Get.html#setTimeRange-long-long-)

    要检索小于或等于给定值的最新版本，从而在某个时间点给出记录的“最新”状态，只需使用从 0 到所需版本的范围，并将最大版本设置为 1 。

#### 29.2.2.默认版本的Get示例

以下 Get 将仅检索行的当前版本

```java
public static final byte[] CF = "cf".getBytes();
public static final byte[] ATTR = "attr".getBytes();
...
Get get = new Get(Bytes.toBytes("row1"));
Result r = table.get(get);
byte[] b = r.getValue(CF, ATTR);  // returns current version of value 
```

#### 29.2.3.给定版本的Get示例

以下 Get 将返回该行的最后 3 个版本。

```java
public static final byte[] CF = "cf".getBytes();
public static final byte[] ATTR = "attr".getBytes();
...
Get get = new Get(Bytes.toBytes("row1"));
get.setMaxVersions(3);  // will return last 3 versions of row
Result r = table.get(get);
byte[] b = r.getValue(CF, ATTR);  // returns current version of value
List<KeyValue> kv = r.getColumn(CF, ATTR);  // returns all versions of this column 
```

#### 29.2.4.Put

执行 put 总是在某个时间戳创建`cell`的新版本。默认情况下，系统使用服务器的`currentTimeMillis`，但您可以在针对每一列指定版本（=长整数）。这意味着您可以在过去或将来指定时间，或者将long值用于非时间目的。

要覆盖现有值，请在与要覆盖的单元格完全相同的行，列和版本上执行 put。



**隐式版本示例**

HBase 将使用当前时间隐式地对以下 Put 进行版本控制。

```java
public static final byte[] CF = "cf".getBytes();
public static final byte[] ATTR = "attr".getBytes();
...
Put put = new Put(Bytes.toBytes(row));
put.add(CF, ATTR, Bytes.toBytes( data));
table.put(put); 
```

**显式版本示例**

以下 Put 具有显式设置的版本时间戳。

```java
public static final byte[] CF = "cf".getBytes();
public static final byte[] ATTR = "attr".getBytes();
...
Put put = new Put( Bytes.toBytes(row));
long explicitTimeInMs = 555;  // just an example
put.add(CF, ATTR, explicitTimeInMs, Bytes.toBytes(data));
table.put(put); 
```

注意：HBase 内部使用版本时间戳来处理生存时间计算等事情。通常最好避免自己设置此时间戳。首选使用行的单独时间戳属性，或将时间戳作为行键的一部分，或两者都使用。

#### 29.2.5.Delete

有三种不同类型的内部删除标记。请参阅 Lars Hofhansl 的博客，讨论他试图添加另一个，[扫描 HBase：前缀删除标记](http://hadoop-hbase.blogspot.com/2012/01/scanning-in-hbase.html)（Scanning in HBase: Prefix Delete Marker）。

*   删除：对于特定版本的列。

*   删除列：适用于列的所有版本。

*   删除系列：适用于特定 ColumnFamily 的所有列

删除整行时，HBase 将在内部为每个 ColumnFamily 创建一个tombstone（逻辑删除标记），而不是每个单独的列。

通过创建*tombstone*标记删除工作。例如，假设我们要删除一行。为此您可以指定版本，否则默认使用`currentTimeMillis`。这意味着 *删除版本小于或等于此版本的所有单元格*。 HBase 从不在原址修改数据，因此例如delete操作不会立即删除(或标记为已删除)存储文件中与delete条件相对应的条目。相反，标记一个tombstone标志，它将屏蔽删除的值。当HBase进行主要压缩时，将处理tombstone来实际删除值，同时删除tombstone标记。如果删除行时指定的版本大于行中任何值的版本，则删除整行。

有关删除和版本控制如何交互的信息性讨论，请参阅用户邮件列表中的思路 [Put w / timestamp→Deleteall→Put w / timestamp failed](http://comments.gmane.org/gmane.comp.java.hadoop.hbase.user/28421) 。

有关内部 KeyValue 格式的更多信息，另请参见 [keyvalue](#keyvalue) 。

除非在列族中设置`KEEP_DELETED_CELLS`选项，否则在下一次主要压缩存储期间将清除删除标记（请参阅[保留已删除的单元格](#cf.keep.deleted)。要将删除保留一段可配置的时间，可以通过 *hbase-site.xml* 中的 `hbase.hstore.time.to.purge.deletes` 属性设置删除 TTL。如果未设置`hbase.hstore.time.to.purge.deletes`或设置为 0，则在下一次主要压缩过程中将清除所有删除标记，包括将来具有时间戳的标记。否则，将保留具有将来时间戳的删除标记，直到在标记的时间戳加上`hbase.hstore.time.to.purge.deletes`的值所表示的时间之后发生的主要压缩，以毫秒为单位。

>  HBase 0.94中此行为表示对引入的意外更改的修复，在 [HBASE-10118](https://issues.apache.org/jira/browse/HBASE-10118) 中得到修复。该变更已被移植到 HBase 0.94 和更新的分支机构。

### 29.3.HBase-2.0.0 中的可选新版本和删除行为

在`hbase-2.0.0`中，操作员可以通过将列描述符属性`NEW_VERSION_BEHAVIOR`设置为 true 来指定备用版本和删除处理（要在列族描述符上设置属性，必须先禁用该表，然后更改列族描述符）;有关编辑列族描述符属性的示例，请参阅[保留已删除单元格](#cf.keep.deleted)。

“新版本行为”撤消了下面列出的限制，即如果在同一位置，`Delete`操作总是屏蔽`Put`操作（在相同的行，列族，限定符和时间戳，而无论哪个首先到达）。版本统计也会更改，因为已删除的版本被视为总版本计数。这样做是为了确保在主要的压缩过程中不会改变结果。有关讨论请参阅`HBASE-15968`以及链接的问题。

目前使用这种新配置运行成本;每次比较时，我们都将计算单元MVCC分解，从而消耗更多的CPU。在测试中，我们发现性能下降在 0％到 25％之间。

如果复制，建议您使用新的串行复制特性（请参阅`HBASE-9465`;串行复制功能未进入`hbase-2.0.0`但应该在后续的 hbase-2.x 版本中发布），就像现在一样，突变到达的顺序是一个关键因素。

### 29.4.目前的限制

以下限制在 hbase-2.0.0 中得到解决。请参阅上面的部分，[可选新版本和 HBase-2.0.0中的删除行为](#new.version.behavior)。

#### 29.4.1.Deletes屏蔽Puts

Deletes操作屏蔽Puts操作，甚至Puts操作发生在Deletes操作之后。参见 [HBASE-2256](https://issues.apache.org/jira/browse/HBASE-2256) 。请记住，删除会写一个tombstone（逻辑删除标记），这个tombstone只有在下一次主要压缩运行后才会消失。假设你删除了所有内容⇐T。在此之后你运行了一个带有时间戳⇐T 的新 put。这个 put，即使它发生在删除之后，也会被删除tombstone标记屏蔽。同时执行put操作并不会失败，但是当执行get时，您会注意到put操作没有作用。在主要压缩运行后它将再次开始工作。如果您对行添加使用始终增加的版本，则这些问题不应成为问题。但即使你不关心时间，它们也会发生：在完成删除并立刻进行put操作时，它们两个操作有可能在同一毫秒内发生。

#### 29.4.2.主压缩会更改查询结果

*...在 t1，t2 和 t3 创建三个单元版本，最大版本设置为 2.因此，在获取所有版本时，仅返回 t2 和 t3 处的值。但是如果你在 t2 或 t3 删除了版本，t1 上的版本将再次出现。显然，一旦主压缩运行，这样的行为将不再存在......（参见 [ HBase中的弯曲时间](https://www.ngdata.com/bending-time-in-hbase/) 中的垃圾回收部分）*

## 30.排序顺序

 HBase 中所有数据模型的操作都将按照排序顺序返回数据。首先是行，然后是列族，后面是列限定符，最后是时间戳（反向排序，因此首先返回最新的时间戳）。

## 31.列的元数据

列族的内部 KeyValue 实例之外没有列的元数据存储。因此，尽管 HBase 不仅可以支持每行的大量列，而且还可以支持行之间的异构列，因此有必要跟踪列名。

获取列族存在的完整列集合的唯一方法是处理所有行。有关 HBase 如何在内部存储数据的更多信息，请参阅 [keyvalue](#keyvalue) 。

## 32.Joins

HBase是否支持join操作是发行版列表上的一个常见问题，这有一个简单的答案：它不能，至少在RDBMS支持它们的方式上是这样（例如，在 SQL 中使用 equi-joins 或 outer-joins）。如本章所述，HBase 中的读取数据模型操作是 Get 和 Scan。

但是，这并不意味着您的应用程序不支持等值连接功能，但必须自己实现。向HBase写入数据时有两种主要策略，一种是反规范化数据，另一种是在应用程序或MapReduce代码中有查找表并以此在HBase表之间进行连接（并且正如 RDBMS 所示，有几种策略可以根据大小而定这些表，例如，嵌套循环与散列连接）。那么哪种方法最好？这取决于你想要做什么，因此没有一个答案适用于每个用例。

## 33.ACID

参见 [ACID 语义](/acid-semantics.html)。更多的，可以参考Lars Hofhansl 写的一份关于[HBase中ACID](http://hadoop-hbase.blogspot.com/2012/03/acid-in-hbase.html)的笔记。