Самый быстрый метод для SQL Server вставки, обновления, выбирает - PullRequest
10 голосов
/ 19 мая 2010

Я использую SP, и это не вопрос SP против программного кода "Build your SQL command". Я ищу высокопроизводительный метод для бэкэнда, который обрабатывает множество мелких транзакций. Я использую SQLDataReader для большинства возвратов, так как пересылка работает только в большинстве случаев для меня.

Я видел, как это было сделано многими способами, и сам использовал большинство из них.

  1. Методы, которые определяют и принимают параметры хранимой процедуры в качестве самих параметров и создают с использованием cmd.Parameters.Add (с указанием или без указания типа значения БД и / или длины)

  2. Сборка ваших параметров SP и их значений в массив или хеш-таблицу, затем переход к более абстрактному методу, который анализирует коллекцию и затем запускает cmd.Parameters.Add

  3. Классы, представляющие таблицы, инициализирующие класс по мере необходимости, устанавливающие открытые свойства, представляющие поля таблицы, и вызывающие методы, такие как Save, Load и т. Д.

Я уверен, что есть и другие, которых я видел, но не могу думать и сейчас. Я открыт для всех предложений.

Ответы [ 6 ]

29 голосов
/ 19 мая 2010

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

У меня есть общий метод для выбора запросов / процедур в моем слое данных, который выглядит примерно так:

private static IEnumerable<IDataRecord> Retrieve(string sql, Action<SqlParameterCollection> addParameters)
{
    //ConnectionString is a private static property in the data layer
    // You can implement it to read from a config file or elsewhere
    using (var cn = new SqlConnection(ConnectionString))
    using (var cmd = new SqlCommand(sql, cn))
    {
        addParameters(cmd.Parameters);

        cn.Open();
        using (var rdr = cmd.ExecuteReader())
        {
            while (rdr.Read())
                yield return rdr;
            rdr.Close();
        }
    }
}

И это позволяет мне писать общедоступные методы слоя данных, которые используют анонимные методы для добавления параметров. Показанный код работает с .Net 2.0+, но может быть написан еще короче, используя .Net 3.5:

public IEnumerable<IDataRecord> GetFooChildrenByParentID(int ParentID)
{
    //I could easily use a stored procedure name instead of a full sql query
    return Retrieve(
        @"SELECT c.* 
         FROM [ParentTable] p 
         INNER JOIN [ChildTable] c ON c.ParentID = f.ID 
         WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
       {
          p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
       }
     );
}

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

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


Однако я хочу продолжить, чтобы объяснить, как все это сочетается. Все остальное довольно просто, но также легко бросить это в список или тому подобное и получить что-то не так, что в конечном итоге снижает производительность. Итак, двигаясь дальше, бизнес-уровень затем использует фабрику для преобразования результатов запроса в объекты (c # 3.0 или новее):

public class Foo
{
    //various normal properties and methods go here

    public static Foo FooFactory(IDataRecord record)
    {
        return new Foo
        {
            Property1 = record[0],
            Property2 = record[1]
            //...
        };
    }
}

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

Мне нужно сделать одно изменение в исходном методе получения. Этот метод «возвращает» один и тот же объект снова и снова, и это не всегда работает так хорошо. Чтобы заставить его работать по-другому, нам нужно заставить копию объекта, представленного текущей записью, сделать так, чтобы при чтении следующей записи читатель работал с чистыми данными. Я подождал, пока не покажу фабричный метод, чтобы мы могли использовать его в конечном коде. Новый метод Retrieve выглядит следующим образом:

private static IEnumerable<T> Retrieve(Func<IDataRecord, T> factory,
                  string sql, Action<SqlParameterCollection> addParameters)
{
    //ConnectionString is a private static property in the data layer
    // You can implement it to read from a config file or elsewhere
    using (var cn = new SqlConnection(ConnectionString))
    using (var cmd = new SqlCommand(sql, cn))
    {
        addParameters(cmd.Parameters);

        cn.Open();
        using (var rdr = cmd.ExecuteReader())
        {
            while (rdr.Read())
                yield return factory(rdr);
            rdr.Close();
        }
    }
}

А теперь мы бы назвали этот новый метод Retrieve () следующим образом:

public IEnumerable<Foo> GetFooChildrenByParentID(int ParentID)
{
    //I could easily use a stored procedure name instead of a full sql query
    return Retrieve(Foo.FooFactory,
        @"SELECT c.* 
         FROM [ParentTable] p 
         INNER JOIN [ChildTable] c ON c.ParentID = f.ID 
         WHERE f.ID= @ParentID", delegate(SqlParameterCollection p)
       {
          p.Add("@ParentID", SqlDbType.Int).Value = ParentID;
       }
     );
}

Очевидно, что этот последний метод может быть расширен для включения любой необходимой дополнительной бизнес-логики. Также оказывается, что этот код исключительно быстрый, потому что он использует преимущества ленивых функций оценки IEnumerable. Недостатком является то, что он имеет тенденцию создавать много недолговечных объектов, и это может снизить производительность транзакций, о которой вы спрашивали. Чтобы обойти это, я иногда нарушаю хороший n-уровень и передаю объекты IDataRecord непосредственно на уровень представления и избегаю ненужного создания объектов для записей, которые просто связаны с элементом управления сеткой сразу.

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

Или я мог бы спасти вас, читая этот длинный пост, и просто сказать вам использовать Entity Framework;)

8 голосов
/ 19 мая 2010

Лично я большой поклонник генерации кода. Я свернул свой собственный доморощенный XML и во время сборки запускаю его через XSLT, чтобы сгенерировать мои файлы .CS. Я опишу процесс в этом посте Использование XSLT для генерации кода счетчиков производительности . Хотя ссылка обсуждает создание кода счетчиков производительности, я использую тот же процесс для создания моего DAL.

Итак, я бы создал XML как:

<query name="LoadCustomerByName" returns="Customer">
  <parameter name="name" type="String"/>
  <text>SELECT ... FROM Customers WHERE name=@name</text>
</query>

и тогда XLST преобразует это в нечто вроде:

public Customer LoadCustomerByName(
  SqlConnection conn,
  SqlTransaction trn,
  String name)
{
  using (Sqlcommand cmd = new SqlCommand(@"SELECT ... FROM ...", conn, trn))
  {
    cmd.Parameters.AddWithValue("@name", name);
    using (SqlDataReader rdr = cmd.ExecuteReader ())
    {
      Customer c = new Customer();
      // Load c from rdr
      return c;
    }
  }
}

Теперь я подробно рассказал о том, что на самом деле делает XSLT-преобразование, но действительно важно то, что этот метод дает мне абсолютный контроль над тем, как я создаю свой DAL, и он гибок во всех аспектах, начиная с сгенерированного. Код CS полностью основан на моих XSLT. Я могу изменить XLST, и это приведет к повторной генерации каждого метода в моем DAL. Это облегчает исследование различных решений, позволяет добавлять в код инструментарий (например, счетчики для измерения производительности и частоты использования каждого отдельного запроса) и многое другое.

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

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

Самый быстрый для времени выполнения или самый быстрый для времени программирования? Единственное, что вы можете сделать для увеличения пропускной способности # 1, - это использовать несколько потоков и соединений для выполнения вставок - вы можете сделать это с помощью SQLCommand.BeginExecuteNonQuery

2 голосов
/ 24 февраля 2015

Это уже давно, но если вы готовы использовать очень микро-ORM, просто используйте dapper-dot-net .

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

Некоторые сравнения производительности:

1 голос
/ 14 февраля 2013

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

0 голосов
/ 08 февраля 2013

Единственное, что мне не нравится, это то, что возвращаемый доход не может находиться в блоке try ... catch. Поэтому централизованная обработка исключений / ведение журнала не могут быть учтены.

Я использовал аналогичное appraoch, но передал IEnumerable в качестве параметра. Тогда мне не нужно использовать доходность.

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