Переосмысление атомарных операций базы данных

Допустим, у меня есть документ

{
    id: 1,
    fruits: []
}

фрукты здесь выступает как НАБОР

Теперь я хочу атомарно добавить значение в массив фруктов для документа с первичным ключом = 1 ИЛИ создать такой документ, если он не существует (т.е. использовать SetInsert ReQL под капотом)

Мне также нужно сделать то же самое для увеличения (ReQL .Add)

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

Я хотел бы, чтобы что-то подобное было возможно

r.table('results').insert({
  id: '62c70132-6516-4279-9803-8daf407ffa5c',
  counter: r.row('counter').add(1).default(0)
}, {conflict: "update"})

но он умирает с «RqlCompileError: r.row не определен в этом контексте в»

Любая помощь / руководство приветствуется, спасибо!


person let4be    schedule 26.07.2015    source источник
comment
r.row в основном означает текущую строку для каждой строки, с которой вы будете работать в ReQL-языке. Во вставке r.row не имеет смысла, поэтому возникает эта ошибка. Однако, если вы ищете конкретный документ, то нет причин, по которым вы не можете получить поле документа с помощью подзапроса, см. мой ответ ниже по этому поводу :)   -  person Chris Foster    schedule 26.07.2015


Ответы (2)


В настоящее время это невозможно с insert. Другое упомянутое решение не является атомарным, поскольку использует подзапрос. Мы работаем над решением этой проблемы на странице https://github.com/rethinkdb/rethinkdb/issues/3753 .

Однако вы можете использовать replace для выполнения атомарного обновления:

r.table('results').get('62c70132-6516-4279-9803-8daf407ffa5c')
 .replace({
  id: '62c70132-6516-4279-9803-8daf407ffa5c',
  counter: r.row('counter').add(1).default(0)
})

replace фактически выполнит вставку, если документ не существует.

person Daniel Mewes    schedule 26.07.2015
comment
Спасибо за помощь Даниил! - person let4be; 27.07.2015

В RethinkDB все обновления с одним запросом являются атомарными. Если Rethink не считает, что конкретная операция обновления/замены будет атомарной, она выдаст ошибку и потребует от вас добавить в запрос флаг неатомарности. Так что, как правило, вам не нужно слишком беспокоиться об этом. Однако это только для запросов update и replace. Это невозможно сделать атомарно с помощью insert.

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

Однако в одном запросе вы можете сделать следующее, что фактически является upsert, таким же образом, как вы использовали insert, но с использованием replace:

r.table('FruitStore')
.get(1)
.replace({
  id : 1, 
  fruits : r.row('fruits').default([]).append('orange') 
})

... который был бы атомным. Точно так же, чтобы использовать операцию добавления:

r.table('FruitStore')
.get(1)
.replace({
  id : 1, 
  count : r.row('count').default(0).add(1) 
})
person Chris Foster    schedule 26.07.2015
comment
Большое спасибо, Крис, за быстрый ответ и объяснение! - person let4be; 26.07.2015
comment
Единственное, что меня здесь беспокоит, это rethinkdb.com/docs/architecture Как работает атомарная модель? Они заявляют следующее: операции, детерминированность которых не может быть доказана, не могут обновлять документ атомарным образом. В настоящее время значения, полученные путем выполнения кода JavaScript, случайные значения и значения, полученные в результате подзапроса (например, увеличение значения атрибута на значение атрибута в другом документе), не могут выполняться атомарно. Здесь он использует подзапрос - person let4be; 26.07.2015
comment
@sfireman Вы правы, документация действительно противоречит этому. Этот подзапрос использует тот же документ, что и обновляемый, поэтому теоретически он должен быть атомарным. Однако вы также можете обновить этот документ на основе результата атрибута другого документа, не задумываясь о том, чтобы вызвать ошибку, в отличие от документов. У меня возникли проблемы с поиском окончательного ответа для вас прямо сейчас ... - person Chris Foster; 26.07.2015
comment
Только что попробовал команду с get(2) внутри, и действительно rethinkdb не выдает ошибку, но противоречит документам... - person let4be; 26.07.2015
comment
@sfireman Основываясь на некоторых экспериментах, совсем не ясно, действительно ли это атомарно. Я создал ошибку Github в Rethinkdb, чтобы решить эту проблему. Когда я получу ответ от них, я обновлю проблему соответствующим образом :) - person Chris Foster; 26.07.2015
comment
Я вижу здесь 2 операции: получить и вставить. А есть и не атомные. Я ошибся? - person Suvitruf - Andrei Apanasik; 26.07.2015
comment
К сожалению, это решение не является атомарным из-за подзапроса. Документация в FAQ вводит в заблуждение в этом отношении. Я собираюсь открыть вопрос, чтобы мы могли его улучшить. Извините за путаницу! - person Daniel Mewes; 26.07.2015
comment
@sfireman Я обновил свой ответ, чтобы он был фактически атомарным, на основе ответа Дэниела на Github. Извините, что изначально ввела в заблуждение! - person Chris Foster; 26.07.2015
comment
Спасибо за помощь! Использование замены нежелательно, так как мне, скорее всего, придется придерживаться фиксированной структуры данных в документе, чтобы иметь возможность повторно использовать старые поля... но, по крайней мере, это будет атомарно :) - person let4be; 27.07.2015
comment
@sfireman, вы можете использовать merge внутри замены, чтобы эмулировать update. Например: table.get(...).replace(r.branch(r.row.eq(null), {id: ..., counter: 0}, r.row.merge({counter: r.row ('счетчик').добавить(1)}))) - person Daniel Mewes; 28.07.2015
comment
Дэниел, только что проверил, используя результаты ветвления в Не удалось доказать детерминированность функции. Может быть, вы хотите использовать флаг non_atomic?... Так что в настоящее время нет способа атомарно обновить динамические документы. - person let4be; 28.07.2015