Является ли .NET равным SQL-серверам newsequentialid()
Мы используем Guid для первичного ключа, который, как вы знаете, кластеризуется по умолчанию.
При вставке новой строки в таблицу она вставлена на случайную страницу в таблице (поскольку Guid является случайным). Это имеет измеримое влияние на производительность, поскольку БД все время разбивает страницы данных (фрагментация). Но основная причина того, что последовательный Guid заключается в том, что я хочу, чтобы новые строки были вставлены в качестве последней строки в таблице... что поможет при отладке.
Я мог бы создать кластерный индекс в CreateDate, но наша БД автоматически генерируется и в разработке, нам нужно сделать что-то дополнительное, чтобы облегчить это. Также CreateDate не является хорошим кандидатом для кластеризованного индекса.
В тот же день я использовал Jimmy Nielsons COMB, но мне было интересно, есть ли что-то в платформе.NET для этого. В SQL 2005 Microsoft представила newsequentialid() в качестве альтернативы newid(), поэтому я надеялся, что они сделали эквивалент.NET, потому что мы генерируем ID в коде.
PS: Пожалуйста, не начинайте обсуждать, правильно это или нет, потому что GUID должен быть уникальным и т.д.
Ответы
Ответ 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