#c# #performance #transactions
#c# #Производительность #транзакции
Вопрос:
В моем веб-приложении есть класс WorkItem
с RecordID
(Guid в качестве первичного ключа) и FriendlyID
(string), который состоит из Type-Date-RandomNumbers
.
Если я создаю новый рабочий элемент, я также создаю новый FriendlyId.
Формат FriendlyId не может быть изменен (спецификация клиента) и является подобным <Type (one char)>-<Current Date (yyymmdd)>-<6 random numbers>
.
private string GenerateFriendlyID()
{
string res = String.Empty;
// code omited
// ...
// IT'S NOT THE QUESTION HOW TO PROGRAM THIS METHOD!
// It's about the fastest and best way/design to make
// sure the generated ID is unique! (see below)
return res; // sth like "K-20110930-158349"
}
public override void Create()
{
if (String.IsNullOrEmpty(friendlyID))
{
GenerateFriendlyID();
}
base.Create();
}
Этот код не работает при большой нагрузке, поэтому я получаю одни и те же идентификаторы FriendlyId несколько раз.
Каков наилучший способ убедиться, что мой дружественный идентификатор уникален?
- Создайте ограничение УНИКАЛЬНОСТИ для FriendlyId в БД.
- Запустите транзакцию, сгенерируйте дружественный идентификатор, вставьте и зафиксируйте
- Откат и повторите попытку, если я получу исключение SQLException.
- Просто создайте его.
- Выберите все рабочие элементы с
this.FriendlyID
помощью . - Если выбор есть
> 1
, повторяйте, пока он не== 1
- Выберите все рабочие элементы с
Я уверен, что есть другой способ, но я думаю, что # 1 должен быть предпочтительным.
Есть ли какие-то способы, которые мне не хватает, или # 1 — это путь? Я ненавижу использовать исключения для своего рабочего процесса, хотя и знаю, что они действительно медленные.
Комментарии:
1. Какие требования предъявляются к идентификатору, чтобы быть «дружественным»?
2. Почему вы не можете использовать
RecordID
asFriendlyID
какие-либо требования дляFriendlyID
?3. Почему у вас есть два столбца для идентификации строки в таблице? У вас должен быть только один. Другое дело, что вы не должны использовать GUID в качестве кластеризованного индекса (я предполагаю, что вы используете значение по умолчанию из mssql, PK)
4. Я не могу понять, почему заголовок вопроса содержит
best performance
5. Вопрос не в том, как сгенерировать FriendlyId, речь идет о лучшем и быстром способе убедиться, что он уникален , и сгенерировать новый, если это не так.
Ответ №1:
Мое предложение в любом случае, какой бы идентификатор вы ни хотели сгенерировать, делайте это в SQL в хранимой процедуре, а не из .ЧИСТЫЙ клиентский код. Всегда лучше иметь атомарную точку входа, которая принимает некоторые параметры и выполняет работу, чтобы вы могли вызвать сохраненную и сохранить свою запись, а идентификатор вернуть вам в качестве параметра out, даже больше, чем один, например, уникальный код и идентификатор guid.
таким образом, вы перемещаете проблемы параллелизма из .СЕТЕВОЙ клиентский код для сервера базы данных и серверов БД спроектирован так, чтобы хорошо обрабатывать параллелизм.
Комментарии:
1. Я не уверен, что это возможно с нашей текущей архитектурой, но я мог бы создать триггер, который вызывается после вставки и в основном делает то же самое. Спасибо за идею!
2. пожалуйста, не делайте этого! избегайте такого рода разреженной логики, просто используйте правильный уникальный индекс и ограничение PK и делайте все в сохраненной процедуре. Требует определенных усилий, но действительно окупается… Я видел так много триггеров ни для чего, например, установка значения по умолчанию или выполнение других действий, которые могут и должны выполняться обычными ограничениями по умолчанию…
Ответ №2:
Поскольку ваш RecordID
уже основан на идентификаторе GUID, я бы проанализировал это, чтобы создать дружественный идентификатор. Guid.ToByteArray()
может быть полезно начать.
Комментарии:
1. Вопрос не в создании идентификатора, а в том, чтобы убедиться, что он уникален
2. Моя точка зрения / полностью /. GUID / гарантированно / уникальны. Все другие (надежные) подходы, выполняемые на стороне клиента, будут связаны с блокировками и, следовательно, потенциальными конфликтами; это может быть непреодолимым, если вы запускаете этот код, например, из фермы веб-серверов без межпроцессного взаимодействия. предложение @ Davide перенести генератор на сервер базы данных уменьшит падение производительности, поскольку тогда будет только одно место, где генерируются идентификаторы, но это не позволит избежать этого полностью.
Ответ №3:
Используйте шаблон KeyGenerator из PoEAA М. Фаулера. Вот пример решения для файловой системы, и оно использует мьютекс для межпроцессной блокировки. В случае MS SQL вы можете использовать транзакцию вместо мьютекса.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.IO;
using System.Runtime.CompilerServices;
namespace ConsoleApplication1
{
public class KeyGenerator
{
private string FileName;
private long IncrementedBy;
private long NextId;
private long MaxId;
public KeyGenerator(string filename, long incrementedby)
{
FileName = filename;
IncrementedBy = incrementedby;
NextId = MaxId = 0;
}
//[MethodImpl(MethodImplOptions.Synchronized)]
public long NextID()
{
if (NextId == MaxId)
{
reserveIds();
}
return NextId ;
}
private void reserveIds()
{
Mutex m = new Mutex(false, "Mutex " FileName.Replace(Path.DirectorySeparatorChar, '_'));
try
{
m.WaitOne();
string s = File.ReadAllText(FileName);
long newNextId = long.Parse(s);
long newMaxId = newNextId IncrementedBy;
File.WriteAllText(FileName, newMaxId.ToString());
NextId = newNextId;
MaxId = newMaxId;
// Simulate some work.
Thread.Sleep(500);
}
finally
{
m.ReleaseMutex();
}
}
}
}