设计一个 SQL 数据库,其中多个表使用相同的模型

Designing an SQL Database where multiple tables use the same model

背景

我正在尝试设计一个 PostgreSQL 数据库,用于存储来自加密货币交易所的交易记录。我的客户端中只有一个 Trade 模型,但我想根据它们所代表的符号将交易拆分为它们自己的 table。可用的符号是在运行时推导出来的,来自交易所的每个交易都有自己的 Id 序列,从 0 开始。

例如,假设我想缓存来自币安的交易。我的客户会调用他们的 API,看到平台上可用的(一些)符号是 BTCUSDTETHUSDTETHBTC,如果这些符号不是已经在 bn.Symbols table 中,它将插入它们,并创建一个新的 table bn.Trades-SYMBOLNAME.

我使用的技术堆栈是带有 Dapper 和 PostgreSQL 的 .NET Core 3。

问题

我看过几篇文章,详细介绍了如何编写一个过程或函数来创建一个 table 并以提供的变量作为名称,以及 a common sentiment seems to be that this is a poor design

除了这种负面情绪之外,我的客户正在使用存储库和工作单元模式,并且 TradeRepository 以定义 GetById(long) 方法的方式进行接口,因此我无法指定我希望存储库以哪个符号为目标,也不能在存储库函数中指定符号,而无需诉诸一些肮脏的技巧。

一个简单的解决方案是将所有交易存储在同一个 table 中,为 Symbol 设置一个外键列,并为 IdSymbol 列。但是,我对此犹豫不决,因为我最终将存储超过 10 亿笔交易,而且这种方法仍然会导致我当前的 Repository 模式设计出现问题。

有没有更好的方法来解决这个问题?

代码

下面是一些显示我的模型当前配置方式的代码:

public abstract class EntityBase : IEntity
{

  #region Properties

  public long Id
  {
     get;
     set;
  }

  #endregion

}

public class Symbol : EntityBase
{

  #region Data Members

  private long _baseAssetId;
  private long _quoteAssetId;

  #endregion

  #region Properties

  public string Name
  {
     get;
     set;
  }

  public long BaseAssetId
  {
     get
     {
        if( BaseAsset != null )
           return _baseAssetId = BaseAsset.Id;

        return _baseAssetId;
     }
     set => _baseAssetId = value;
  }

  public int BaseAssetPrecision
  {
     get;
     set;
  }

  public long QuoteAssetId
  {
     get
     {
        if( QuoteAsset != null )
           return _quoteAssetId = QuoteAsset.Id;

        return _quoteAssetId;
     }
     set => _quoteAssetId = value;
  }

  public int QuoteAssetPrecision
  {
     get;
     set;
  }

  #endregion

  #region Navigation Properties

  public virtual Asset BaseAsset
  {
     get;
     set;
  }

  public virtual Asset QuoteAsset
  {
     get;
     set;
  }

  #endregion

}

public class Trade : EntityBase
{

  #region Properties

  public decimal Price
  {
     get;
     set;
  }

  public decimal Quantity
  {
     get;
     set;
  }

  public decimal QuoteQuantity
  {
     get;
     set;
  }

  public long Timestamp
  {
     get;
     set;
  }

  public bool IsBuyer
  {
     get;
     set;
  }

  public DateTime Time
  {
     get => Timestamp.ToUnixTimeMilliseconds();
  }

  #endregion

}

以下是所有存储库的接口方式:

public interface IRepository<TEntity> : IDisposable
  where TEntity : IEntity, new()
{

  Task AddAsync( TEntity entity, CancellationToken cancellationToken = default );
  Task<TEntity> GetByIdAsync( long id, CancellationToken cancellationToken = default );
  Task<bool> RemoveAsync( TEntity entity, CancellationToken cancellationToken = default );
  Task<bool> UpdateAsync( TEntity entity, CancellationToken cancellationToken = default );

}

下面是 UnitOfWork 的接口方式:

public interface IUnitOfWork : IDisposable
{

  #region Properties

  Guid Id
  {
     get;
  }

  IDbConnection Connection
  {
     get;
  }

  IDbTransaction Transaction
  {
     get;
  }

  #endregion

  #region Repositories

  IAssetRepository Assets
  {
     get;
  }

  ITradeRepository Trades
  {
     get;
  }

  ISymbolRepository Symbols
  {
     get;
  }

  #endregion

  #region Public Methods

  void Begin();
  void Commit();
  void Rollback();

  #endregion

}

A simple solution would be to store all trades in the same table, have a foreign key column for Symbol, and create a unique composite index for the Id and Symbol columns. However, I am hesitant to do this, because I will eventually be storing over a billion trades, and this approach also still causes issues with my current Repository pattern design.

这就是答案。使用单个 table 并区分您的符号列。将它们分开的缺点是性能(主要是因为连接)和 "normalisation" 因为 table 中的重复几乎相同