1. Введение
В реальном процессе разработки программного проекта из-за потребностей бизнеса структура между нашими таблицами базы данных представляет собой связь «один-ко-многим». Если взять в качестве примера таблицу заказов и таблицу позиций заказа, то в базе данных mysql их отношения следующие. картина:
Если мы хотим узнать, какие продукты были приобретены в течение определенного периода времени,Его можно передать следующим образомjoin
Запрос путем объединения таблиц。
select t.*
from tb_order t
left join tb_order_item tt on t.order_id = tt.order_id
where tt.product_name like '%Название продукта%'
and t.createTime >= '2022-06-01 00:00:00'
and t.createTime <= '2022-06-16 00:00:00';
Затем Elasticsearch и большинство NoSQL база Подобно данным, это плоская структура хранения, которая не может быть похожа на реляционную базу данных. данныхтаким образом,может пройтиjoin
Как выполнить поиск по списку участников。
Однако ElasticsSearch (далее именуемый ES) все-таки дошел до версии 8.x, и уже существует несколько дополнительных способов эффективной поддержки сопоставления и поиска этой связи «один ко многим».
Существует три наиболее часто используемых практических решения:
Второй тип — это та часть, на которой мы хотим сосредоточиться сегодня. Без лишних слов, давайте объясним вам конкретные практические идеи в виде реальных случаев.
так называемый Вложенные объекты,Это настоящееjson
Объект имеет встроенныйjson
объект,Возьмем данные заказа в качестве примера,Содержит данные о нескольких позициях,Формат следующий:
{
"orderId":"1",
"orderNo":"123456",
"orderUserName":"Чжан Сан",
"orderItems":[
{
"orderItemId":"12234",
"orderId":"1",
"productName":"колбаса с ветчиной",
"brandName":"Шуанхуэй",
"sellPrice":"28"
},
{
"orderItemId":"12235",
"orderId":"1",
"productName":"желе",
"brandName":"Хуюань",
"sellPrice":"12"
}
]
}
Создайтеorder_index
Индекс,Мы храним вышеуказанные данные документа в ЭП.,Взгляните на структуру индексного документа.mapping
Как это выглядит,Содержание следующее:
{
"order_index":{
"mappings":{
"_doc":{
"properties":{
"orderId":{
"type":"keyword"
},
"orderNo":{
"type":"keyword"
},
"orderUserName":{
"type":"keyword"
},
"orderItems":{
"properties":{
"orderItemId":{
"type":"keyword"
},
"orderId":{
"type":"keyword"
},
"productName":{
"type":"keyword"
},
"brandName":{
"type":"keyword"
},
"sellPrice":{
"type":"keyword"
}
}
}
}
}
}
}
}
Вы можете очень четко видеть, куда приезжать.,Внутри поля сопоставления индекса заказа,содержитorderItems
поля,это тип объекта,Он имеет свои собственные внутренние свойства поля. На самом деле это отношения включения,Указывает, что заказ может содержать информацию о нескольких позициях.
Мы можем запросить набор результатов индекса, чтобы увидеть результаты.,использоватьpostman
Выполнить запрос для всех данных документа по индексу.!
POST order_index/_search
{
"query": {
"match_all": {}
}
}
Возвращаемые результаты следующие (некоторые неважные данные были удалены для облегчения наблюдения):
[
{
"_index":"order_index",
"_type":"_doc",
"_id":"1",
"_score":1,
"_source":{
"orderId":"1",
"orderNo":"123456",
"orderUserName":"Чжан Сан",
"orderItems":[
{
"orderItemId":"12234",
"orderId":"1",
"productName":"колбаса с ветчиной",
"brandName":"Шуанхуэй",
"sellPrice":"28"
},
{
"orderItemId":"12235",
"orderId":"1",
"productName":"желе",
"brandName":"Хуюань",
"sellPrice":"12"
}
]
}
}
]
Вы можете очень четко видеть, куда приезжать.,Возвращенные результаты также прекрасно представлены.,orderItems
На самом деле этоlist
,Содержит два объекта,Вся информация в одном документе.
Давайте попробуем еще раз ES по торговому наименованию и названию бренда,Условная фильтрация двух объединений,Чтобы запросить информацию о заказе клиента,писатьDSL
оператор запроса,Найдите название продукта дляВетчина колбаса
И бренд естьХуэйюань
заказ,Содержание следующее:
POST order_index/_search
{
"query":{
"bool":{
"must":[
{
"match":{
"orderItems.productName":"Колбаса с ветчиной"
}
},
{
"match":{
"orderItems.brandName":"Хуюань"
}
}
]
}
}
}
Возвращаемые результаты следующие (некоторые неважные данные были удалены для облегчения наблюдения):
[
{
"_index":"order_index",
"_type":"_doc",
"_id":"1",
"_score":1,
"_source":{
"orderId":"1",
"orderNo":"123456",
"orderUserName":"Чжан Сан",
"orderItems":[
{
"orderItemId":"12234",
"orderId":"1",
"productName":"колбаса с ветчиной",
"brandName":"Шуанхуэй",
"sellPrice":"28"
},
{
"orderItemId":"12235",
"orderId":"1",
"productName":"желе",
"brandName":"Хуюань",
"sellPrice":"12"
}
]
}
}
]
Анализируйте ожидаемые результаты,Ни один клиент не приобрел торговую маркуХуэйюань
И название продуктаВетчина колбаса
заказ,Теоретически,Не должно быть никаких данных!
Однако в результате данные этого заказа возвращаются! Почему это?
оказаться ES дляjson
объектмножествоиз Сделал выравниваниеиметь дело с,Например, приведенный выше пример в ES Структура хранения такая:
{
"orderId": [ 1 ],
"orderItems.productName":["колбаса с ветчиной","желе"],
"orderItems.brandName": [«Шуанхуэй», «Хуюань»],
...
}
Это очевидно,Такая структура теряет связь между названием продукта и торговой маркой.,При запросе,Происходит сбой,Если бизнес требует точного поиска,Тогда это решение не соответствует требованиям。
Если ваш бизнес-сценарий не чувствителен к этой проблеме, вы можете выбрать этот метод, поскольку он достаточно прост и более эффективен, чем два решения, представленные ниже.
Это очевидновышеобъектмножествоиз Нет планаиметь дело схороший интерьеробъектизпограничные проблемы,JSON
множествообъектодеяло ES Принудительное хранение в плоском списке пар ключ-значение. Чтобы решить эту проблему, ES Было запущено так называемое решение для вложенных документов. Официальное описание этого решения выглядит следующим образом:
The nested type is a specialised version of the object datatype that allows arrays of objects to be indexed in a way that they can be queried independently of each other.
Можно смотретьприезжать Вложенные документыиз План на самом деле для обычного внутреннегообъектпланиз Пополнить。我们将вышезаказиндексв структуреизorderItems
данныетип,измените это наnested
тип,Пересоздайте индекс.
{
"properties":{
"orderItems":{
"properties":{
....
},
"type":"nested"
}
....
}
}
orderItems
данныетип,Изменить на даnested
,Представляет внедренный документ,Остальные свойства остаются неизменными.
Давайте попробуем еще раз по названию продукта и торговой марки.Чтобы запросить информацию о заказе клиента,**Разница в том, что,При запросе,Необходимо указатьnested
ключевые слова и путиpath
**,Позиция запроса следующая:
POST order_index/_search
{
"query":{
"nested":{
"path":"orderItems",
"query":{
"bool":{
"must":[
{
"match":{
"orderItems.productName":"Колбаса с ветчиной"
}
},
{
"match":{
"orderItems.brandName":"Хуюань"
}
}
]
}
}
}
}
}
Результат запроса пуст[]
,Соответствует ожидаемым результатам!
Давайте снова изменим условия запроса,Название продукта запроса:Ветчина колбаса
и торговая маркаШуанхуэй
заказ。
POST order_index/_search
{
"query":{
"nested":{
"path":"orderItems",
"query":{
"bool":{
"must":[
{
"match":{
"orderItems.productName":"Колбаса с ветчиной"
}
},
{
"match":{
"orderItems.brandName":"Шуанхуэй"
}
}
]
}
}
}
}
}
Результаты запроса следующие:
[
{
"_index":"order_index",
"_type":"_doc",
"_id":"1",
"_score":1,
"_source":{
"orderId":"1",
"orderNo":"123456",
"orderUserName":"Чжан Сан",
"orderItems":[
{
"orderItemId":"12234",
"orderId":"1",
"productName":"колбаса с ветчиной",
"brandName":"Шуанхуэй",
"sellPrice":"28"
},
{
"orderItemId":"12235",
"orderId":"1",
"productName":"желе",
"brandName":"Хуюань",
"sellPrice":"12"
}
]
}
}
]
В соответствии с ожидаемыми результатами, несмотря на все это, кажется, что вложенные документы очень полезны. В предыдущей схеме нет проблемы пропуска границ объекта, и ее использование кажется несложным. То есть у него нет недостатков? Конечно, сначала проведем эксперимент.
Сначала посмотрите на количество документов, индексируемых в настоящее время.
GET _cat/indices?v
Результаты запроса.
green open order_index FJsEIFf_QZW4Q4SlZBsqJg 1 1 3 0 17.7kb 8.8kb
Возможно, вы заметили, что я не использовал следующую информацию для просмотра количества документов здесь.
GET order_index/_count
Вместо этого индексная информация просматривается напрямую. Разница между ними заключается в следующем:
Вы можете очень четко видеть, куда приезжать.,order_index
индекс,существовать ES Данные документа ACCCIM: 3. Почему бы и нет? 1 Шерстяная ткань?
Это потому, чтоnested
вложенный документсуществовать ES Внутри он фактически независим lucene Документ, только когда мы запрашиваем, ES Внутри мы сделали что-то вроде базы данныхизjoin
иметь дело с。Наконец-то это выглядит как независимыйиз Документация та же самая。
Если в заказе 1000 позиций заказа, то количество документов, существующих в ЭП, равно 1001, что увеличивается вдвое по мере увеличения количества заказов.
Вполне возможно, что в тех же условиях производительность этого решения определенно будет не такой хорошей, как у обычных внутренних объектов. В реальных бизнес-приложениях необходимо решить, следует ли выбирать это решение, исходя из реальной ситуации.
Одно можно сказать наверняка: он может удовлетворить требования по точному поиску внутренних данных объекта!
Давайте посмотрим на пример выше,Что делать, если мне нужно обновить документизorderNo
свойствоизценить,ES обновления документации,Принцип работы:Удалить данные прохождения,Вставьте еще один,Но id индекса такие же。
Это означает,Несмотря на тоorderItems
Поле,мне не нужны обновления,Он также будет переиндексирован вместе с основным документом.
Кроме того, если между определенной таблицей и определенной таблицей существует связь «многие ко многим»,Например, вложенный документ может принадлежать нескольким основным документам.,использоватьnested
Невозможно достичь,В настоящее время вы можете рассмотреть возможность использованияродительско-дочерний документСтруктура приходитиметь дело с。
Ниже мы рассмотрим экзаменационные вопросы в качестве примера.,Вопрос может иметь несколько ответов, и ответ может соответствовать нескольким вопросам.。
Сначала мы определяем структуру индексного документа следующим образом:
PUT exam_index
{
"mappings":{
"_doc":{
"properties":{
"my_id":{
"type":"keyword"
},
"parent_join_child":{
"type":"join",
"relations":{
"question":"answer"
}
}
}
}
}
}
my_id
Это настроенополя,parent_join_child
это для насизродительско-дочерний документсвязьизимя,Это можно настроить,join
означает, что этородительско-дочерний документсвязь,relations
выраженный внутриquestion
отец,answer
Да。
Сначала мы вставляем два родительских документа.
PUT exam_index/_doc/1
{
"my_id":"1",
"text":"Это вопрос 1",
"parent_join_child":{
"name":"question"
}
}
PUT exam_index/_doc/2
{
"my_id":"2",
"text":"Это вопрос 2",
"parent_join_child":{
"name":"question"
}
}
в"name":"question"
Указывает на вставкуизотецдокумент。
Затем вставьте два вложенных документа
PUT exam_index/_doc/3?routing=1
{
"my_id":"3",
"text":"Это ответ 1, соответствующий вопросу 1",
"parent_join_child":{
"name":"answer",
"parent":"1"
}
}
PUT exam_index/_doc/4?routing=1
{
"my_id":"4",
"text":"Это ответ 2, соответствующий вопросу 1",
"parent_join_child":{
"name":"answer",
"parent":"1"
}
}
Вложенные документы могут объяснить больше.,Сначала из документацииid
我们可以判断вложенный документ都是独立издокумент(иnested
нет то же самое)。Во-вторыхrouting
Ключевые словамаршрут указанизid
отецдокумент1
, этотid
и нижеизparent
Ключевые словапереписыватьсяизid
является последовательнымиз。
Что необходимо подчеркнуть, так это,При индексировании поддокументов,routing
Это необходимоиз,Потому что вам необходимо убедиться, что дочерний документ и родительский документ находятся в одном сегменте.
"name":"answer"
Ключевое слово указывает, что это вложенный документ.。
сейчассуществоватьexam_index
индекс Есть четыре независимыхиздокумент,Давайте посмотримродительско-дочерний Каково положение документа при поиске?
Начнем с безусловного запроса на возврат всех данных документа.
POST exam_index/_search
{
"query":{
"match_all":{
}
},
"sort":["my_id"]
}
Возвращенные результаты следующие:
[
{
"_index":"crm_exam_index",
"_type":"_doc",
"_id":"1",
"_score":null,
"_source":{
"my_id":"1",
"text":"Это вопрос 1",
"parent_join_child":{
"name":"question"
}
}
},
{
"_index":"crm_exam_index",
"_type":"_doc",
"_id":"2",
"_score":null,
"_source":{
"my_id":"2",
"text":"Это вопрос 2",
"parent_join_child":{
"name":"question"
}
}
},
{
"_index":"crm_exam_index",
"_type":"_doc",
"_id":"3",
"_score":null,
"_routing":"1",
"_source":{
"my_id":"3",
"text":"Это ответ 1, соответствующий вопросу 1",
"parent_join_child":{
"name":"answer",
"parent":"1"
}
}
},
{
"_index":"crm_exam_index",
"_type":"_doc",
"_id":"4",
"_score":null,
"_routing":"1",
"_source":{
"my_id":"4",
"text":"Это ответ 2, соответствующий вопросу 1",
"parent_join_child":{
"name":"answer",
"parent":"1"
}
}
}
]
Можно смотретьприезжатьвозвращатьсяиз Результат был приведён.parent_join_child
Ключевые слова,Указывает, является ли это родительским или дочерним документом.
Если мы хотим запросить родительский документ через информацию вложенного документа, мы можем сделать это следующим образом:
POST exam_index/_search
{
"query":{
"has_child":{
"type":"answer",
"query":{
"match":{
"текст":"ответ"
}
}
}
}
}
Результаты возврата:
[
{
"_index":"exam_index",
"_type":"_doc",
"_id":"1",
"_score":1,
"_source":{
"my_id":"1",
"text":"Это вопрос 1",
"parent_join_child":{
"name":"question"
}
}
}
]
Если мы хотим запросить вложенные документы через информацию родительского документа, мы можем сделать это следующими способами:
POST exam_index/_search
{
"query":{
"has_parent":{
"parent_type":"question",
"query":{
"match":{
"текст":"вопрос"
}
}
}
}
}
Результаты возврата:
[
{
"_index":"crm_exam_index",
"_type":"_doc",
"_id":"3",
"_score":1,
"_routing":"1",
"_source":{
"my_id":"3",
"text":"Это ответ 1, соответствующий вопросу 1",
"parent_join_child":{
"name":"answer",
"parent":"1"
}
}
},
{
"_index":"crm_exam_index",
"_type":"_doc",
"_id":"4",
"_score":1,
"_routing":"1",
"_source":{
"my_id":"4",
"text":"Это ответ 2, соответствующий вопросу 1",
"parent_join_child":{
"name":"answer",
"parent":"1"
}
}
}
]
Если мы хотим запросить дочерние документы по родительскому идентификатору, мы можем сделать это следующим образом:
POST exam_index/_search
{
"query":{
"parent_id":{
"type":"answer",
"id":"1"
}
}
}
Возвращаемый результат такой же, как указано выше,разницасуществовать Вparent_id
поиск по умолчаниюиспользовать Оценка релевантности,иhas_parent
По умолчанию нетиспользоватьрасчет。
Есть некоторые моменты, которые требуют особого внимания при использовании шаблона документа «родитель-потомок»:
join field
routing
join field
ДобавленосвязьПодводя итог, можно сказать, что вложенные объекты повышают производительность запросов за счет избыточных данных и подходят для сценариев, в которых требуется больше чтения и меньше записи. ES даjson
множествообъектвыполнить выравниваниеиметь дело с,В результате поиск встроенных объектов будет не очень точным.,Если требования к поиску бизнес-сценария не высоки,Это решение рекомендуется.
Если бизнес-сценарий требует точного поиска, вы можете использовать решение с вложенными документами. При каждом обновлении данные документа будут удаляться, а затем вставляться снова, а производительность записи и запросов будет ниже, чем у вложенных объектов.
Если между таблицами существует сценарий «многие ко многим», можно использовать решение родительско-дочерних документов. Каждое обновление будет обновлять данные только одного документа, и запись будет быстрее, чем вложенные документы. Скорость запроса будет выше, чем у того же документа. Запросы вложенных документов выполняются в 5–10 раз медленнее!
Выбор конкретного решения также должен быть сделан разумно, исходя из текущего бизнес-сценария.
1. Rhino Breeder - вложенные документы серии ES и родительско-дочерние документы.