7

冷知識 - Guid 如何排序?

 2 years ago
source link: https://blog.darkthread.net/blog/guid-sorting/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

冷知識 - Guid 如何排序?

2021-06-30 07:29 PM 0 969

研究 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 排序就不會一頭霧水了。(其實不知道也沒差)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK