Подтвердить что ты не робот

Существуют ли какие-либо Ruby ORM, которые используют курсоры или интеллектуальную выборку?

Я ищу Ruby ORM для замены ActiveRecord. Я смотрел Sequel и DataMapper. Они выглядят неплохо, но ни один из них, похоже, не выполняет основную задачу: не загружать все в память, когда вам это не нужно.

Я имею в виду, что я пробовал следующее (или эквивалентное) в ActiveRecord и Sequel на таблице с большим количеством строк:

 posts.each { |p| puts p }

Оба они сходят с ума по памяти. Кажется, они загружают все в памяти, а не при необходимости извлекают материал. Я использовал find_in_batches в ActiveRecord, но это не приемлемое решение:

  • ActiveRecord не является приемлемым решением, потому что с ним было слишком много проблем.
  • Почему мой код должен знать о пейджинговом механизме? Я рад настроить где-то размер страницы, но это так. С помощью find_in_batches вам нужно сделать что-то вроде:

    post.find_in_batches {| batch | batch.each {| p | puts p}}

Но это должно быть прозрачным.

Так есть где-то надежный Ruby ORM, который правильно выполняет выборку?


Update:

Как упоминал Серджио, в Rails 3 вы можете использовать find_each, что именно то, что я хочу. Однако, поскольку ActiveRecord не является вариантом, за исключением случаев, когда кто-то действительно может убедить меня использовать его, вопросы:

  • Какие ORM поддерживают эквивалент find_each?
  • Как это сделать?
  • Зачем нам нужен find_each, а find должен это делать, не так ли?
4b9b3361

Ответ 1

Sequel Dataset#each дает отдельные строки за раз, но большинство драйверов базы данных сначала загружают весь результат в памяти.

Если вы используете адаптер Sequel Postgres, вы можете использовать реальные курсоры:

posts.use_cursor.each{|p| puts p}

По умолчанию выбирает 1000 строк за раз, но вы можете использовать опцию, чтобы указать количество строк для захвата на выбор курсора:

posts.use_cursor(:rows_per_fetch=>100).each{|p| puts p}

Если вы не используете адаптер Sequel Postgres, вы можете использовать расширение растягивания Sequel:

Sequel.extension :pagination
posts.order(:id).each_page(1000){|ds| ds.each{|p| puts p}}

Однако, как и ActiveRecord find_in_batches/find_each, это делает отдельные запросы, поэтому вам нужно быть осторожными, если есть параллельные изменения в наборе данных, который вы извлекаете.

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

Как минимум с поддержкой курсора адаптера Postgres, довольно легко сделать его по умолчанию для вашей модели:

Post.dataset = Post.dataset.use_cursor

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

Ответ 2

ActiveRecord фактически имеет почти прозрачный пакетный режим:

User.find_each do |user|
  NewsLetter.weekly_deliver(user)
end

Ответ 3

Sequel.extension :pagination
posts.order(:id).each_page(1000) do |ds|
  ds.each { |p| puts p }
end

Это очень медленно на больших таблицах!

Становится ясно, посмотрел на тело метода: http://sequel.rubyforge.org/rdoc-plugins/classes/Sequel/Dataset.html#method-i-paginate

# File lib/sequel/extensions/pagination.rb, line 11

def paginate(page_no, page_size, record_count=nil)
  raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
  paginated = limit(page_size, (page_no - 1) * page_size)
  paginated.extend(Pagination)
  paginated.set_pagination_info(page_no, page_size, record_count || count)
end

Ответ 4

Этот код работает быстрее, чем find_in_batches в ActiveRecord

id_max = table.get(:max[:id])
id_min = table.get(:min[:id])
n=1000
(0..(id_max-id_min)/n).map.each do |i|
    table.filter(:id >= id_min+n*i, :id < id_min+n*(i+1)).each {|row|}
end

Ответ 5

Возможно, вы можете рассмотреть Ohm, который основан на Redis NoSQL store.