数据存储主键类的选择-雪花漂移算法
数据存储主键类的选择-雪花漂移算法
常用主键类型:
- 整型:int,long。优点:可以自增,占用空间小,存取速度快。缺点:难于扩展,需要合并、分表、分库或者数据迁移会相当痛苦。不大适合分布式存储。
- 字符串。性能差不推荐使用。
- GUID/UUID:优点:全局唯一,合并、分表、分库,迁移相当方便。缺点:无序,存储存储空间大,性能差,每次新增数据会导致索引重新排序。
从上可以看出整型和GUID/UUID的优缺点正好相反。
想要一种同时具有整型和GUID/UUID的优点的主键,可以参考Twitter公开的雪花算法(Snowflake),Twitter原版https://github.com/twitter-archive/snowflake,网上有很多国内写的实现,如:https://github.com/cloudyan/snowflake。生成一个 64 bit
的整数,保证不同进程主键的不重复性以及相同进程主键的有序性。
优点:生成的 ID 都是趋势递增的;不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成 ID 的性能也是非常高的;根据自身业务特性分配 bit 位,非常灵活
缺点:强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。
个人更喜欢用国人写的雪花漂移https://github.com/yitter/IdGenerator,优点巴拉巴拉介绍了一大堆,主要用起来简单。
在.Net项目中用法如下:
1、NuGet包管理器中添加IdGenerator包引用
2、在程序初始化时配置工作站编号。
打开项目的Program.cs文件,添加以下代码。
// 配置雪花漂移算法
var options = new IdGeneratorOptions(1);
YitIdHelper.SetIdGenerator(options);`
其中“IdGeneratorOptions(1)”中的数字“1”是机器码,不同的系统使用不同的机器码避免生成的id重复,默认最大值为63,可以配置64个系统。通过配置WorkerIdBitLength参数增加机器码长度。
3、在模型类中生成id。
新建模型类:Entity
类中的Id属性添加Key, DatabaseGenerated(DatabaseGeneratedOption.None)标注,表示Id为主键且生成行为不进行任何处理,也就不自增。并在Entity类的构造函数中使用YitIdHelper.NextId()生成id。代码如下
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using Yitter.IdGenerator;
namespace Ledger.Entity
{
/// <summary>
/// 保存到数据库的根实体。
/// </summary>
public class Entity
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="createNewId">创建新ID</param>
public Entity(bool createNewId)
{
if(createNewId) Id = YitIdHelper.NextId();
}
public Entity(long id)
{
Id = id;
}
[Key, DatabaseGenerated(DatabaseGeneratedOption.None)]
public long Id { get; set; }
/// <summary>
/// 为Id创建一个新值
/// </summary>
public void CreateNewId()
{
Id = YitIdHelper.NextId();
}
}
}
为避免每次实例化时都自动创建Id影响性能,这里有两个构造函数:
-
public Entity(bool createNewId),明确传入createNewId=true时才创建id。主要用于实例化一个需要保存到数据库的新对象时采用。
-
public Entity(long id),传入一个Id值,用在非创建新对象时用。
另外还写了一个public void CreateNewId()方法,在需要时为类生成新id。
为了使用方面Entity可以做基类,其他模型类继承自这个类。
如:
using System.ComponentModel.DataAnnotations;
namespace Ledger.Entity
{
/// <summary>
/// 商户
/// </summary>
public class Shop : Entity
{
/// <summary>
/// 创建新商户
/// </summary>
/// <param name="bookId">账本Id</param>
/// <param name="name">名称</param>
/// <param name="createNewId">是否创建新id</param>
public Shop(long bookId, string name,bool createNewId = true) : base(createNewId)
{
BookId = bookId;
Name = name;
}
/// <summary>
/// 设置已有商户
/// </summary>
/// <param name="id">商户Id</param>
/// <param name="bookId">账本Id</param>
/// <param name="name">名称</param>
public Shop(long id,long bookId, string name) : base(id)
{
BookId = bookId;
Name = name;
}
/// <summary>
/// 图标
/// </summary>
public string? Icon { get; set; }
/// <summary>
/// 名称
/// </summary>
[StringLength(20)]
public string Name { get; set; }
}
}
其他没有什么不同。