Что делает параметр buffered в сети точек Dapper?

Dapper dot net имеет параметр buffer (логическое значение), но, насколько я могу судить, единственное действие, которое он делает, — это преобразование результата в список перед его возвратом.

Согласно документации:

По умолчанию Dapper выполняет ваш sql и буферизует весь ридер по возвращении. В большинстве случаев это идеально, поскольку сводит к минимуму общие блокировки в базе данных и сокращает время работы сети с базой данных.

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

Я не уверен, как это достигается приведением результата к списку. Я что-то упускаю? Моя единственная идея состоит в том, что предполагается установить CommandBehavior для ExecuteReader на CommandBehavior.SequentialAccess (но это не так).


person smdrager    schedule 02.10.2012    source источник
comment
Дубликат Объяснения dapper buffer/cache, который имеет немного более длинный и подробный ответ @Marc (хотя, по общему признанию, на самом деле был задан после этого вопроса).   -  person Groo    schedule 21.10.2014
comment
@Groo, если другой вопрос был задан после этого, то другой вопрос является дубликатом, а не этот.   -  person kristianp    schedule 27.02.2019


Ответы (3)


но, насколько я могу судить, единственное, что он делает, это приводит результат к списку, прежде чем вернуть его

Вы ничего не упускаете. Это ключевое отличие. За исключением того, что это не приведение как таковое: фактически возвращаемый объект сильно отличается. По сути, есть два способа чтения данных:

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

Если вы читаете очень большой объем данных (от тысяч до миллионов строк), небуферизованный API может быть предпочтительнее. В противном случае используется много памяти, и может возникнуть заметная задержка еще до того, как будет доступна первая строка. Однако в большинстве распространенных сценариев объем считываемых данных находится в разумных пределах, поэтому разумно поместить их в список перед передачей вызывающей стороне. Это означает, что команда /reader и т. д. завершилась до возврата.

В качестве примечания, буферизованный режим также позволяет избежать очень распространенного «уже есть открытое средство чтения в соединении» (или что-то еще в точной формулировке).

person Marc Gravell    schedule 02.10.2012

Я должен не согласиться с @chris-marisic в этом... Я столкнулся с несколькими исключениями «Недостаточно памяти» именно в этой строке (data.ToList()) при использовании buffered:true. Это был не запрос «миллион строк X базиллион столбцов», а просто обычный SQL-результат из 5-6 тысяч строк с примерно 30 столбцами.

Это действительно зависит от вашей конфигурации. Например. независимо от того, работают ли ваши SQL и IIS на одной физической машине или нет. И сколько памяти установлено на машине IIS, и каковы настройки файла подкачки и т. д. Если веб-сервер имеет 2 ГБ или меньше - рассмотрите возможность установки «буферизованного: ложь» для сверхтяжелых отчетов.

person jazzcat    schedule 19.05.2016
comment
Чтобы уточнить это: data.ToList() все равно создает список полностью в памяти. Поэтому, если вы используете buffered:true, вы, вероятно, получите список в памяти дважды. - person frankhommers; 11.02.2017
comment
Под data.ToList() я ссылался на настоящую строку из исходного кода dapper. - person jazzcat; 06.05.2017

На практике лучше никогда не использовать buffered: false.

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

Если ваши наборы результатов меньше многих миллиардов значений, по какой-либо причине не стоит использовать buffered: false.

Во время фактического анализа я был шокирован тем, что чтение гигабайт данных с Sql Server было быстрее (в 2-6 раз быстрее) и более эффективно использовало память в стандартном буферизованном режиме. Повышение производительности учитывает даже самую маленькую возможную операцию, добавление объекта в разреженный массив по индексу в массив, размер которого не изменяется. При переключении разреженного массива размером в несколько гигабайт с небуферизованного на буферизованное время загрузки увеличилось в 2 раза. Запись в словарь с использованием буферизации привела к 6-кратному увеличению времени загрузки при вставке миллионов записей (словарь использовал int PK таблицы в качестве ключа, чтобы максимально возможно было вычислить хэш-код).

Как и во всем, что касается производительности, вы всегда должны анализировать. Однако я могу сказать вам с очень высокой степенью уверенности: всегда начинайте с буферизованного по умолчанию поведения Dapper.

person Chris Marisic    schedule 27.05.2015
comment
Крис, когда вы говорите о заполнении массива или записи в словарь, вы говорите о том, как вы обрабатываете результаты, которые Dapper уже вернул? Так что технически это профилирование вашего собственного кода после буферизации-чтения? Или вы говорите об обработке результатов из SQL Server на более низком уровне? - person Rich; 20.01.2016
comment
@Rich Использование foreach(item in unbuffered) dictionary.Add(item.Id, item) было в 6 раз медленнее, чем bufferedquery.ToDictionary(x=> x.Id). Существует окончательная стоимость задержки для использования небуферизованного. (Я читаю наборы из миллионов и сотен миллионов, обратите внимание, что я также использовал конструктор емкости правильного размера для словаря) - person Chris Marisic; 20.01.2016
comment
Так что вы действительно говорите о том, что я думал. Потрясающий. Очень полезная информация, спасибо. - person Rich; 20.01.2016
comment
Не знаю, какой у вас рабочий процесс, но использование buffered = true для миллионов строк очень небрежно к ресурсам. При развертывании на компьютерах с ограниченными ресурсами он может работать очень медленно (из-за подкачки страниц) или просто давать сбой из-за размера строки типичных бизнес-данных. - person Kasey Speakman; 01.04.2018
comment
@KaseySpeakman, а что, если экономическое обоснование заключается в загрузке bмиллиона записей в память? - person Chris Marisic; 11.04.2018
comment
@ChrisMarisic Мы используем небуферизованный для экспорта данных в CSV с серверных узлов AWS с низким объемом памяти/процессора. Это может быть много данных (широкие строки), но процесс по-прежнему использует мало памяти. Он отлично подходит для этого. - person Kasey Speakman; 11.04.2018
comment
@KaseySpeakman, разве нет собственного способа экспорта данных в CSV для сервера sql? - person Chris Marisic; 11.04.2018
comment
@ChrisMarisic Возможно, но я использую Postgres, и файл сохраняется непосредственно на S3. - person Kasey Speakman; 12.04.2018
comment
@KaseySpeakman, если вы записываете файл на S3, разве вы уже не буферизуете 100% всего в память? Я почти уверен, что S3 не поддерживает блочную запись. - person Chris Marisic; 12.04.2018
comment
@ChrisMarisic Мы используем поддержку S3 многостраничной загрузки. Где мы загружаем части за раз, а S3 объединяет их по завершении. Достаточно сказать, что есть хорошие варианты использования как для небуферизованных, так и для буферизованных запросов Dapper. Мы используем буферизацию для большинства вызовов API, но мы также ограничиваем количество результатов, чтобы избежать проблем с ресурсами. - person Kasey Speakman; 12.04.2018
comment
@ChrisMarisic Ах, мой путь или шоссе. Не беспокойся. Зрители могут составить собственное мнение. - person Kasey Speakman; 13.04.2018
comment
Возможно, с момента написания этого ответа что-то изменилось, но текущий код Dapper говорит: return buffered ? results.ToList() : results; И results - это yield return строк, когда они поступают из соединения. Какова ваша теория о том, что d.ToList().ToDictionary() в 6 раз быстрее, чем d.ToDictionary()? - person Daniel Earwicker; 10.10.2019
comment
@DanielEarwicker, вероятно, снова и снова создает множество промежуточных коллекций, поскольку коллекция неизвестной длины гидратируется. Также возможно, что существует намного больше переключений контекста, чем попытка закрыть потоки ввода-вывода как можно скорее с помощью ToList. - person Chris Marisic; 11.10.2019
comment
@ChrisMarisic Я почти уверен, что если у вас есть хранилище данных или вам нужно фактически возвращать миллиарды записей несколько раз одновременно, ваш единственный вариант - использовать небуферизованный, если, конечно, у вас нет 1 ТБ ОЗУ. Все существует по какой-то причине, это не то, что вы просто считаете бесполезным или считаете плохой практикой. Очевидно, что если вы можете буферизовать любые данные в память, это будет намного быстрее, чем не буферизовать их, но иногда у вас просто нет ресурсов для этого или это считается глупым. Все можно злоупотреблять. - person SpiritBob; 15.05.2020
comment
@SpiritBob для справки, все это было измерено при чтении около 4 миллиардов строк за несколько минут каждые несколько часов. И да, механизм создания отчетов должен иметь доступ ко всем данным в памяти. Что, если ваши данные не представляют собой большие случайные строки, это всего лишь дюжина или две дюжины гигабайт памяти. Не 100 и не 1000. - person Chris Marisic; 22.05.2020