---
title: 索引列频繁 UPDATE 导致索引 MemTable Key 膨胀的验证与分析-OceanBase数据库使用指南
description: 了解OceanBase数据库在实际应用中关于索引列频繁 UPDATE 导致索引 MemTable Key 膨胀的验证与分析相关的常见问题和使用技巧，帮助您快速解决索引列频繁 UPDATE 导致索引 MemTable Key 膨胀的验证与分析的难题。
image: https://mdn.alipayobjects.com/huamei_22khvb/afts/img/A*OSPzQ6GUQF4AAAAAQHAAAAgAeiGDAQ/original
---
切换语言

- 中文站 - 简体中文
- International - English
- 日本站 - 日本語

划线反馈

# 索引列频繁 UPDATE 导致索引 MemTable Key 膨胀的验证与分析

更新时间：2026-08-25 06:56

适用版本： V1.4.x、V2.1.x、V2.2.x、V3.1.x、V3.2.x、V3.3.x、V4.0.x、V4.1.x、V4.2.x、V4.3.x、V4.4.x 内容类型：TechNote  

## 问题现象

当业务频繁 UPDATE 二级索引列时，索引表 MemTable 中会产生大量“死 key”（`INSERT→DELETE`），导致索引 Range Scan 读取行数远超实际有效行数，查询性能严重劣化。

## 问题原因

频繁 UPDATE 索引列时，每次 UPDATE 操作在索引表中会生成一个包含新值的 `INSERT` 记录，并生成一个删除旧值的 `DELETE` 记录。这些 `DELETE` 记录对应的旧索引键值在事务提交后成为“死 key”，它们会保留在索引的 MemTable 中，直到发生 Major Compaction 才会被物理回收。索引扫描（Range Scan）会遍历这些“死 key”，导致读取行数膨胀，性能下降。

## 关键信息

- **影响对象**：包含频繁更新列的二级索引。
 - **关键指标**：执行计划中的 `physical_range_rows` 远大于 `logical_range_rows`；SQL Audit 中的 `MEMSTORE_READ_ROW_COUNT` 或 `SSSTORE_READ_ROW_COUNT` 异常增高。
 - **核心机制**：索引的 MemTable 中堆积了 `INSERT→DELETE` 的 MVCC 记录。
 - **解决路径**：Major Compaction 可以回收“死 key”；转储（Minor Freeze）仅迁移数据，不回收。

## 问题的风险及影响

1. **查询性能劣化**：索引扫描需要遍历大量无效的“死 key”，增加了 I/O 和 CPU 开销，查询响应时间变长。
 2. **内存与存储压力**：“死 key”占用 MemTable 内存，转储后占用 SSTable 存储空间。
 3. **执行计划失真**：优化器基于 `physical_range_rows` 等统计信息可能做出次优选择。
 4. **监控指标误导**：`MEMSTORE_READ_ROW_COUNT` 等监控指标异常偏高，可能干扰性能问题定位。

## 适用版本

OceanBase 4.2.5.6 及受相同 MVCC 机制影响的其他版本。

## 解决方法

### 短期缓解

对于数据量较小的表，可以在查询中使用 `/*+ FULL(table) */` Hint 强制走全表扫描，避免低效的索引扫描。

### 中期处理

定期执行 Major Compaction（合并）以回收“死 key”，释放存储空间并恢复索引扫描性能。

```sql
-- 在 sys 租户下执行
ALTER SYSTEM MAJOR FREEZE TENANT = 'tenant_name';

```

合并后，建议清理 Plan Cache 以使优化器基于新的数据结构生成执行计划。

### 长期优化

1. 审视业务逻辑，避免对索引列进行高频更新。
 2. 考虑调整索引设计，将频繁更新的列从索引中移除。
 3. 如果业务允许，可将 UPDATE 模式改为 DELETE + INSERT。

## 规避方式

1. **索引设计阶段**：评估列更新频率，谨慎将高频更新列加入索引。
 2. **开发规范**：在代码审查中关注对索引列的 UPDATE 操作频率。
 3. **运维监控**：定期监控关键索引的 `physical_range_rows` 与 `logical_range_rows` 比值，以及 `MEMSTORE_READ_ROW_COUNT` 指标，提前发现膨胀趋势。
 4. **合并策略**：根据业务负载，规划并执行定期的 Major Compaction。

## 附录：测试验证详情

### 1. 测试环境

| 项目 | 值 |
| --- | --- |
| OceanBase 版本 | 4.2.5.6 |
| 租户 | mysql (tenant_id=1002) |
| 主表 tablet_id | 200012 |
| 索引表 tablet_id | 1152921504606846995 |
| 主表 table_id | 500037 |
| 索引表 table_id | 500038 |

### 2. 测试方案

#### 2.1 表结构

```sql
CREATE TABLE `stream_event_table_partition_v1` (
  `id` varchar(40) NOT NULL,
  `application` varchar(40) NOT NULL,
  `company_id` varchar(40) NOT NULL,
  `created_at` datetime DEFAULT NULL,
  `inqueue` bit(1) NOT NULL,
  `partition_key` varchar(40) NOT NULL,
  `timestamp` bigint(20) NOT NULL,
  `topic` varchar(40) NOT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `idx_application_timestamp` (`application`, `timestamp`)
) DEFAULT CHARSET = utf8mb4 ROW_FORMAT = COMPACT;

```

索引表 rowkey 结构为 `(application, timestamp, id)`。

#### 2.2 测试数据与操作

1. 插入 50 行数据，所有行的 `application = 'i18n-executor'`。
 2. 对其中 10 行数据执行 30 轮 UPDATE，每轮修改索引列 `timestamp` 的值。

```sql
UPDATE stream_event_table_partition_v1
SET inqueue=0, `timestamp`=UNIX_TIMESTAMP(NOW())*1000
WHERE id IN ('evt-00001-a1b2c3d4e5f6', ... , 'evt-00045-c3d4e503a1b2');

```

#### 2.3 验证 SQL

```sql
SELECT /*+ INDEX(eventtable0_ idx_application_timestamp) */
  eventtable0_.*
FROM stream_event_table_partition_v1 eventtable0_
WHERE eventtable0_.application = 'i18n-executor'
ORDER BY eventtable0_.timestamp ASC
LIMIT 500;

```

### 3. 各阶段验证结果

#### 3.1 UPDATE 压测后（数据在 MemTable 中）

- **索引表 MemTable 状态**：`btree_item_count=350`。其中：
     - 活 key（有效）：50 个。
     - 死 key（`INSERT→DELETE`）：300 个。
 - **执行计划关键指标**：
     - `physical_range_rows: 350` （索引 BTree 中的物理行数，含死 key）
     - `logical_range_rows: 50` （逻辑可见行数）
 - **SQL Audit 关键指标**：
     - `MEMSTORE_READ_ROW_COUNT: 400` （从 MemTable 读取的总行数）
     - 拆解：索引扫描 350 行 + 回表读主表 50 行 = 400 行。

#### 3.2 转储后（Minor Freeze）

- **执行命令**：`ALTER SYSTEM MINOR FREEZE TENANT = 'mysql';`
 - **效果**：MemTable 数据迁移至 SSTable，但“死 key”未被回收。
 - **SQL Audit 关键指标**：
     - `MEMSTORE_READ_ROW_COUNT: 0`
     - `SSSTORE_READ_ROW_COUNT: 400` （读取行数未减少，仅存储位置变化）

#### 3.3 合并后（Major Compaction）

- **执行命令**：`ALTER SYSTEM MAJOR FREEZE TENANT = 'mysql';`
 - **效果**：“死 key”被物理回收。
 - **执行计划关键指标**：
     - `physical_range_rows: 50` （恢复至与逻辑行数一致）
 - **SQL Audit 关键指标**：
     - `SSSTORE_READ_ROW_COUNT: 100` （大幅下降）
     - 拆解：索引扫描 50 行 + 回表读主表 50 行 = 100 行。

### 4. 全阶段对比总结

| 阶段 | 索引 `physical_range_rows` | MemStore Read | SSStore Read | 总读行数 |
| --- | --- | --- | --- | --- |
| UPDATE 压测后（MemTable） | 350 | **400** | 0 | 400 |
| 转储后（Minor SSTable） | 950* | 0 | **400** | 400 |
| 合并后（Major SSTable） | **50** | 0 | **100** | 100 |

*注：转储后 *`physical_range_rows`* 估算值升高与 SSTable 多版本存储方式有关。*

Previous

[业务 SQL 执行时偶发性报错：ORA-00600: internal error code, arguments: -11049, Exceed query memory limit](https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000006783511)

Next

[dbms_xplan.display_active_session_plan 记录的 real_time 实时时间不准确问题](https://www.oceanbase.com/knowledge-base/oceanbase-database-1000000006896960) ![有帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*y6ocSqN8cqsAAAAAAAAAAAAAARQnAQ)![无帮助](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*BG9IQJyLHF8AAAAAAAAAAAAAARQnAQ)![反馈](https://gw.alipayobjects.com/mdn/ob_asset/afts/img/A*eTWdQKCRKHwAAAAAAAAAAAAAARQnAQ)[AI](https://www.oceanbase.com/obi) 咨询热线
