智能合约 eos 智能合约数据库 multi_index 源码分析,以及使用注意事项

liweidong · 2018年06月11日 · 9527 次阅读

multi_index用于封装智能合约对数据库的管理,增、删、改、查。源码位于路径,contracts/eosiolib/multi_index.hpp 对数据存储的深入了解才能写出更高效的智能合约。本文对部分源码进行详细分析,并总结multi_index一些使用时的注意事项和正确的使用方式。

1、数据缓存结构代码段

struct item : public T
{
   template<typename Constructor>
   item( const multi_index* idx, Constructor&& c )
   :__idx(idx){
      c(*this);
   }

   const multi_index* __idx;
   int32_t            __primary_itr;
   int32_t            __iters[sizeof...(Indices)+(sizeof...(Indices)==0)];
};

struct item_ptr
{
   item_ptr(std::unique_ptr<item>&& i, uint64_t pk, int32_t pitr)
   : _item(std::move(i)), _primary_key(pk), _primary_itr(pitr) {}

   std::unique_ptr<item> _item;
   uint64_t              _primary_key;
   int32_t               _primary_itr;
};

mutable std::vector<item_ptr> _items_vector;

说明: _items_vector是数据表的缓存数据,即数据表的本地映射。在multi_index的构造函数中没有对_items_vector做初始化,说明这是一个空的vector。我们发现每当执行get,find,++,—(减减) 查询方法时最终都会执行load_object_by_primary_iterator方法。该方法中首先从表缓存_items_vector中查找,如果找到对应数据刚直接返回。如果找不到会调用db_get_i64从数据库读取并保存到缓存中,方便下次快速查找。以上分析说明在智能合约里数据的查询封装的还是很不错的。

同时我们也发现了一个问题。multi_index中没有size()方法,要想知道一个表里某个域下有多少条数据,可以利用迭代器从begin到end把数据数据都读取一遍以计算数据总量。因此在涉及到数据数量的业务时最好是能自己记录当前有多少条数据。例如如西游降妖志中竞技场数据可记录当前竞技场一共有多少玩家,并且可根据排名直接定位到某一玩家。

2、数据修改代码段

由于篇幅太长,modify方法代码就不直接贴出来了,请自行阅读,新增(emplace),删除(earse)原理也是一样的。

在modify中修改数据的实现可没有查询数据那么优雅了,我们发现每次修改都会直接修改数据库,并修改本地缓存。更为重要的一点就是要重新排序,可以想像一下这个操作有多么的严重。如果一个开发者对这个结构不够深入了解,只是简单的会使用,在逻辑中对一条记录执行多次修改,那会是什么代价呢?

结合我上一篇文章提出的把客户端操作打包成命令集合的方式来使用,肯定会出现对一条记录多次修改的情况。在卡牌游戏西游降妖志项目中我的解决办法在void UserData::saveUserData()方法中有体现,主要思想是自己把数据再做一次缓存,只对缓存数据做修改操作,最后结束时统一保存到数据库。

3、索引使用代码段

static_assert( sizeof...(Indices) <= 16, "multi_index only supports a maximum of 16 secondary indices" ); 

说明: 一个multi_index最多支持16个二级索引。

定义二级索引案例,西游降妖志卡牌游戏竞技场玩家按用户名做主健,排名做二级索引。

struct arenaRankInfo
{
    uint32_t arena_rank;
    account_name user_id;
    uint64_t primary_key()const { return arena_rank; }
    uint64_t user_key() const { return user_id; }
};
typedef eosio::multi_index<N(arenaRankInf),arenaRankInfo,
eosio::indexed_by<N(arenaUserId), eosio::const_mem_fun<arenaRankInfo, uint64_t, &arenaRankInfo::user_key> >> arenaRankInfos;

multi_index模板中前两个参数分别是表名和数据类型。之后可以增加0~16个二级索引。

定义二级索引使用eosio::indexed_by模板。N(arenaUserId)为索引的名称,arenaRankInfo表数据类型,uint64_t索引类型即索引函数返回的类型(只支持uint64_t,uint128_t,double,long double,key256类型),&arenaRankInfo::user_key索引函数。

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册