设计一个 SQL 数据库,其中多个表使用相同的模型
Designing an SQL Database where multiple tables use the same model
背景
我正在尝试设计一个 PostgreSQL 数据库,用于存储来自加密货币交易所的交易记录。我的客户端中只有一个 Trade
模型,但我想根据它们所代表的符号将交易拆分为它们自己的 table。可用的符号是在运行时推导出来的,来自交易所的每个交易都有自己的 Id 序列,从 0 开始。
例如,假设我想缓存来自币安的交易。我的客户会调用他们的 API,看到平台上可用的(一些)符号是 BTCUSDT
、ETHUSDT
和 ETHBTC
,如果这些符号不是已经在 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
设置一个外键列,并为 Id
和 Symbol
列。但是,我对此犹豫不决,因为我最终将存储超过 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 中的重复几乎相同
背景
我正在尝试设计一个 PostgreSQL 数据库,用于存储来自加密货币交易所的交易记录。我的客户端中只有一个 Trade
模型,但我想根据它们所代表的符号将交易拆分为它们自己的 table。可用的符号是在运行时推导出来的,来自交易所的每个交易都有自己的 Id 序列,从 0 开始。
例如,假设我想缓存来自币安的交易。我的客户会调用他们的 API,看到平台上可用的(一些)符号是 BTCUSDT
、ETHUSDT
和 ETHBTC
,如果这些符号不是已经在 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
设置一个外键列,并为 Id
和 Symbol
列。但是,我对此犹豫不决,因为我最终将存储超过 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 中的重复几乎相同