Альтернативы LINQ To SQL на высоконагруженных страницах - PullRequest
3 голосов
/ 06 мая 2010

Для начала я ЛЮБЛЮ СВЯЗЬ С SQL. Это намного проще в использовании, чем прямой запрос.

Но есть одна большая проблема: она плохо работает при высоких нагрузках. У меня есть некоторые действия в моем проекте ASP.NET MVC, которые вызываются сотни раз каждую минуту.

Раньше у меня была LINQ to SQL, но поскольку количество запросов гигантское, LINQ TO SQL почти всегда возвращало «Строка не найдена или изменена» или «Не удалось обновить X из X». И это понятно. Например, я должен увеличивать значение на единицу с каждым запросом.

var stat = DB.Stats.First();
stat.Visits++;
// ....
DB.SubmitChanges();

Но пока ASP.NET работал над этими // ... инструкциями, значение stats.Visits, хранящееся в таблице, изменилось.

Я нашел решение, я создал хранимую процедуру

ОБНОВЛЕНИЕ Статистика SET Посещения = Посещения + 1

Хорошо работает.

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

Итак, мой вопрос, как решить эту проблему? Есть ли альтернативы, которые могут работать здесь?

Я слышал, что Stackoverflow работает с LINQ to SQL. И он более загружен, чем мой сайт.

Ответы [ 3 ]

4 голосов
/ 06 мая 2010

По сути, это не проблема Linq to SQL, это ожидаемый результат с оптимистичным параллелизмом, который по умолчанию использует Linq to SQL.

Оптимистический параллелизм означает, что при обновлении записи вы проверяете текущую версию в базе данных на соответствие копии, которая была первоначально получена, прежде чем делать какие-либо автономные обновления; если они не совпадают, сообщите о нарушении параллелизма («строка не найдена или изменена»).

Более подробное объяснение этого здесь . Существует также довольно значительное руководство по обработке ошибок параллелизма . Обычно решение заключается в простом перехвате ChangeConflictException и выборе разрешения, например:

try
{
    // Make changes
    db.SubmitChanges();
}
catch (ChangeConflictException)
{
    foreach (var conflict in db.ChangeConflicts)
    {
        conflict.Resolve(RefreshMode.KeepCurrentValues);
    }
}

Приведенная выше версия перезапишет все, что находится в базе данных, с текущими значениями, независимо от того, какие другие изменения были внесены. Другие возможности см. В перечислении RefreshMode .

Другой вариант - полностью отключить оптимистический параллелизм для полей, которые, как вы ожидаете, могут быть обновлены. Это можно сделать, установив параметр UpdateCheck на UpdateCheck.Never. Это должно быть сделано на уровне поля; Вы не можете сделать это на уровне сущности или глобально на уровне контекста.

Может быть, я должен также упомянуть, что вы не выбрали очень хороший дизайн для конкретной проблемы, которую вы пытаетесь решить. Увеличение «счетчика» путем многократного обновления одного столбца одной строки не очень хорошее / правильное использование реляционной базы данных. Что вы должны делать, так это поддерживать таблицу истории - например, Visits - и, если вам действительно нужно денормализовать счетчик, реализуйте это с помощью триггера в самой базе данных. Попытка реализовать счетчик сайта на уровне приложения без каких-либо данных для его резервного копирования - это просто проблема.

Используйте ваше приложение, чтобы поместить фактические данные в вашу базу данных, и позволить базе данных обрабатывать агрегаты - это одна из тех вещей, с которыми базы данных хорошо справляются.

3 голосов
/ 06 мая 2010

Используйте модель производителя / потребителя или очереди сообщений для обновлений, которые не обязательно должны происходить немедленно, особенно обновления статуса. Вместо того, чтобы пытаться обновить базу данных, немедленно сохраняйте очередь обновлений, которую потоки asp.net могут отправить, а затем создайте процесс / поток записи, который записывает очередь в базу данных. Поскольку только один поток пишет, будет меньше споров по соответствующим таблицам / ролям.

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

1 голос
/ 06 мая 2010

Во-первых, вы можете позвонить DB.SubmitChanges() сразу после stats.Visits++, и это значительно уменьшит проблему.

Однако это все равно не спасет вас от нарушения параллелизма (то есть одновременного изменения фрагмента данных двумя одновременными процессами). Чтобы бороться с этим, вы можете использовать стандартный механизм транзакций . С LINQ-to-SQL вы используете транзакции, создавая экземпляр класса TransactionScope, таким образом:

using( TransactionScope t = new TransactionScope() )
{
    var stats = DB.Stats.First();
    stats.Visits++;
    DB.SubmitChanges();
}

Обновление : , как правильно заметил Аарона, TransactionScope здесь не поможет. Сожалею. Но читайте дальше.

Будьте осторожны, чтобы не сделать тело транзакции слишком длинным, так как это заблокирует другие параллельные процессы и, таким образом, значительно снизит вашу общую производительность.

И это подводит меня к следующему пункту: ваш дизайн, вероятно, имеет недостатки.

Основной принцип работы с данными с высокой степенью общего доступа состоит в том, чтобы спроектировать ваше приложение таким образом, чтобы операции с этими данными были быстрыми, простыми и семантически понятными и выполнялись одна за другой, а не одновременно.

Одна операция, которую вы описываете - подсчет посещений - довольно понятна и проста, поэтому не должно возникнуть никаких проблем после добавления транзакции. Однако я должен добавить, что, хотя это будет понятно, безопасно для типов и в остальном «хорошо», решение с хранимой процедурой на самом деле является гораздо более предпочтительным. Это на самом деле именно так, как приложения баз данных были разработаны в прежние времена. Подумайте об этом: зачем вам нужно извлекать счетчик на всем пути от базы данных до вашего приложения (возможно, по сети!), Если в его обработке не задействована бизнес-логика. Сервер базы данных может также увеличивать его, даже не отправляя ничего обратно приложению.

Теперь, что касается других операций, которые скрыты за // ..., кажется (по вашему описанию), что они несколько тяжелые и длинные. Я не могу сказать наверняка, потому что я не вижу, что там, но если это так, вы, вероятно, хотите разделить их на более мелкие и более быстрые, или иначе переосмыслить свой дизайн. Я действительно не могу ничего сказать с этой маленькой информацией.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...