📜  eosio 多索引清除 - C++ (1)

📅  最后修改于: 2023-12-03 15:30:37.695000             🧑  作者: Mango

EOSIO 多索引清除

在 EOSIO 开发中,多索引表是一个十分常用的数据存储方式。不过,当需要清空一张多索引表的全部数据时,通常会遇到不少困难。本文介绍了一种清除多索引表全部数据的方法,希望对 EOSIO 开发者有所帮助。

问题描述

假设有一个名为 example 的合约,其中包含一个名为 mytable 的多索引表,其类型为 multi_index<"mytable"_n, examplestruct>。当需要清空 mytable 内所有数据时,通常会尝试直接遍历表内元素并逐个删除,代码示例如下:

typedef eosio::multi_index<"mytable"_n, examplestruct> mytable_t;
mytable_t mytable(contract, contract.value);

// 删除 mytable 内所有数据
auto itr = mytable.begin();
while (itr != mytable.end()) {
    itr = mytable.erase(itr);
}

但是,很多情况下这种方法并不适用。例如:

  1. 当表内数据非常多时,这个过程会非常耗时,甚至会因此造成交易超时;
  2. 当表内数据量非常大时,删除操作的 CPU 开销也会非常高,有可能超出某些合约的计算资源限制。

因此,我们需要一个更高效、资源消耗更少的清空多索引表的方法。

解决方案

高效清空多索引表的方案是:使用系统提供的批量删除接口 eosio::multi_index::emplace,在批量删除时创建一些虚拟的具有相同主键的行,直接利用批量删除接口删除这些虚拟行即可。

具体来说,我们可以在 example 合约中添加如下代码片段:

// 范围删除所有 existing 行, 通过创建多个不存在的行
uint64_t s = std::numeric_limits<uint64_t>::min();
uint64_t sk = std::numeric_limits<uint64_t>::min();
while (s < std::numeric_limits<uint64_t>::max() - BATCH_SIZE) {
  auto end = s + BATCH_SIZE;
  auto upper = mytable.upper_bound(s);
  if (upper != mytable.begin()) {
    --upper;
    if (upper->key == sk) {
      --upper;
      if (upper != mytable.begin()) {
        --upper;
      }
    }
    s = mytable.erase(mytable.begin(), ++upper)->key;
    sk = s;
  } else {
    sk = s = end;
  }
  mytable.emplace(get_self(), [&](auto& a){
    a.key = s;
    a.value = 0; // value 的类型为 uint64_t
  });
}
auto upper = mytable.upper_bound(s);
if (upper != mytable.end() && upper->key != sk) {
  mytable.erase(mytable.begin(), upper);
  mytable.emplace(get_self(), [&](auto& a) {
    a.key = std::numeric_limits<uint64_t>::max();
    a.value = 0;
  });
}

值得注意的是,在以上代码片段中,我们使用了一些 EOSIO 提供的一些内置类型极值,如 numeric_limits<uint64_t>::min()numeric_limits<uint64_t>::max(),这使得我们不必手动处理溢出边界的问题,避免了很多麻烦。

总结

对于需要清空大量数据的多索引表,直接单个元素逐个删除的代价非常高,无法用于生产环境中。借助 eosio::multi_index::emplace 接口创建一些虚拟行,可以大幅提升清空表的效率,从而更加高效地使用 EOSIO 示例合约。