Когда я могу сохранить данные JSON или XML в таблице SQL
при использовании SQL
или MySQL
(или любая реляционная БД, если на то пошло) - я понимаю, что сохранение данных в регулярных столбцах лучше для индексирования и других целей...
вещь загружается и сохраняет JSON
данные иногда намного проще. и делает развитие более легким.
существуют ли "золотые правила" для сохранения raw JSON
данные в БД?
это абсолютно неправильная практика так?
резюме
были даны очень хорошие ответы, но, без сомнения, наиболее хорошо организован ответ, данный @Shnugo, который заслуживает награды.
также хотел бы указать на ответы, данные @Gordon Linoff и @Amresh Pandey для объяснения других особых случаев использования.
слава Богу, и хорошая работа всем!
7 ответов
основные вопросы
- что вы собираетесь делать с этими данными? и
- как вы фильтруете / сортируете / соединяете / манипулируете этими данными?
JSON (например, XML) отлично подходит для обмена данными, небольшого хранилища и общих структур, но не может участвовать в типичных действиях, выполняемых в вашей СУБД. В большинстве случаев будет лучше перенести данные JSON в нормальные столы и воссоздать JSON, когда вам это нужно.
XML / JSON и 1.NF
первое правило нормализации предписывает никогда не хранить более одного бита информации в одном столбце. Вы видите столбец "PersonName" со значением типа "Микки Маус"? Ты указываешь на это и кричишь:--23-->изменить это немедленно!
как насчет XML или JSON? Эти типы ломают 1.НФ? Ну, и да, и нет...
совершенно нормально хранить полное структура как один бит информации если это один бит информации на самом деле. Вы получаете ответ SOAP и хотите сохранить его, потому что вам может понадобиться это для дальнейшего использования (но вы будете не использовать эти данные для собственных процессов)? Просто храните его как!
теперь представьте себе сложная структура (XML или JSON), представляющая человека (С его адресом, дальнейшие подробности...). Теперь вы положите это в одна колонка как PersonInCharge
. Это неправильно? Разве это не должно жить в правильно разработанных связанных таблицах со ссылкой на внешний ключ вместо XML / JSON? Особенно, если один и тот же человек может встречаться во многих разных строках, определенно неправильно использовать подход XML/JSON.
но теперь представьте себе необходимость хранения исторических данных. Вы хотите остаются данные человека на данный момент времени. Через несколько дней человек сообщает вам новый адрес? Нет проблема! Старый адрес живет в XML / JSON, если он вам когда-либо понадобится...
вывод: если вы храните данные только для того, чтобы сохранить его, все в порядке. Если эти данные являются уникальный порция, все в порядке...
Но если вам нужно внутренние части регулярно или если это будет означать избыточное дублирующее хранилище, это не нормально...
физическая хранения
следующий для SQL Server и может отличаться от других СУРБД.
XML хранится не как текст, который вы видите, а как дерево иерархии. Это удивительно хорошо работает! Эта структура не анализируется на уровне строки!
JSON в SQL Server (2016+) живет в строке и должен быть проанализирован. Нет реального родной типа JSON (как есть родной типа XML). Это может произойти позже, но сейчас я бы предположил, что JSON не будет таким же эффективным, как XML на SQL Server (см. раздел обновление 2). Нужно прочитать значение из JSON потребуется чертовски много скрытых вызовов метода string...
что это значит для вас?
код привлекательный DB художник :-D не знает, что хранение JSON как, противоречит общим принципам РСУБД. Он знает,
- что JSON, скорее всего, нарушает 1.NF
- что JSON может измениться во времени (тот же столбец, разное содержимое).
- что JSON не является легко читать, и очень трудно фильтровать/искать/присоединяться или Сортировать по нему.
- что такие операции перенесут довольно дополнительную нагрузку на бедный маленький сервер БД
есть некоторые обходные пути (в зависимости от используемых СУБД), но большинство из них не работают так, как вам хотелось бы...
ответ на ваш вопрос короче
да
- если вы не хотите использовать данные, которые хранится внутри ваш JSON-формате для дорогостоящих операций (фильтр/объединение/сортировка).
Вы можете хранить это так же, как и любой другой только содержание. Мы храним много изображений как капли, но мы не будем пытаться фильтровать все изображения с помощью цветка... - если вы совсем не беспокоитесь о том, что внутри (просто сохраните его и прочитайте его как один бит информации)
- если структуры являются переменными, что затруднит создание физической затем таблицы для работы с данными JSON.
- если структура глубоко вложена, то хранилище в физических таблицах намного превышает
нет
- если вы хотите использовать внутренние данные, как вы бы использовали данные реляционной таблицы (фильтр, индексов, соединений...)
- если вы будете хранить дубликаты (создать избыточность)
- в общем: если вы столкнулись с проблемами производительности (наверняка вы столкнетесь с ними во многих типичные сценарии!)
вы можете начать с JSON в строковом столбце или как BLOB и изменить это на физические таблицы, когда вам это нужно. Мой магический хрустальный шар говорит мне, что это может быть завтра : - D
обновление
найдите некоторые идеи о производительности и дисковом пространстве здесь:https://stackoverflow.com/a/47408528/5089204
обновление 2: подробнее о производительности...
следующий адреса поддержки JSON и XML в SQL-Server 2016
пользователь @mike123 указал на статья в официальном блоге microsoft что, кажется, доказывает в эксперименте, что запрос JSON является 10 x быстрее затем запрос XML в SQL-Server.
некоторые мысли об этом:
некоторые перекрестные проверки с "эксперимент":
- на "эксперимент" измеряет много, но не работу с XML и JSON с. Выполнение одного и того же действия agaist одной и той же (неизмененной) строки повторно не является реалистичным сценарием
- проверенные примеры далеко до простого для общего заявления!
- значение read всегда одно и то же и даже не используется. Оптимизатор увидит это...
- ни единого слова о могучем
XQuery
поддержка! Найти продукт с заданным ID в массив? JSON должен прочитать всю партию и используйте фильтр после использованияWHERE
, аXML
позволит внутренняяXQuery predicate
. Не говоря уже оFLWOR
... - "эксперименты" код как в моей системе появляется: JSON кажется 3x быстрее (но не 10x).
-
добавлять
/text()
доXPath
уменьшает это до менее чем 2x. В соответствующей статье пользователь "Mister Magoo" указал на это уже, но click-bait название еще не менявшийся... - С таким легким JSON, как указано в "эксперименте", самый быстрый чистый подход T-SQL был комбинацией
SUBSTRING
иCHARINDEX
: - D
следующий код покажет более реалистичный эксперимент
- использование JSON и идентичного XML с более чем одним
Product
(массив JSON против родственных узлов) - JSON и XML немного меняются (10000 запущенных номеров) и вставляются в таблицы.
- существует начальный вызов agaist обе таблицы, чтобы избежать первый звонок-уклон
- все 10000 записей считываются и полученные значения вставляются в другую таблицу.
- используя
GO 10
будет проходить через этот блок десять раз, чтобы избежать первый звонок-уклон
конечный результат ясно показывает, что JSON медленнее, чем XML (не так много, около 1.5 x на все еще очень простой образец.)
в заключительном заявлении:
- С чрезмерно упрощенным примером при неоправданных обстоятельствах JSON может быть быстрее, чем XML
- работа с JSON чистое действие строки, в то время как XML анализируется и преобразуется. Это довольно дорого в первом действии, но ускорит все, как только это будет сделано.
- JSON может быть лучше в один раз действие (избегает накладных расходов на создание внутреннего иерархическое представление XML)
- С еще очень простым, но более реалистичным примером XML будет быстрее в простом чтении
- всякий раз, когда есть необходимость прочитать определенный элемент из массива, отфильтровать все записи, где данный ProductID включен в массив, или перемещаться вверх и вниз по пути, JSON не может задерживаться. Он должен быть полностью разобран из строки - каждый раз, когда вам нужно схватиться за нее...
тест код
USE master;
GO
--create a clean database
CREATE DATABASE TestJsonXml;
GO
USE TestJsonXml;
GO
--create tables
CREATE TABLE TestTbl1(ID INT IDENTITY,SomeXml XML);
CREATE TABLE TestTbl2(ID INT IDENTITY,SomeJson NVARCHAR(MAX));
CREATE TABLE Target1(SomeString NVARCHAR(MAX));
CREATE TABLE Target2(SomeString NVARCHAR(MAX));
CREATE TABLE Times(Test VARCHAR(10),Diff INT)
GO
--insert 10000 XMLs into TestTbl1
WITH Tally AS(SELECT TOP 10000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL))*2 AS Nmbr FROM master..spt_values AS v1 CROSS APPLY master..spt_values AS v2)
INSERT INTO TestTbl1(SomeXml)
SELECT
N'<Root>
<Products>
<ProductDescription>
<Features>
<Maintenance>' + CAST(Nmbr AS NVARCHAR(10)) + ' year parts and labor extended maintenance is available</Maintenance>
<Warranty>1 year parts and labor</Warranty>
</Features>
<ProductID>' + CAST(Nmbr AS NVARCHAR(10)) + '</ProductID>
<ProductName>Road Bike</ProductName>
</ProductDescription>
<ProductDescription>
<Features>
<Maintenance>' + CAST(Nmbr + 1 AS NVARCHAR(10)) + ' blah</Maintenance>
<Warranty>1 year parts and labor</Warranty>
</Features>
<ProductID>' + CAST(Nmbr + 1 AS NVARCHAR(10)) + '</ProductID>
<ProductName>Cross Bike</ProductName>
</ProductDescription>
</Products>
</Root>'
FROM Tally;
--insert 10000 JSONs into TestTbl2
WITH Tally AS(SELECT TOP 10000 ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Nmbr FROM master..spt_values AS v1 CROSS APPLY master..spt_values AS v2)
INSERT INTO TestTbl2(SomeJson)
SELECT
N'{
"Root": {
"Products": {
"ProductDescription": [
{
"Features": {
"Maintenance": "' + CAST(Nmbr AS NVARCHAR(10)) + ' year parts and labor extended maintenance is available",
"Warranty": "1 year parts and labor"
},
"ProductID": "' + CAST(Nmbr AS NVARCHAR(10)) + '",
"ProductName": "Road Bike"
},
{
"Features": {
"Maintenance": "' + CAST(Nmbr + 1 AS NVARCHAR(10)) + ' blah",
"Warranty": "1 year parts and labor"
},
"ProductID": "' + CAST(Nmbr + 1 AS NVARCHAR(10)) + '",
"ProductName": "Cross Bike"
}
]
}
}
}'
FROM Tally;
GO
--Do some initial action to avoid first-call-bias
INSERT INTO Target1(SomeString)
SELECT SomeXml.value('(/Root/Products/ProductDescription/Features/Maintenance/text())[1]', 'nvarchar(4000)')
FROM TestTbl1;
INSERT INTO Target2(SomeString)
SELECT JSON_VALUE(SomeJson, N'$.Root.Products.ProductDescription[0].Features.Maintenance')
FROM TestTbl2;
GO
--Start the test
DECLARE @StartDt DATETIME2(7), @EndXml DATETIME2(7), @EndJson DATETIME2(7);
--Read all ProductNames of the second product and insert them to Target1
SET @StartDt = SYSDATETIME();
INSERT INTO Target1(SomeString)
SELECT SomeXml.value('(/Root/Products/ProductDescription/ProductName/text())[2]', 'nvarchar(4000)')
FROM TestTbl1
ORDER BY NEWID();
--remember the time spent
INSERT INTO Times(Test,Diff)
SELECT 'xml',DATEDIFF(millisecond,@StartDt,SYSDATETIME());
--Same with JSON into Target2
SET @StartDt = SYSDATETIME();
INSERT INTO Target2(SomeString)
SELECT JSON_VALUE(SomeJson, N'$.Root.Products.ProductDescription[1].ProductName')
FROM TestTbl2
ORDER BY NEWID();
--remember the time spent
INSERT INTO Times(Test,Diff)
SELECT 'json',DATEDIFF(millisecond,@StartDt,SYSDATETIME());
GO 10 --do the block above 10 times
--Show the result
SELECT Test,SUM(Diff) AS SumTime, COUNT(Diff) AS CountTime
FROM Times
GROUP BY Test;
GO
--clean up
USE master;
GO
DROP DATABASE TestJsonXml;
GO
результат (SQL Server 2016 Express на Acer Aspire v17 Nitro Intel i7, 8GB Ram)
Test SumTime
------------------
json 2706
xml 1604
Это слишком долго для комментария.
Если бы это было "абсолютно неправильно", то большинство баз данных не поддерживает. Хорошо, большинство баз данных поддерживают запятые в FROM
предложение, и я считаю, что это "абсолютно неправильно". Но поддержка JSON-это новая разработка, а не обратная совместимость "функции".
один очевидный случай, когда структура JSON является просто BLOB, который передается обратно в приложение. Тогда нет дебатов - другие, то накладные расходы на хранение JSON, что излишне многословно для структурированных данных с общими полями в каждой записи.
другим случаем является случай "разреженных" столбцов. У вас есть строки со многими возможными столбцами, но они варьируются от строки к строке.
другой случай, когда вы хотите сохранить "вложенные" записи в записи. JSON является мощным.
Если JSON имеет общие поля для записей, которые вы хотите запросить, то вам обычно лучше поместить их в соответствующие столбцы базы данных. Однако данные сложны, и есть место для таких форматов, как JSON.
я помашу своей волшебной палочкой. Пуф! Золотые правила по использованию JSON:
если MySQL не нужно искать внутри JSON, и приложение просто нуждается в коллекции вещей, то JSON в порядке, возможно, даже лучше.
если вы будете искать по данным, которые находятся внутри и у вас есть MariaDB 10.0.1 или MySQL 5.7 (с типом данных и функциями JSON), затем json может быть практичным. "Динамические" столбцы MariaDB 5.3-это вариант этого.
если вы делаете" сущность-атрибут-значение", то JSON не хорош, но это наименьшее из нескольких зол. http://mysql.rjweb.org/doc.php/eav
для поиска по индексированному столбцу отсутствие значения, похороненного внутри JSON, является большим плюсом.
для поиска по диапазону в индексированном столбце или
FULLTEXT
поиск илиSPATIAL
, и JSON это невозможно.на
WHERE a=1 AND b=2
"составной" показательINDEX(a,b)
отлично; вероятно, не может приблизиться к JSON.JSON хорошо работает с" разреженными " данными; индексирование работает, но не так хорошо, с такими. (Я имею в виду значения, которые "отсутствуют" или NULL для многих строк.)
JSON может дать вам "массивы" и "деревья", не прибегая к дополнительным таблицам. Но копайтесь в таких массивах / деревьях только в app,не в SQL.
JSON-это миры лучше, чем XML. (Мое мнение)
если вы не хотите попасть в строку JSON, кроме как из приложения, то я рекомендую сжать (в клиенте) его хранение в
BLOB
. Думайте о нем, как .jpg - там есть вещи, но SQL это не волнует.
укажите ваше приложение; возможно, мы можем быть более конкретными.
новый SQL Server предоставляет функции для обработки текста JSON. Информация в формате JSON может храниться в виде текста в стандартных столбцах SQL Server, а SQL Server предоставляет функции, которые могут извлекать значения из этих объектов JSON.
DROP TABLE IF EXISTS Person
CREATE TABLE Person
( _id int identity constraint PK_JSON_ID primary key,
value nvarchar(max)
CONSTRAINT [Content should be formatted as JSON]
CHECK ( ISJSON(value)>0 )
)
эта простая структура похожа на стандартную коллекцию NoSQL, которую можно создать в базах данных NoSQL (например, Azure DocumentDB или MongoDB) , где у вас есть только ключ, представляющий ID и значение, представляющее формат JSON.
обратите внимание, что NVARCHAR-это не просто обычный текст. SQL Server имеет встроенный механизм сжатия текста, который может прозрачно сжимать данные, хранящиеся на диске. Сжатие зависит от языка и может достигать 50% в зависимости от ваших данных (см. сжатие UNICODE ).
ключевое различие между SQL server и другими обычными базами данных NoSQL заключается в том, что SQL Server позволяет использовать гибридную модель данных, в которой можно хранить несколько объектов JSON в одной "коллекции" и объединять они с регулярными реляционными столбцами.
в качестве примера представьте, что мы знаем, что каждый человек в вашей коллекции будет иметь имя и фамилию, и что вы можете хранить общую информацию о человеке как один объект JSON, а номера телефонов/адреса электронной почты как отдельные объекты. В SQL Server 2016 мы можем легко создать эту структуру без какого-либо дополнительного синтаксиса:
DROP TABLE IF EXISTS Person
CREATE TABLE Person (
PersonID int IDENTITY PRIMARY KEY,
FirstName nvarchar(100) NOT NULL,
LastName nvarchar(100) NOT NULL,
AdditionalInfo nvarchar(max) NULL,
PhoneNumbers nvarchar(max) NULL,
EmailAddresses nvarchar(max) NULL
CONSTRAINT [Email addresses must be formatted as JSON array]
CHECK ( ISJSON(EmailAddresses)>0 )
)
вместо одного объекта JSON вы можете организовать свои данные в этой "коллекции". Если у вас не хотите явно проверять структуру каждого столбца JSON, вам не нужно добавлять ограничение проверки JSON для каждого столбца (в этом примере я добавил ограничение проверки только для столбца EmailAddresses).
если вы сравните эту структуру со стандартной коллекцией NoSQL, вы можете заметить, что у вас будет более быстрый доступ к строго типизированным данным (FirstName и LastName). Таким образом, это решение является хорошим выбором для гибридных моделей, где можно определить некоторые повторяющиеся сведения во всех объектах и других переменных информация может храниться как JSON. Таким образом, вы можете сочетать гибкость и производительность.
если вы сравните эту структуру со схемой базы данных таблицы Person AdventureWorks, вы можете заметить, что мы удалили много связанных таблиц.
кроме простоты схемы, операции доступа к данным будет проще по сравнению со сложной структурой. Теперь вы можете читать одну таблицу вместо объединения нескольких таблиц. Когда вы нужно ввести нового человека с соответствующей информацией (адреса, телефоны) вы можете вставить одну запись в одной таблице, а не вставка одной записи в базе данных AdventureWorks человека, стола, принимая столбец идентификаторов, чтобы найти внешний ключ, который будет использоваться для хранения телефонов, адресов электронной почты и т. д. Кроме того, в этой модели вы можете легко удалить строку одного человека без каскадного удаления с помощью связей внешнего ключа.
базы данных NoSQL оптимизированы для простого чтения, вставки и операции удаления-SQL Server 2016 позволяет применять ту же логику в реляционной базе данных.
ограничения JSON В предыдущих примерах мы видели, как добавить простое ограничение, которое проверяет правильность форматирования текста, хранящегося в столбце. Хотя у JSON нет сильной схемы, вы также можете добавить сложные ограничения, объединив функции, считывающие значения из JSON и стандартных функций T-SQL:
ALTER TABLE Person
ADD CONSTRAINT [Age should be number]
CHECK ( ISNUMERIC(JSON_VALUE(value, '$.age'))>0 )
ALTER TABLE Person
ADD CONSTRAINT [Person should have skills]
CHECK ( JSON_QUERY(value, '$.skills') IS NOT NULL)
First constraint will take the value of $.age property and check is this numeric value. Second constraint will try to find JSON object in $.skills property and verify that it exists. The following INSERT statements will fail due to the violation of constraints:
INSERT INTO Person(value)
VALUES ('{"age": "not a number", "skills":[]}')
INSERT INTO Person(value)
VALUES ('{"age": 35}')
обратите внимание, что ограничения проверки могут замедлиться ваши процессы вставки / обновления, чтобы вы могли избежать их, если вам нужна более высокая производительность записи.
сжатое хранилище JSON Если у вас большой текст JSON, вы можете явно сжать текст JSON, используя встроенную функцию сжатия. В следующем примере сжатое содержимое JSON хранится как двоичные данные, и мы вычисляем столбец, который распаковывает JSON как исходный текст с помощью функции распаковки:
CREATE TABLE Person
( _id int identity constraint PK_JSON_ID primary key,
data varbinary(max),
value AS CAST(DECOMPRESS(data) AS nvarchar(max))
)
INSERT INTO Person(data)
VALUES (COMPRESS(@json))
функции сжатия и распаковки используют стандартное сжатие GZip. Если ваш клиент может обрабатывать сжатие GZip (e.G браузер, который понимает содержимое gzip), вы можете напрямую возвращать сжатый контент. Обратите внимание, что это компромисс производительности/хранения. Если вы часто запрашиваете сжатые данные, у mig более низкая производительность, потому что текст должен быть распакован каждый раз.
Примечание: функции JSON доступны только в SQL Server 2016+ и базе данных SQL Azure.
больше можно прочитать от источника этого статья
https://blogs.msdn.microsoft.com/sqlserverstorageengine/2015/11/23/storing-json-in-sql-server/
"золотое правило", которое я использую, в виде махания рукой, заключается в том, что если мне нужен JSON в его формате raw, это нормально хранить. Если я должен сделать особый акцент на его анализе, то это не так.
например, если я создаю API, который отправляет raw JSON, и по какой-либо причине это значение не изменится, то это ОК хранить его как raw JSON. Если мне нужно разобрать его, изменить, обновить и т. д... тогда не так много.
вопрос, который вы должны задать:
Я привязан к использованию только этой базы данных?
DO
- если вы можете использовать другую базу данных для хранения JSON, используйте решение для хранения документов, такое как CouchDB, DynamoDB или MongoDB.
- используйте способность этих БД хранения документов индексировать и искать иерархические данные.
- использовать реляционную базу данных для реляционных данных.
- использовать реляционную базу данных для отчетность, хранение данных и интеллектуальный анализ данных.
НЕ
- хранить JSON как строку, если это возможно.
- попробуйте придумать максимальную длину данных JSON.
- используйте varchar для хранения JSON (при необходимости используйте text/blob).
- попробуйте выполнить поиск по сохраненному JSON для значений.
- беспокоиться о побеге JSON для хранения в виде строки.
Json не велики по отношению к db. Если вы разворачиваете json в столбцы и храните в БД , это здорово, но хранение json как blob рядом с использованием его в качестве архивной системы данных.
может быть несколько причин не разворачивать json и хранить его в одном столбце, но решение было бы принято, поскольку значения в этом поле json не будут использоваться для каких-либо запросов (или значения уже были развернуты в столбцы).
кроме того , большинство обработки json, если бы вообще было запрошено поле, было бы вне среды sql, поскольку sql просто не предназначен для обработки json. Реальный вопрос тогда становится, где я храню этот json, я просто позволяю ему быть плоскими файлами и при необходимости запрашивать их через какую-то другую систему (spark/hive/etc).
Я бы согласился с вашим художником БД, не используйте СУБД для архивирования. Есть более дешевые варианты. Также JSON blobs может получить огромный и может начать увязать дисковое пространство DB с время.