Подтвердить что ты не робот

Является ли .NET равным SQL-серверам newsequentialid()

Мы используем Guid для первичного ключа, который, как вы знаете, кластеризуется по умолчанию.

При вставке новой строки в таблицу она вставлена на случайную страницу в таблице (поскольку Guid является случайным). Это имеет измеримое влияние на производительность, поскольку БД все время разбивает страницы данных (фрагментация). Но основная причина того, что последовательный Guid заключается в том, что я хочу, чтобы новые строки были вставлены в качестве последней строки в таблице... что поможет при отладке.

Я мог бы создать кластерный индекс в CreateDate, но наша БД автоматически генерируется и в разработке, нам нужно сделать что-то дополнительное, чтобы облегчить это. Также CreateDate не является хорошим кандидатом для кластеризованного индекса.

В тот же день я использовал Jimmy Nielsons COMB, но мне было интересно, есть ли что-то в платформе.NET для этого. В SQL 2005 Microsoft представила newsequentialid() в качестве альтернативы newid(), поэтому я надеялся, что они сделали эквивалент.NET, потому что мы генерируем ID в коде.

PS: Пожалуйста, не начинайте обсуждать, правильно это или нет, потому что GUID должен быть уникальным и т.д.

4b9b3361

Ответ 1

Должно быть возможно создать последовательный GUID в С# или vb.net, используя вызов API для UuidCreateSequential. Объявление API (С#), приведенное ниже, было взято из Pinvoke.net, где вы также можете найти полный пример вызова функции.

[DllImport("rpcrt4.dll", SetLastError=true)]
static extern int UuidCreateSequential(out Guid guid);

Статья MSDN, связанная с функцией UuidCreateSequential, может быть найдена здесь, которая включает в себя предпосылки для использования.

Ответ 2

Обновление 2018: Также проверьте мой другой ответ

Так NHibernate генерирует секвенциальные идентификаторы:

NHibernate.Id.GuidCombGenerator

/// <summary>
/// Generate a new <see cref="Guid"/> using the comb algorithm.
/// </summary>
private Guid GenerateComb()
{
    byte[] guidArray = Guid.NewGuid().ToByteArray();

    DateTime baseDate = new DateTime(1900, 1, 1);
    DateTime now = DateTime.Now;

    // Get the days and milliseconds which will be used to build the byte string 
    TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks);
    TimeSpan msecs = now.TimeOfDay;

    // Convert to a byte array 
    // Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 
    byte[] daysArray = BitConverter.GetBytes(days.Days);
    byte[] msecsArray = BitConverter.GetBytes((long) (msecs.TotalMilliseconds / 3.333333));

    // Reverse the bytes to match SQL Servers ordering 
    Array.Reverse(daysArray);
    Array.Reverse(msecsArray);

    // Copy the bytes into the guid 
    Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
    Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);

    return new Guid(guidArray);
}

Ответ 3

Возможно, простым способом определить порядок, в который были добавлены строки, было бы добавить столбец IDENTITY в таблицу, избегая необходимости держать ваши GUID в порядке и, следовательно, избегая повышения производительности при сохранении кластерного индекса на GUID.

Я не могу не задаться вопросом, как поддерживать эти строки в порядке, когда вы отлаживаетесь. Не могли бы вы немного расширить его?

Ответ 4

Важно отметить, что UUID, созданные с помощью UuidCreateSequential, не будут упорядочены по заказу SQL Server.

  • SQL Server следует RFC, когда дело доходит до сортировки UUID
  • RFC ошибся.
  • UuidCreateSequential сделал это правильно.
  • но UuidCreateSequential создает нечто отличное от того, что ожидает SQL Server

Фон

UUID типа 1, созданные UuidCreateSequential, не сортируются в SQL Server.

SQL Server NewSequentialID использует UuidCreateSequential, при этом применяется некоторая байт-перетасовка. Из онлайн-книг:

NEWSEQUENTIALID (Transact-SQL)

NEWSEQUENTIALID является оболочкой над функцией Windows UuidCreateSequential, с применяется некоторая байт-перетасовка

который затем ссылается на сообщение в блоге MSDN:

Как создать последовательные GUID для SQL Server в .NET (archive)

public static Guid NewSequentialId()
{
   Guid guid;
   UuidCreateSequential(out guid);
   var s = guid.ToByteArray();
   var t = new byte[16];

   t[3] = s[0];
   t[2] = s[1];
   t[1] = s[2];
   t[0] = s[3];

   t[5] = s[4];
   t[4] = s[5];
   t[7] = s[6];
   t[6] = s[7];
   t[8] = s[8];
   t[9] = s[9];
   t[10] = s[10];
   t[11] = s[11];
   t[12] = s[12];
   t[13] = s[13];
   t[14] = s[14];
   t[15] = s[15];

   return new Guid(t);
}

Все начинается с количества тиков с 1582-10-15 00:00:00 (15 октября 1592 года, даты  Грегорианская реформа к христианскому календарю). Клещи - это число интервалов в 100 нс.

Например:

  • 12/6/2017 4:09:39 UTC
  • = 137 318 693 794 503 714 тиков
  • = 0x01E7DA9FDCA45C22 тики

RFC говорит, что мы должны разделить это значение на три части:

  • UInt32 low (4 байта)
  • Uint16 mid (2 байта)
  • UInt32 hi (2 байта)

Итак, мы разделили его:

0x01E7DA9FDCA45C22

|   Hi   |   Mid  |    Low     |
|--------|--------|------------|
| 0x01E7 | 0xDA9F | 0xDCA45C22 |

И тогда RFC говорит, что эти три целых числа должны быть записаны в следующем порядке:

  • Низкий: 0xDCA45C22
  • Середина: 0xDA9F
  • Высокий: 0x01E7

Если вы следуете RFC, эти значения должны быть написаны в формате big-endian (иначе называемый "порядок байтов сети" ):

DC A4 5C 22 DA 9F x1 E7 xx xx xx xx xx xx xx xx

Это был плохой дизайн, потому что вы не можете взять первые 8 байтов UUID и относиться к ним как к большому концу UInt64, так и к малоинтенсивному UInt64. Это абсолютно немая кодировка.

UuidCreateSequential получает это право

Microsoft придерживалась всех тех же правил:

  • Низкий: 0xDCA45C22
  • Середина: 0xDA9F
  • Высокий: 0x1E7

Но они записывают его в Intel little-endian порядке:

22 5C A4 DC 9F DA E7 x1 xx xx xx xx xx xx xx xx

Если вы посмотрите на это, вы просто выписали little-endian Int64:

225CA4DC9FDAE701

Значение:

  • если вы хотите извлечь временную метку
  • или сортировать по метке времени

это тривиально; просто обрабатывайте первые 8 байтов как UInt64.

В RFC у вас нет выбора, кроме как выполнять все виды бит-бит. Даже на машинах большого конца вы не можете обрабатывать 64-битную метку времени как 64-битную метку времени.

Как отменить его

Учитывая малый endian guid от UuidCreateSequential:

DCA45C22-DA9F-11E7-DDDD-FFFFFFFFFFFF

с необработанными байтами:

22 5C A4 DC 9F DA E7 11 DD DD FF FF FF FF FF FF

Это декодируется на:

Low      Mid  Version High
-------- ---- ------- ---- -----------------
DCA45C22-DA9F-1       1E7 -DDDD-FFFFFFFFFFFF
  • Низкий: 0xDCA45C22
  • Середина: 0xDA9F
  • Высокий: 0x1E7
  • Версия: 1 (тип 1)

Мы можем записать это обратно в RFC-порядке:

DC A4 5C 22 DA 9F 11 E7 DD DD FF FF FF FF FF FF

Краткая версия

               |   Swap      | Swap  | Swap  | Copy as-is
Start index    |  0  1  2  3 |  4  5 |  6  7 | 
End index      |  3  2  1  0 |  5  4 |  7  6 | 
---------------|-------------|-------|-------|------------------------ 
Little-endian: | 22 5C A4 DC | 9F DA | E7 11 | DD DD FF FF FF FF FF FF
Big-endian:    | DC A4 5C 22 | DA 9F | 11 E7 | DD DD FF FF FF FF FF FF

Ответ 5

Unfortunatley, нет эквивалента .NET для newsequentialid(). Вы можете продолжить использование Comb. У меня на самом деле есть реализация С# для гребня где-то... Я посмотрю, смогу ли я его выкопать.

Ответ 6

Вот код С# для создания GUID COMB.

byte[] guidArray = System.Guid.NewGuid().ToByteArray();

DateTime baseDate = new DateTime(1900, 1, 1);
DateTime now = DateTime.Now;

// Get the days and milliseconds which will be used to build the byte string 
TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks);
TimeSpan msecs = new TimeSpan(now.Ticks - (new DateTime(now.Year, now.Month, now.Day).Ticks));

// Convert to a byte array 
// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333 
byte[] daysArray = BitConverter.GetBytes(days.Days);
byte[] msecsArray = BitConverter.GetBytes((long)(msecs.TotalMilliseconds / 3.333333));

// Reverse the bytes to match SQL Servers ordering 
Array.Reverse(daysArray);
Array.Reverse(msecsArray);

// Copy the bytes into the guid 
Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);

return new System.Guid(guidArray);

Ответ 7

Для людей, которые специально используют Entity Framework, вы можете сохранить хранимую процедуру на сервере, чтобы сгенерировать новый последовательный идентификатор и вернуть идентификатор. Затем вы можете использовать этот последовательный идентификатор для заполнения другой таблицы. Я думаю, это должно сработать.

Ответ 8

Ключевой проблемой является знание последнего значения в приложении .NET. SQL Server отслеживает это для вас. Вам нужно будет сохранить последнее значение самостоятельно и использовать конструктор Guid с байтовым массивом, содержащим следующее значение. Конечно, в распределенном приложении это, вероятно, не поможет, и вам, возможно, придется использовать рандомизированные гиды. (Не то, чтобы я не вижу в этом ничего плохого.)

http://msdn.microsoft.com/en-us/library/90ck37x3.aspx

Ответ 9

Мне повезло, что случайные Гиды могут быть полезны для производительности в некоторых случаях использования. По-видимому, вставка на случайные страницы может избежать конкуренции, которая в противном случае возникла бы на конечной странице, когда несколько человек пытаются вставить одновременно.

Предложения John PInvoke, вероятно, наиболее близки к SQL-версии, но в документах UUidCreateSequential указано, что вы не должны использовать его для идентификации объекта, который он строго локален для машины, генерирующей Guid.

Я бы оценил фактическую эффективность использования прецедентов с реалистичными данными в реалистичных количествах, прежде чем я буду изучать последовательное построение Guid.

Ответ 10

О выбранном ответе. Документы говорят... Созданный Guid не даст вам uniqueId между компьютерами, если у них нет доступа к Интернету.

Если вы должны знать руководство при вставке, не могли бы вы позволить Sql-серверу вернуть блок последовательных указателей, которые вы назначаете своим данным, прежде чем вставлять их?

declare @ids table(id uniqueidentifier default NEWSEQUENTIALID(), dummy char(1))

declare @c int
set @c = 0;
while (@c < 100)
begin
    insert into @ids (dummy) values ('a');
    set @c += 1;
end

select id from @ids

Ответ 11

Для этого вы можете использовать крошечную библиотеку NewId.

Установите его через NuGet:

Install-Package NewId

И используйте его вот так:

Guid myNewSequentialGuid =  NewId.NextGuid();

См. Страницу проекта на GitHub