Алгоритм Hi/Lo полезен, когда вам нужны уникальные ключи. Если коротко, то алгоритм Hi/Lo описывает механизм генерации безопасных идентификаторов на стороне клиента, а не в базе данных (безопасных в этом контексте означает отсутствие коллизий). Он задает уникальные идентификаторы строкам таблицы, не в зависимости от того, будет ли сразу храниться строка в базе данных или нет. Это позволяет сразу же использовать идентификаторы, как обычные последовательные идентификаторы базы данных.
Последовательности (sequences) баз данных кэшируются, масштабируются и решают проблемы параллелизма. Но для каждого нового значения последовательности нам постоянно нужно обращаться в базу данных. И когда у нас есть большое количества инсертов то это становится немного накладно. Поэтому, для оптимизации работы с последовательностями используют алгоритм Hi/Lo. EntityFramework Core поддерживает Hi/Lo «из коробки» с помощью метода
ForSqlServerUseSequenceHiLo
Как работает Hi/Lo
Начнем с того что такое Hi/Lo. Основная идея состоит в том, что у вас есть два числа, чтобы составить первичный ключ — «высокий»(high) номер и «низкий»(low) номер. Клиент может в основном увеличивать «высокую» последовательность, зная, что он может безопасно генерировать ключи из всего диапазона предыдущего «высокого» значения с множеством «низких» значений.
Например, предположим, что у вас есть «высокая» последовательность с текущим значением 35, а «низкий» номер находится в диапазоне 0-1023. Затем клиент может увеличить последовательность до 36 (для других клиентов, чтобы иметь возможность генерировать ключи при использовании 35) и знать, что ключи 35/0, 35/1, 35/2, 35/3… 35/1023 являются все доступные.
Может быть очень полезно (особенно с ORM), чтобы иметь возможность устанавливать первичные ключи на стороне клиента вместо того, чтобы вставлять значения без первичных ключей, а затем извлекать их обратно на клиент. Помимо всего прочего, это означает, что вы можете легко создавать отношения между родителями и дочерними элементами и иметь все ключи на месте, прежде чем делать какие-либо вставки, что упрощает их батчинг(batching).
Используем Hi/Lo для генерации ключей в Entity Framework Core
Давайте посмотрим, как использовать Hi/Lo для генерации ключей с помощью Entity Framework Core. Допустим у нас есть простая модель Категорий:
public class Category
{
public int Id { get; set; }
public string Name { get; set; }
}
Помним что EF конфигурирует свойство
Id
или <имя типа>Id
в качестве ключа. Теперь нам нужно создать DBContext:public class SampleDBContext : DbContext
{
public SampleDBContext()
{
Database.EnsureDeleted();
Database.EnsureCreated();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionBuilder)
{
string сonnString = @"Server=localhost\SQLEXPRESS01;Database=EFSampleDB;Trusted_Connection=true;";
optionBuilder.UseSqlServer(сonnString);
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
var entityTypeBuilder = modelBuilder.Entity<Category>();
entityTypeBuilder.ToTable("Category");
entityTypeBuilder.HasKey(t => t.Id);
entityTypeBuilder.Property(t => t.Id).ForSqlServerUseSequenceHiLo();
entityTypeBuilder.Property(t => t.Name).HasMaxLength(128).IsRequired();
}
public DbSet<Category> Category { get; set; }
}
DbContext состоит из:
- конструктора
SampleDBContext
который представляет собой реализацию инициализатора базы данныхDropCreateDatabaseAlways
; - метода
OnConfiguring
для настройки DBContext; - метода
OnModelCreating
— это место, где можно определить конфигурацию модели. Чтобы определить последовательность Hi/Lo, используйте метод расширенияForSqlServerUseSequenceHiLo
.
Запускаем приложение. В результате должна создаться база «EFSampleDB» с таблицей «Category» и с последовательностью «EntityFrameworkHiLoSequence». Имя последовательности и схему в которой она будет создана можно передать как параметр:
ForSqlServerUseSequenceHiLo(string name, string schema)
.Скрипт создания «EntityFrameworkHiLoSequence» виглядит следюющим образом
CREATE SEQUENCE [dbo].[EntityFrameworkHiLoSequence]
AS [bigint]
START WITH 1
INCREMENT BY 10
MINVALUE -9223372036854775808
MAXVALUE 9223372036854775807
CACHE
GO
Данная последовательность начинается с 1 и увеличивается на 10. Существует разница между Sequence и Hi/Lo Sequence относительно опции
INCREMENT BY
. В Sequence INCREMENT BY
добавляет значение к предыдущему значению последовательности, чтобы сгенерировать новое значение. Таким образом, в этом случае, если ваше предыдущее значение последовательности равно 11, тогда следующее значение последовательности будет равно 11 + 10 = 21. В случае Hi/Lo Sequence параметр INCREMENT BY
обозначает значение блока, это означает, что следующее значение последовательности будет выбрано после использования первих 10.Давайте добавим некоторые данные в базу:
using (var dataContext = new SampleDBContext())
{
dataContext.Category.Add(new Category { Name = "Name-1" });
dataContext.Category.Add(new Category { Name = "Name-2" });
dataContext.Category.Add(new Category { Name = "Name-3" });
dataContext.SaveChanges();
}
Как только этот код попадает в строку, где добавляется категория в DBContext, выполняется вызов базы данных, чтобы получить значение последовательности. Это можно проверить его с помощью SQL Server Profiler.
Когда вызывается
dataContext.SaveChanges();
все 3 категории будут сохранены с значениями первичного ключа которые уже сгенерированные и выбираются только один раз.Значение последовательности не будет выбрано из базы данных пока не исчерпана часть Lo (10 записей в нашем случае), тольки при добавлении 11-ой записи будет выполняться вызов базы данных, чтобы получить следующее значение последовательности (Hi).
Можно создать одну последовательность нескольких таблиц:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ForSqlServerUseSequenceHiLo("DBSequenceHiLo");
}
Настройка последовательности Hi/Lo
В метода
ForSqlServerHasSequence
нет никаких параметров для изменения начального значения и значения инкременты. Но можно указать эти значения следующим образом, сначала определяем последовательность с параметрами StartAt
и IncrementBy
а дальше используем тот же метод расширения ForSqlServerUseSequenceHiLo
.modelBuilder.HasSequence<int>("DBSequenceHiLo").StartsAt(1000).IncrementsBy(5);
modelBuilder.ForSqlServerUseSequenceHiLo("DBSequenceHiLo");
В этом случае sql-скрипт для DBSequenceHiLo будет иметь следующий вид
CREATE SEQUENCE [dbo].[DBSequenceHiLo]
AS [int]
START WITH 1000
INCREMENT BY 5
MINVALUE -2147483648
MAXVALUE 2147483647
CACHE
GO
Теперь, когда мы добавляем три категорий, значение ключа начинается с 1000. А так как параметр
IncrementBy
установлен на «5», то, когда будет добавлена шестая запись в контекст, будет сделан запрос в базу данных, чтобы получить следующее значение последовательности. Если поменять код, и добавить еще 3 категории то на скрине Вы можете увидеть второй вызов базы данных.Спасибо за внимание!
Комментарии (3)
vladimirkolyada
20.02.2018 08:34Если он не последователен то не подходит для ключа в подавляющем большинстве случаев использования индексов, будет приводить к безудержной фрагментации. Если он последовательный, то как решаются коллизии?
JohnLemon
А чем подход использование Hi-Lo лучше Guid? поискал сам но ничего кроме «красивее» айди не нашел
Deosis
Guid очень случаен и плохо подходит для вставки большого количества элементов.
Поэтому и возникают схемы, в которых клиенту отдается диапазон идентификаторов.
Либо часть ключа берется из метки времени, что позволяет хранить записи в порядке добавления.