研究 SQL NEWSEQUENTIALID() 時引發一個疑問,Guid 在 .NET、SQL 的排序方式是否相同?是依據什麼規則決定大小?

答案是 .NET 與 SQL Server 排序規則不同。

例如隨機產生三個 Guid,在 .NET 的排序為

  1. 87e4077c-a7b7-48d5-b155-79dbdc09c398
  2. 391ff00e-c526-40ec-a87a-0301837cfa4b
  3. 63b8a83c-b580-4808-8858-6b4cb89eee88

在 SQL Server 則是

  1. 391ff00e-c526-40ec-a87a-0301837cfa4b
  2. 63b8a83c-b580-4808-8858-6b4cb89eee88
  3. 87e4077c-a7b7-48d5-b155-79dbdc09c398

好奇心起,想搞清楚這個順序是怎麼排出來的。

先看 .NET。

在 Github 可以找到 .NET Guid 的原始碼,很快得知 CompareTo() 邏輯如下:

////////////////////////////////////////////////////////////////////////////////
//  Member variables
////////////////////////////////////////////////////////////////////////////////
private int         _a;
private short       _b;
private short       _c;
private byte       _d;
private byte       _e;
private byte       _f;
private byte       _g;
private byte       _h;
private byte       _i;
private byte       _j;
private byte       _k;

public int CompareTo(Guid value)
{
    if (value._a!=this._a) {
        return GetResult((uint)this._a, (uint)value._a);
    }

    if (value._b!=this._b) {
        return GetResult((uint)this._b, (uint)value._b);
    }

    if (value._c!=this._c) {
        return GetResult((uint)this._c, (uint)value._c);
    }

    if (value._d!=this._d) {
        return GetResult((uint)this._d, (uint)value._d);
    }

    if (value._e!=this._e) {
        return GetResult((uint)this._e, (uint)value._e);
    }

    if (value._f!=this._f) {
        return GetResult((uint)this._f, (uint)value._f);
    }

    if (value._g!=this._g) {
        return GetResult((uint)this._g, (uint)value._g);
    }

    if (value._h!=this._h) {
        return GetResult((uint)this._h, (uint)value._h);
    }

    if (value._i!=this._i) {
        return GetResult((uint)this._i, (uint)value._i);
    }

    if (value._j!=this._j) {
        return GetResult((uint)this._j, (uint)value._j);
    }

    if (value._k!=this._k) {
        return GetResult((uint)this._k, (uint)value._k);
    }

    return 0;
} 

翻譯一下,.NET 把 GUID 的 16 個 Bytes 拆解成 一個 int _a (4 Bytes)、兩個 short _b (2 Bytes) 及 _c (2 Bytes),再加上 8 個 byte _d 到 _k (8 Bytes),對映成 GUID 格式就是 aaaaaaaa-bbbb-cccc-ddee-ffgghhiijjkk,比對時先比 _a,再比 _b,再比 _c,一直比到 _k。

SQL 的比對邏輯則不同,我在 stackoverflow 找到這篇 SQL Uniqueidentifier 排序規則討論,依據 SQL Server RD,SQL Server 排序 Uniqueidentifier 的依據是先比 10-15 Bytes,再比 8-9 Bytes,6-7 Bytes、4-5 Bytes、0-3 Bytes,若對映到 .NET,是將 _f ~ _k 6 Bytes 當成一個數字、_d _e (當成 short)、_c (short)、_b (short)、_a (int) 逐一比序。

來驗證一下,.NET 排序為

  1. 87e4077c-a7b7-48d5-b155-79dbdc09c398
  2. 391ff00e-c526-40ec-a87a-0301837cfa4b
  3. 63b8a83c-b580-4808-8858-6b4cb89eee88

比較 _a 分別為 0x87e4077c = -2015099012 (注意:int 為有號數,超過 0x8... 時為負數) < 0x391ff00e < 0x63b8a83c,依此決定排序。

SQL 排序為

  1. 391ff00e-c526-40ec-a87a-0301837cfa4b
  2. 63b8a83c-b580-4808-8858-6b4cb89eee88
  3. 87e4077c-a7b7-48d5-b155-79dbdc09c398

先看最後一節,0301837cfa4b < 6b4cb89eee88 < 79dbdc09c398 (最後 6 Bytes 直接比對 byte[] 轉 16 進位字串),排完收工。

原則蠻簡單的,以後遇到 Guid 排序就不會一頭霧水了。(其實不知道也沒差)

This article explains how .NET and MSSQL sort GUID.


Comments

Be the first to post a comment

Post a comment