## 模块 6: TiKV MVCC 内存引擎 (v8.5.0)

多版本并发控制 (MVCC) 是现代数据库的关键方面，允许并发访问而不影响完整性。TiKV MVCC 内存引擎主要用于加速需要扫描大量 MVCC 历史版本的查询。一个优化良好的查询可能会因为扫描大量 MVCC 版本而变成慢查询。

TiKV MVCC 内存引擎适用于以下场景以提高查询性能：

+ 需要查询频繁更新或删除记录的应用程序。
+ 需要调整 `tidb_gc_life_time` 以在 TiDB 中保留历史版本更长时间的应用程序。

### 步骤 1. 检查默认的 `in-memory-engine` 设置

+ 连接到 TiDB。

  ```bash
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root
  ```

  ```output
  mysql> 
  ```

+ 验证 TiKV MVCC `in-memory-engine` 默认是关闭的。

  ```sql
  SHOW CONFIG WHERE type='tikv' AND name='in-memory-engine.enable';
  ```

  ```output
  +------+-------------------+-------------------------+-------+
  | Type | Instance          | Name                    | Value |
  +------+-------------------+-------------------------+-------+
  | tikv | 10.90.1.41:20160  | in-memory-engine.enable | false |
  | tikv | 10.90.1.223:20160 | in-memory-engine.enable | false |
  | tikv | 10.90.1.231:20160 | in-memory-engine.enable | false |
  +------+-------------------+-------------------------+-------+
  3 rows in set (0.01 sec)
  ```

+ 增加 `tidb_gc_life_time` 以便在本次研讨会期间保留以前的版本。

  ```sql
  SET GLOBAL tidb_gc_life_time='1440m';
  ```

### 步骤 2. 验证 `in-memory-engine` 设置为 `OFF` 时的行为

+ 设置两个名为 `ime_test1` 和 `ime_test2` 的测试表，具有相同的模式。请注意，每个表的 `content` 列上都有一个索引。

  ```sql
  DROP TABLE IF EXISTS test.ime_test1;
  DROP TABLE IF EXISTS test.ime_test2;
  CREATE TABLE test.ime_test1 (
      `id` BIGINT AUTO_RANDOM PRIMARY KEY,
      `content` INT,
      `created_at` DATETIME,
      `updated_at` DATETIME,
      `version` INT,
      KEY content_idx (`content`)
  );
  CREATE TABLE test.ime_test2 (
      `id` BIGINT AUTO_RANDOM PRIMARY KEY,
      `content` INT,
      `created_at` DATETIME,
      `updated_at` DATETIME,
      `version` INT,
      KEY content_idx (`content`)
  );
  ```

+ 向两个表中插入一行。

  ```sql
  INSERT INTO test.ime_test1 (
      content, created_at, updated_at, version
  ) VALUES (
      FLOOR(1 + (RAND() * 99999999)), NOW(), NOW(), 1
  );
  INSERT INTO test.ime_test2 (
      content, created_at, updated_at, version
  ) VALUES (
      FLOOR(1 + (RAND() * 99999999)), NOW(), NOW(), 1
  );
  SELECT * FROM test.ime_test1;
  SELECT * FROM test.ime_test2;
  EXIT
  ```

+ 扩展两个表到 32768 行。

  ```bash
  cat > double.sql <<EOF
  INSERT INTO test.ime_test1 (
      content, created_at, updated_at, version
  )
  SELECT FLOOR(1 + (RAND() * 99999999)), NOW(), NOW(), 1 FROM test.ime_test1;
  INSERT INTO test.ime_test2 (
      content, created_at, updated_at, version
  )
  SELECT FLOOR(1 + (RAND() * 99999999)), NOW(), NOW(), 1 FROM test.ime_test2;
  EOF
  cat > double.sh <<EOF
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root < double.sql
  EOF
  chmod +x double.sh
  for c in {1..15}; do
  ./double.sh
  done;
  ```

+ 通过对 `content` 列执行 100 次更新操作，为表 `test.ime_test2` 中的每一行生成 100 个版本。在我们的实验环境中（未优化性能），大约需要 2 分钟。

  ```bash
  cat > update_hog.sql <<EOF
  UPDATE test.ime_test2
  SET content = FLOOR(1 + (RAND() * 99999999)), version = version + 1;
  EOF
  cat > update_hog.sh <<EOF
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root < update_hog.sql
  EOF
  chmod +x update_hog.sh
  for c in {1..100}; do
  ./update_hog.sh
  done;
  ```

+ 再次连接到 TiDB。

  ```bash
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root
  ```

  ```output
  mysql> 
  ```

+ 运行两个对表 `test.ime_test1` 和 `test.ime_test2` 进行全表扫描的 SQL 语句。

  ```sql
  SELECT COUNT(*) FROM test.ime_test1;
  SELECT COUNT(*) FROM test.ime_test2;
  EXIT
  ```

  ```output
  +----------+
  | COUNT(*) |
  +----------+
  |    32768 |
  +----------+
  1 row in set (0.03 sec)

  +----------+
  | COUNT(*) |
  +----------+
  |    32768 |
  +----------+
  1 row in set (1.09 sec)

  Bye
  ```

+ 转到 [TiDB PD Dashboard](http://${LAB:HOST_PD1_PUBLIC_IP}:2379/dashboard) 并使用用户名 `root` 登录，目前不需要密码。导航到 `SQL Statements` 面板，将显示时间更改为 `Recent 5 min`。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-dashboard-time-1.png)

+ 选择数据库为 `test`。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-dashboard-db-2.png)

+ 将显示的操作设置为 `Select` 和 `Update`。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-dashboard-kind-3.png)

+ 在 Dashboard 的右上角，选择 `Columns` 并勾选菜单项 `Mean Visible Version Per Query` 和 `Mean Meet Versions Per Query`，然后点击 `Query` 按钮。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-dashboard-columns-4.png)

+ 从查询结果中，对于表 `test.ime_test1` 的扫描，`Mean Visible Version Per Query` 等于 `Mean Meet Versions Per Query`，表明没有 MVCC 快照需要处理。
相比之下，对于表 `test.ime_test2` 的扫描，`Mean Meet Versions Per Query` 显著高于 `Mean Visible Version Per Query`，表明在扫描过程中有大量的 MVCC 快照处理。这也从 `Total Latency` 和 `Mean Latency` 列中得以体现，显示对于相同数量的扫描行，执行时间不同，表 `test.ime_test2` 的扫描消耗了大量时间处理 MVCC 快照。

  ![95%](https://lab-static.pingcap.com/quick-demo/ime-off-dashboard-sql-5.png)

+ 点击 [Grafana 监控](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:3000) 并使用用户名和密码（均为 "admin"）登录。导航到 `Home` > `tidb-demo` > `tidb-demo-TikV-Details` > `In Memory Engine`，向下滚动到关于 `Region Cache Hit`，`Region Cache Hit Rate`，`Region Cache Miss Reason`，`Memory Usage` 和 `Region Count` 的面板。

  ![90%](https://lab-static.pingcap.com/quick-demo/ime-off-grafana-no-data-1.png)

+ 目前，它们都显示 `No data`，因为当前数据库集群中未启用 TiKV MVCC 内存引擎。将 `Absolute time range` 更改为 `Last 5 minutes`。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-grafana-time-range-2.png)

+ 将 `Refresh time` 更改为 `10s`。

  ![60%](https://lab-static.pingcap.com/quick-demo/ime-off-grafana-refresh-time-3.png)

### 步骤 3. 启用 TiKV MVCC 内存引擎

+ 返回到 [终端 1](ssh://monitor1-ssh#1) ，使用 `tiup cluster edit-config` 打开 `vi` 中的 TiDB 集群配置。

  ```bash
  export PATH="$PATH:/home/ec2-user/.tiup/bin"
  tiup cluster edit-config tidb-demo
  ```

+ 要启用 TiKV MVCC 内存引擎，请为所有三个 `tikv_servers` 设置 `in-memory-engine.capacity` 为 `1GiB`，`in-memory-engine.enable` 为 `true`，以及 `in-memory-engine.mvcc-amplification-threshold` 为 `100`。

+ 将以下部分复制到每个 TiKV 的 `os: linux` 条目下，然后使用 `:wq` 保存并退出。

  ```yaml
    config:
      in-memory-engine.capacity: 1GiB
      in-memory-engine.enable: true
      in-memory-engine.mvcc-amplification-threshold: 50
  ```

  ```output
  ...
  tikv_servers:
  - host: 10.90.1.41
    ssh_port: 22
    port: 20160
    status_port: 20180
    deploy_dir: /tidb-deploy/tikv-20160
    data_dir: /tidb-data/tikv-20160
    log_dir: /tidb-deploy/tikv-20160/log
    arch: amd64
    os: linux
    config:
      in-memory-engine.capacity: 1GiB
      in-memory-engine.enable: true
      in-memory-engine.mvcc-amplification-threshold: 50
  - host: 10.90.1.223
    ssh_port: 22
    port: 20160
    status_port: 20180
    deploy_dir: /tidb-deploy/tikv-20160
    data_dir: /tidb-data/tikv-20160
    log_dir: /tidb-deploy/tikv-20160/log
    arch: amd64
    os: linux
    config:
      in-memory-engine.capacity: 1GiB
      in-memory-engine.enable: true
      in-memory-engine.mvcc-amplification-threshold: 50
  - host: 10.90.1.231
    ssh_port: 22
    port: 20160
    status_port: 20180
    deploy_dir: /tidb-deploy/tikv-20160
    data_dir: /tidb-data/tikv-20160
    log_dir: /tidb-deploy/tikv-20160/log
    arch: amd64
    os: linux
    config:
      in-memory-engine.capacity: 1GiB
      in-memory-engine.enable: true
      in-memory-engine.mvcc-amplification-threshold: 50
  ...
  ```

+ 回答提示 `y`。

  ```output
  Please check change highlight above, do you want to apply the change? [y/N]:(default=N) y
  Applying changes...
  Applied successfully, please use `tiup cluster reload tidb-demo [-N <nodes>] [-R <roles>]` to reload config.
  ```

+ 运行 `tiup cluster reload` 重新加载配置。

  ```bash
  tiup cluster reload tidb-demo --yes
  ```

  ```output
  ...
  Reloaded cluster `tidb-demo` successfully
  ```

+ 配置重新加载后，连接到 TiDB。

  ```bash
  mysql -h ${HOST_TIPROXY1_PRIVATE_IP} --port 6000 -u root
  ```

  ```output
  mysql> 
  ```

+ 验证 TiKV MVCC `in-memory-engine` 已开启。

  ```sql
  SHOW CONFIG WHERE type='tikv' AND name='in-memory-engine.enable';
  EXIT
  ```

  ```output
  +------+-------------------+-------------------------+-------+
  | Type | Instance          | Name                    | Value |
  +------+-------------------+-------------------------+-------+
  | tikv | 10.90.1.41:20160  | in-memory-engine.enable | true  |
  | tikv | 10.90.1.223:20160 | in-memory-engine.enable | true  |
  | tikv | 10.90.1.231:20160 | in-memory-engine.enable | true  |
  +------+-------------------+-------------------------+-------+
  3 rows in set (0.02 sec)

  Bye
  ```

### 步骤 4. 验证 `in-memory-engine` 设置为 `ON` 时的行为

+ 点击 [Grafana 监控](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:3000)  并使用用户名和密码（均为 "admin"）登录。导航到 `Home` > `tidb-demo` > `tidb-demo-TikV-Details` > `In Memory Engine`，向下滚动到关于 `Region Cache Hit`，`Region Cache Hit Rate`，`Region Cache Miss Reason`，`Memory Usage` 和 `Region Count` 的面板。现在，这些面板不再显示 `No data`，但是 `Region Cache Hit`，`Region Cache Hit Rate`，`Region Cache Miss Reason`，`Memory Usage` 和 `Region Count` 的值非常低，几乎为零，因为目前没有查询 MVCC 快照。

  ![90%](https://lab-static.pingcap.com/quick-demo/ime-on-grafana-no-query-1.png)

+ 返回到[终端 1](ssh://monitor1-ssh#1) ，通过命令行对 `test.ime_test1` 表执行 10,000 次扫描，同时在 [Grafana 监控](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:3000)  中监控 `Home` > `tidb-demo` > `tidb-demo-TikV-Details` > `In Memory Engine` 面板。

  ```bash
  for i in `seq 10000`;
  do mysql -uroot -h ${HOST_TIPROXY1_PRIVATE_IP} -P 6000 -e "SELECT COUNT(*) FROM test.ime_test1;";
  done;
  ```

+ 在 `test.ime_test1` 表的扫描过程中，可以从 `tidb-demo-TikV-Details` > `In Memory Engine` 面板中观察到 `Region Cache Hit` 和 `Region Cache Hit Rate` 变化不大。（`Region Cache Hit` 表示成功从 Region 缓存中检索数据的次数，而 `Region Cache Hit Rate` 表示 Region 缓存的命中率。）变化不大的原因是 `test.ime_test1` 表本身没有 MVCC 历史快照。

  ![90%](https://lab-static.pingcap.com/quick-demo/ime-on-grafana-ime-test1.png)

+ 在 `test.ime_test1` 表的 10,000 次扫描完成后，返回到[终端 1](ssh://monitor1-ssh#1) ，通过命令行对 `test.ime_test2` 表执行另外 50,000 次扫描，同时再次在 [Grafana 监控](http://${LAB:HOST_MONITOR1_PUBLIC_IP}:3000)  中监控 `Home` > `tidb-demo` > `tidb-demo-TikV-Details` > `In Memory Engine` 面板。

  ```bash
  for i in `seq 50000`;
  do mysql -uroot -h ${HOST_TIPROXY1_PRIVATE_IP} -P 6000 -e "SELECT COUNT(*) FROM test.ime_test2;";
  done;
  ```

+ 在 `test.ime_test2` 表的扫描过程中，可以从 `tidb-demo-TikV-Details` > `In Memory Engine` 面板中观察到 `Region Cache Hit` 和 `Region Cache Hit Rate` 发生了显著变化。原因是 `test.ime_test2` 表本身包含大量的 MVCC 历史快照。TiKV MVCC 内存引擎将最新写入的 MVCC 版本缓存到内存中，并实现了独立于 TiDB 的 MVCC GC 机制。因此，在扫描过程中，从 TiKV MVCC 内存引擎的缓存中读取了大量数据。

  ![90%](https://lab-static.pingcap.com/quick-demo/ime-on-grafana-ime-test2.png)

+ TiKV MVCC 内存引擎有时可能无法及时缓存数据；在这种情况下，您可以尝试重复上述步骤，再次扫描 `ime_test2` 表并观察监控面板。
