Самый быстрый способ вставки, обновления, выборки SQL Server

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

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

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

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

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

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

Ответ 1

В этом ответе основное внимание уделяется операциям "select" vs update/create/delete. Я думаю, что реже обновлять более чем одну или несколько записей за раз, и поэтому я также думаю, что "выбор" - это то место, где узкие места имеют тенденцию возникать. Тем не менее, вам нужно знать ваше приложение (профиль). Лучшее место для фокусировки вашего времени оптимизации почти всегда находится на уровне базы данных в самих запросах, а не на клиентском коде. Клиентский код - это всего лишь сантехника: это не основная сила вашего приложения. Однако, поскольку plumbing, как правило, повторно используется во многих различных приложениях, я сочувствую желанию максимально приблизить его к оптимальному, и поэтому у меня есть много возможностей сказать, как построить этот код.

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

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;
       }
     );
}

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

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


Я хочу продолжить, однако, объяснить, как все это сочетается. Остальное довольно прямолинейно, но также легко бросить это в список или схожий и получить что-то не так, что в конечном итоге усугубляет производительность. Поэтому, продвигаясь дальше, бизнес-уровень использует factory для преобразования результатов запроса в объекты (С# 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]
            //...
        };
    }
}

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

Мне нужно внести одно изменение в исходный метод извлечения. Этот метод "возвращает" один и тот же объект снова и снова, и это не всегда так хорошо работает. То, что мы хотим сделать по-другому, чтобы заставить его работать, - заставить копию объекта, представленного текущей записью, так что, когда читатель мутирует для следующей записи, мы работаем с чистыми данными. Я ждал, пока не покажу метод factory, поэтому мы можем использовать его в конечном коде. Новый метод 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;)

Ответ 2

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

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

<query name="LoadCustomerByName" returns="Customer">
  <parameter name="name" type="String"/>
  <text>SELECT ... FROM Customers WHERE [email protected]</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

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

Ответ 4

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

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

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

Ответ 6

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

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