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 и родительско-дочерние документы.