超短小迷笛 發表於 2025-8-8 08:28:06

SQLServer中生成雪花ID(Snowflake ID)的实现方法

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">前言</a></li><li><a href="#_label1">认识雪花ID</a></li><ul class="second_class_ul"><li><a href="#_lab2_1_0">雪花ID的核心特点</a></li><li><a href="#_lab2_1_1">雪花ID的结构(64位)</a></li><li><a href="#_lab2_1_2">雪花ID的优势</a></li><li><a href="#_lab2_1_3">雪花ID的局限性</a></li><li><a href="#_lab2_1_4">雪花ID的应用场景</a></li><li><a href="#_lab2_1_5">示例ID解析</a></li></ul><li><a href="#_label2">生成雪花ID</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_6">使用T-SQL函数实现</a></li><li><a href="#_lab2_2_7">查看效果</a></li></ul></ul></div><p class="maodian"><a name="_label0"></a></p><h2>前言</h2>
<p>在我的印象中用到这个雪花ID比较少,可能是我接触的大型项目或者开源项目比较少,同时接触到中大型分布式也比较少,基本都是自研系统,用的是自增ID和GuidValue作为唯一编号。<br />最近项目上使用了一套第三方框架代码,使用了雪花ID作为表的唯一主键,并且之前表没有这个字段,需要进行表迁移的同时初始化雪花ID字段值。<br />因此,趁这次机会简单总结下雪花ID以及在Sql Server上如何生成雪花ID。</p>
<p class="maodian"><a name="_label1"></a></p><h2>认识雪花ID</h2>
<p>雪花ID是Twitter开发的一种分布式唯一ID生成算法,主要用于在分布式系统中生成全局唯一的ID标识符。它的名称来源于&quot;自然界中没有两片完全相同的雪花&quot;这一概念,象征着每个生成的ID都是独一无二的。</p>
<p class="maodian"><a name="_lab2_1_0"></a></p><h3>雪花ID的核心特点</h3>
<ol><li><strong>全局唯一性</strong>:在分布式系统中生成的ID不会重复</li><li><strong>时间有序性</strong>:ID按照时间顺序递增</li><li><strong>高性能</strong>:本地生成,不依赖数据库等外部系统</li><li><strong>可解析</strong>:ID中包含的信息可以被解析出来</li></ol>
<p class="maodian"><a name="_lab2_1_1"></a></p><h3>雪花ID的结构(64位)</h3>
<p>标准的雪花ID由以下三部分组成(共64位):</p>
<blockquote><p>| 1位符号位 | 41位时间戳 | 10位工作节点ID | 12位序列号 |</p></blockquote>
<p>具体分解:</p>
<ol><li><strong>符号位(1位)</strong>:始终为0,保证ID为正数</li><li><strong>时间戳(41位)</strong>:毫秒级的时间戳,可以使用约69年<ul><li>通常从自定义纪元开始计算(如Twitter使用2010-11-04 01:42:54 UTC)</li></ul></li><li><strong>工作节点ID(10位)</strong>:<ul><li>通常分为5位数据中心ID + 5位机器ID</li><li>最多支持32个数据中心,每个数据中心32台机器</li></ul></li><li><strong>序列号(12位)</strong>:同一毫秒内的序列号,支持每毫秒生成4096个ID</li></ol>
<p class="maodian"><a name="_lab2_1_2"></a></p><h3>雪花ID的优势</h3>
<ol><li><strong>分布式友好</strong>:不同节点可以独立生成ID而不需要协调</li><li><strong>时间有序</strong>:生成的ID按时间递增,有利于数据库索引</li><li><strong>高性能</strong>:本地生成,不依赖网络或数据库</li><li><strong>信息丰富</strong>:ID本身包含时间、节点等信息</li></ol>
<p class="maodian"><a name="_lab2_1_3"></a></p><h3>雪花ID的局限性</h3>
<ol><li><strong>时钟依赖</strong>:严重依赖系统时钟,时钟回拨会导致ID重复</li><li><strong>节点ID配置</strong>:需要手动或通过外部系统分配节点ID</li><li><strong>时间耗尽</strong>:41位时间戳大约69年后会耗尽</li></ol>
<p class="maodian"><a name="_lab2_1_4"></a></p><h3>雪花ID的应用场景</h3>
<ol><li>分布式系统主键生成</li><li>订单号、交易号等业务编号</li><li>日志跟踪ID</li><li>任何需要全局唯一且有序ID的场景</li></ol>
<p class="maodian"><a name="_lab2_1_5"></a></p><h3>示例ID解析</h3>
<p>假设一个雪花ID:<code>123456789012345678</code></p>
<p>转换为二进制后可以解析出:</p>
<ul><li>时间戳部分:可以转换为具体的生成时间</li><li>工作节点部分:知道是在哪个数据中心哪台机器生成的</li><li>序列号部分:知道这是该毫秒内生成的第几个ID</li></ul>
<p>雪花ID因其简单高效的特性,已经成为分布式系统ID生成的经典解决方案之一。</p>
<p class="maodian"><a name="_label2"></a></p><h2>生成雪花ID</h2>
<p>雪花ID是Twitter提出的一种分布式ID生成算法,它生成64位的唯一ID,通常包含时间戳、工作节点ID和序列号。<br />在SQL Server中可以通过以下几种方式实现雪花ID的生成:</p>
<p class="maodian"><a name="_lab2_2_6"></a></p><h3>使用T-SQL函数实现</h3>
<div class="jb51code"><pre class="brush:sql;">-- 创建配置表
CREATE TABLE SnowflakeConfig (
    MachineId BIGINT NOT NULL,
    DatacenterId BIGINT NOT NULL,
    LastTimestamp BIGINT NOT NULL,
    Sequence BIGINT NOT NULL,
    CONSTRAINT PK_SnowflakeConfig PRIMARY KEY (MachineId, DatacenterId)
);
</pre></div>
<div class="jb51code"><pre class="brush:sql;">-- 初始化配置 (机器ID和数据中心ID需要在每个节点上配置不同)
INSERT INTO SnowflakeConfig (MachineId, DatacenterId, LastTimestamp, Sequence)
VALUES (1, 1, 0, 0);
</pre></div>
<div class="jb51code"><pre class="brush:sql;">-- 创建获取当前时间戳的函数
CREATE FUNCTION GetCurrentTimestamp()
RETURNS BIGINT
AS
BEGIN
    DECLARE @epoch DATETIME2 = '1970-01-01 00:00:00';
    DECLARE @now DATETIME2 = SYSUTCDATETIME();
    RETURN CAST(DATEDIFF_BIG(MILLISECOND, @epoch, @now) AS BIGINT);
END;
</pre></div>
<div class="jb51code"><pre class="brush:sql;">-- 创建等待下一毫秒的函数
CREATE FUNCTION TilNextMillis(@lastTimestamp BIGINT)
RETURNS BIGINT
AS
BEGIN
    DECLARE @timestamp BIGINT;
    SET @timestamp = dbo.GetCurrentTimestamp();
   
    WHILE @timestamp &lt;= @lastTimestamp
    BEGIN
      SET @timestamp = dbo.GetCurrentTimestamp();
    END
   
    RETURN @timestamp;
END;
GO
</pre></div>
<div class="jb51code"><pre class="brush:sql;">-- 创建计算幂的函数(替代位移操作)
CREATE FUNCTION PowerOfTwo(@exponent BIGINT)
RETURNS BIGINT
AS
BEGIN
    RETURN CAST(POWER(CAST(2 AS FLOAT), @exponent) AS BIGINT);
END;
GO
</pre></div>
<div class="jb51code"><pre class="brush:sql;">-- 创建生成雪花ID的存储过程
CREATE PROCEDURE GenerateSnowflakeId
    @MachineId BIGINT = 1,
    @DatacenterId BIGINT = 1,
    @SnowflakeId BIGINT OUTPUT
AS
BEGIN
    SET NOCOUNT ON;
   
    -- 常量定义
    DECLARE @Twepoch BIGINT = 1700058600000;
    DECLARE @MachineIdBits BIGINT = 5;
    DECLARE @DatacenterIdBits BIGINT = 5;
    DECLARE @SequenceBits BIGINT = 12;
   
    -- 使用POWER计算替代位移
    DECLARE @MaxMachineId BIGINT = dbo.PowerOfTwo(@MachineIdBits) - 1;
    DECLARE @MaxDatacenterId BIGINT = dbo.PowerOfTwo(@DatacenterIdBits) - 1;
    DECLARE @SequenceMask BIGINT = dbo.PowerOfTwo(@SequenceBits) - 1;
   
    DECLARE @MachineIdShift BIGINT = @SequenceBits;
    DECLARE @DatacenterIdShift BIGINT = @SequenceBits + @MachineIdBits;
    DECLARE @TimestampLeftShift BIGINT = @SequenceBits + @MachineIdBits + @DatacenterIdBits;
   
    -- 验证参数
    IF @MachineId &gt; @MaxMachineId OR @MachineId &lt; 0
    BEGIN
      THROW 50000, 'MachineId can''t be greater than maxMachineId or less than 0', 1;
      RETURN;
    END
   
    IF @DatacenterId &gt; @MaxDatacenterId OR @DatacenterId &lt; 0
    BEGIN
      THROW 50000, 'DatacenterId can''t be greater than maxDatacenterId or less than 0', 1;
      RETURN;
    END
   
    -- 使用事务确保原子性
    BEGIN TRANSACTION;
   
    BEGIN TRY
      DECLARE @LastTimestamp BIGINT;
      DECLARE @Sequence BIGINT;
      DECLARE @Timestamp BIGINT;
      
      -- 获取当前状态
      SELECT @LastTimestamp = LastTimestamp, @Sequence = Sequence
      FROM SnowflakeConfig WITH (UPDLOCK)
      WHERE MachineId = @MachineId AND DatacenterId = @DatacenterId;
      
      -- 获取当前时间戳
      SET @Timestamp = dbo.GetCurrentTimestamp();
      
      -- 检查时钟回拨
      IF @Timestamp &lt; @LastTimestamp
      BEGIN
            ROLLBACK TRANSACTION;
            THROW 50000, 'Clock moved backwards. Refusing to generate id', 1;
            RETURN;
      END
      
      -- 同一毫秒内生成多个ID
      IF @LastTimestamp = @Timestamp
      BEGIN
            SET @Sequence = (@Sequence + 1) &amp; @SequenceMask;
            IF @Sequence = 0
            BEGIN
                -- 序列耗尽,等待下一毫秒
                SET @Timestamp = dbo.TilNextMillis(@LastTimestamp);
            END
      END
      ELSE
      BEGIN
            SET @Sequence = 0;
      END
      
      -- 更新状态
      UPDATE SnowflakeConfig
      SET LastTimestamp = @Timestamp,
            Sequence = @Sequence
      WHERE MachineId = @MachineId AND DatacenterId = @DatacenterId;
      
      -- 生成ID (使用乘法替代位移)
      SET @SnowflakeId =
            (@Timestamp - @Twepoch) * dbo.PowerOfTwo(@TimestampLeftShift) +
            @DatacenterId * dbo.PowerOfTwo(@DatacenterIdShift) +
            @MachineId * dbo.PowerOfTwo(@MachineIdShift) +
            @Sequence;
      
      COMMIT TRANSACTION;
    END TRY
    BEGIN CATCH
      ROLLBACK TRANSACTION;
      THROW;
    END CATCH
END;
GO
</pre></div>
<p class="maodian"><a name="_lab2_2_7"></a></p><h3>查看效果</h3>
<div class="jb51code"><pre class="brush:sql;">-- 使用存储过程版本
DECLARE @Id BIGINT;
EXEC GenerateSnowflakeId @MachineId = 1, @DatacenterId = 1, @SnowflakeId = @Id OUTPUT;
SELECT @Id AS SnowflakeId;
</pre></div>
<p style="text-align:center"><img alt="" src="https://img.jbzj.com/file_images/article/202508/2025080808262142.png" /></p>
頁: [1]
查看完整版本: SQLServer中生成雪花ID(Snowflake ID)的实现方法