Уникальные ограничения в couchdb - PullRequest
27 голосов
/ 09 октября 2009

Существуют ли какие-либо методы / предложения для обеспечения соблюдения уникальных ограничений? Да, мы можем создать ключ, который уникален, но мы не можем изменить ключ и ключи, а также этот подход не подходит для сложной проверки (отдельный уникальный вход в систему, отдельный уникальный адрес электронной почты и т. Д.) *

Например, Учетная запись должна иметь уникальный логин и адрес электронной почты. Получение ключа из этого поля приведет к несоответствие:

key1: "Account-john@example.net-john", { email: "john@example.net", login: "john"}
key2: "Account-mary@example.net-mary", { email: "mary@example.net", login: "mary"}

Хорошо выглядит, но:

key1: "Account-john@example.net-mary", { email: "john@example.net", login: "mary"}
key2: "Account-mary@example.net-mary", { email: "mary@example.net", login: "mary"}

Упс, теперь у нас есть 2 аккаунта с логином: "mary"

Ответы [ 3 ]

24 голосов
/ 16 марта 2014

Основной ответ

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

  1. Создать представление . В функции map используйте поле, которое вы хотите применить уникальным, как key . Ценность может быть ничем. Используйте функцию уменьшения , чтобы получить счетчик для каждого из ваших ключей. Лучший способ (для производительности) - использовать встроенную _count функцию уменьшения.

  2. Сразу после того, как вы PUT / POST новый документ в базе данных, захватите возвращенные id и rev и GET / yourdb / _design / yourapp / _view / ViewName? группа = истина и ключ = " стоимость из-вашего-уникального поля-с шагом-1 ".

  3. Если результат последнего GET даст вам значение счетчика, отличное от 1 , то вы просто вставили дубликат. Немедленно УДАЛИТЬ /yourdb/id-from-step-2?rev=rev-from-step-2.

  4. Relax .


Грубый пример

Допустим, вы храните учетные записи пользователей и хотите, чтобы адрес электронной почты был уникальным, но вы не хотите делать его идентификатором документа (по какой-либо причине). Создайте представление, чтобы быстро проверить уникальность адреса электронной почты, как описано выше. Давайте назовем это emails . Он будет иметь функцию карты , возможно, похожую на эту ...

function(doc) {  
  if(doc.type === 'account') {
    emit(doc.email, 1);
  }
}

И просто _count как функция уменьшения . Если вы излучаете 1 , как указано выше, _sum также будет работать. Наличие и проверка поля type на вашем документе, как показано выше, - это просто соглашение, но иногда я нахожу его полезным. Если все, что вы храните, это учетные записи пользователей, вам, вероятно, это не нужно.

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

POST /mydb/
{
  "name": "Joe",
  "email": "joe@example.org"
}

И CouchDB ответит что-то вроде ...

{
  ok: true,
  id: 7c5c249481f311e3ad9ae13f952f2d55,
  rev: 1-442a0ec9af691a6b1690379996ccaba6
}

Проверьте, есть ли у нас более одного joe@example.org в базе данных ...

GET /mydb/_design/myapp/_view/emails/?group=true&key="joe@example.org"

И CouchDB ответит чем-то вроде ...

{
  rows: [
    {
      key: "joe@example.org",
      value: 1
    }
  ]
}

Если value отличается от 1 в этом ответе, вы, вероятно, просто вставили дубликат электронной почты. Поэтому удалите документ и верните сообщение об ошибке в виде строки Адрес электронной почты должен быть уникальным , аналогичным типичному ответу базы данных SQL или как вам угодно.

Удаление будет выглядеть примерно так ...

DELETE /mydb/7c5c249481f311e3ad9ae13f952f2d55?rev=1-442a0ec9af691a6b1690379996ccaba6

Краткое обсуждение (если вам интересно)

Если вы пришли из старого доброго * фона SQL, это будет казаться неправильным и странным. Перед тем, как скинуть, рассмотрите эти моменты. Наличие ограничения на поле, такого как уникальность или что-либо еще, подразумевает схему . CouchDB без схемы . Исходя из * SQL означает, что вам придется изменить способ мышления. ACID и Locks - не единственный способ. CouchDB обладает большой гибкостью и мощью. То, как вы используете это, чтобы получить то, что вы хотите, будет зависеть от деталей вашего варианта использования и от того, насколько хорошо вы сможете выйти за пределы традиционного мышления реляционных баз данных.

Причина, по которой я иногда реализую уникальность таким образом, заключается в том, что она очень мягкая для меня. Если вы думаете о том, как CouchDB работает с обработкой конфликтов обновления и т. Д., Этот подход следует той же последовательности. Мы храним документ, который нам дали, затем проверяем, является ли наше поле уникальным. Если нет, то возьмите тот документ, который мы только что вставили, с помощью удобного дескриптора, который у нас есть, и сделайте что-нибудь, чтобы устранить потребность в уникальности, например, удалив его.

В приведенном выше примере может возникнуть искушение проверить уникальность адреса электронной почты до you POST doc. Быть осторожен!! Если происходит несколько событий, возможно, что другой документ с тем же адресом электронной почты может быть вставлен в базу данных в момент после , когда вы проверяете, существует ли электронная почта, но до вы сделай свой POST! Это оставит вас с дубликатом электронной почты.

Нет ничего плохого в том, что вы также проверяете адрес электронной почты перед вами POST. Например, это хорошая идея, если ваш пользователь заполняет форму и вы можете отправить значение поля электронной почты в базу данных. Вы можете заранее предупредить пользователя о том, что адрес электронной почты существует, или запретить отправку и т. Д. Однако во всех случаях вам следует также всегда проверять уникальность после вас POST док. Затем реагируйте по мере необходимости. С точки зрения пользовательского интерфейса, вышеприведенные шаги не будут отличаться от результата, полученного от традиционной * базы данных SQL, использующей ограничение уникальности.

Что-нибудь может пойти не так? Да. Учти это. Предположим, что joe@example.org еще не существует в базе данных. Два документа приходят почти одновременно и сохраняются в базе данных. Документ A равен POSTed, затем Документ B равен POSTed, но прежде чем мы сможем проверить уникальность Документ A POST. Итак, теперь, когда мы выполняем проверку уникальности для Doc A POST, мы видим, что joe@example.org находится в базе данных дважды. Итак, мы удалим его и сообщим о проблеме. Но, скажем, прежде чем мы сможем удалить Doc A , также выполняется проверка уникальности для Doc B , получая то же значение счетчика для joe@example.org. Теперь оба POSTs будут отклонены, хотя joe@example.org изначально не было в базе данных! Другими словами, если два документа с совпадающим значением входят в ваше приложение практически одновременно, возможно может видеть друг друга POSTs и ошибочно заключить, что значение, которое они несут, уже находится в база данных! Мы не можем реально предотвратить это, потому что в CouchDB нет традиционного стиля RDB блокировка . Но в обмен на эту небольшую цену мы получаем реплику мастер-мастер и массу других интересных вещей. Я возьму это! И если это огромная проблема для вашей реализации, вы можете попытаться решить ее, внедрив некоторый механизм повторов и т. Д.

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

15 голосов
/ 19 декабря 2009

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

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

user-1234: { username: "kurt", email: "kurt@localhost" }
user-9876: { username: "petunia", email: "pie@crust.com" }

Уникальные указатели полей будут выглядеть примерно так:

user-username-kurt: { primary_doc: "user-1234" }
user-email-kurt@localhost: { primary_doc: "user-1234" }
user-username-petunia: { primary_doc: "user-9876" }
user-email-pie@crust.com: { primary_doc: "user-9876" }

Для создания или обновления пользователя необходимо выполнить следующие шаги:

  1. Подготовьте свой пользовательский документ, при необходимости сгенерируйте ключ для него
  2. Сохранить документ-указатель для каждого измененного уникального поля.
  3. Если сохранение какого-либо из них не удалось, остановите и исправьте ошибки
  4. Сохранить основной документ пользователя

Шаг 3 займет некоторое время. Например, вы не захотите пытаться использовать уникальные значения для полей, которые не изменились. Вы могли бы, но тогда вам нужно было бы добавить некоторую дополнительную логику для обработки случая, когда вы запрашиваете значение для пользователя, которому это значение уже принадлежит.

Шаг 3 был бы хорошим местом, чтобы позволить людям также принять старые заявленные ценности. Например, если один пользователь «освободил» имя пользователя kurt, я могу просто обновить этот конкретный документ, чтобы он указывал на мой новый ключ после проверки того, что он больше не используется. Альтернативой является очистка заявленных уникальных значений при их изменении. Я не уверен, что будет меньше работы, на самом деле. Оставить устаревшие значения в имеет для меня самый смысл.

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

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

9 голосов
/ 18 октября 2009

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

Если вы заботитесь только об одном сервере, возможно, вы могли бы перестроить ваши couchjs с поддержкой сети и выполнить запрос http в вашей функции validate_doc_update(), которая будет выполнять запрос к БД, чтобы проверить, уже ли адрес электронной почты уже существует. использовал и не удалось обновить, если так. Проверьте здесь для более подробной информации о механизме проверки. Я не рекомендую делать это, вместо этого я бы вставил всю уникальность в поле id (напрямую или через хэширование) и просто занимался перемещением документа, если пользователь изменил что-либо, что на это повлияло.

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