当使用SQL
或MySQL
(或与此相关的任何关系数据库)时-我知道将数据保存在常规列中对于索引和其他目的更好。
事情是加载和保存JSON
数据有时会更加简单-并使开发更容易。
是否有用于JSON
在数据库中保存原始数据的“黄金法则” ?
这样做绝对是不好的做法吗?
当使用SQL
或MySQL
(或与此相关的任何关系数据库)时-我知道将数据保存在常规列中对于索引和其他目的更好。
事情是加载和保存JSON
数据有时会更加简单-并使开发更容易。
是否有用于JSON
在数据库中保存原始数据的“黄金法则” ?
这样做绝对是不好的做法吗?
Answers:
主要问题是
JSON(如XML)非常适合数据交换,小型存储和通用定义的结构,但它不能参与在RDBMS中运行的典型操作。在大多数情况下,最好将JSON数据传输到普通表中,并在需要时重新创建JSON。
规范化的第一条规则规定,永远不要将多于一位的信息存储到一列中。您看到带有“ Mickey Mouse”之类的值的“ PersonName”列吗?您指向此并哭了:立即更改!
XML或JSON呢?这些类型是否会破坏1.NF?好吧,是的,不是...
如果实际上只存储一小部分信息,则完全可以将完整结构存储为一小部分信息。您会得到一个SOAP响应并想要存储它,因为您可能需要它作为以后的参考(但是您不会将这些数据用于您自己的进程)?只需按原样存储它即可!
现在,想象一个代表人的复杂结构(XML或JSON)(及其地址,更多详细信息...)。现在,将其放在的一列中PersonInCharge
。错了吗 难道这不应该存在于具有外键引用而不是XML / JSON的经过适当设计的相关表中吗?特别是如果同一个人可能出现在许多不同的行中,那么使用XML / JSON方法肯定是错误的。
但是现在想象一下需要存储历史数据。您想在给定的时间段内保留该人的数据。几天后,对方告诉您新地址?没问题!如果您需要,旧地址将保存在XML / JSON中...
结论:如果存储数据只是为了保留它,就可以了。如果这些数据是唯一的部分,那没关系。
但是如果您需要定期使用内部零件,或者这意味着多余的重复存储,那是不对的。
以下内容适用于SQL Server,在其他RDBM上可能有所不同。
XML不会存储为您看到的文本,而是存储为层次结构树。查询这是惊人的好表现!无法在字符串级别上解析此结构!
SQL Server(2016+)中的JSON位于字符串中,必须进行解析。没有真正的本机JSON类型(例如,有本机XML类型)。这可能会在以后出现,但是现在我假设JSON在SQL Server上的性能不如XML(请参阅UPDATE 2一节)。任何需要从JSON读取值的操作都将需要大量隐藏的字符串方法调用...
您可爱的DB艺术家:-D知道,按原样存储JSON违反RDBM的通用原则。他知道,
有一些解决方法(取决于您所使用的RDBMS),但是大多数方法都无法按照您希望的方式工作...
是
没有
您可以在字符串列中以JSON开头或以BLOB开头,并在需要时将其更改为物理表。我的魔幻水晶球告诉我,这可能是明天:-D
在此处找到有关性能和磁盘空间的一些想法:https : //stackoverflow.com/a/47408528/5089204
以下内容解决了SQL-Server 2016中的JSON和XML支持
用户@ mike123指向微软官方博客上的一篇文章,该文章似乎在实验中得到了证明,与在SQL Server中查询XML相比,查询JSON的速度快10倍。
关于此的一些想法:
与“实验”进行一些核对:
XQuery
支持一言不发!在数组中找到具有给定ID的产品?JSON需要读取全部内容,并在使用之后使用过滤器WHERE
,而XML
允许使用内部过滤器XQuery predicate
。不说FLWOR
.../text()
到会将其XPath
减少到小于2x。在相关文章中,用户“ Magento先生”已经指出了这一点,但是点击诱饵的标题仍然没有改变。SUBSTRING
and CHARINDEX
:-D的组合以下代码将显示更实际的实验
Product
(JSON数组与同级节点)GO 10
将遍历此块十次,以避免出现首次通话偏见最终结果清楚地表明,JSON比XML慢(不是那么多,在一个非常简单的示例中约为1.5倍)。
最后声明:
测试代码
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
结果(Acer Aspire v17 Nitro Intel i7、8GB Ram上的SQL Server 2016 Express)
Test SumTime
------------------
json 2706
xml 1604
ProductID=1234
存在带有JSON的产品,您是否尝试搜索成千上万的JSON ?您是否尝试操纵JSON?您是否尝试过使用更深层的嵌套层次结构?
这个评论太长了。
如果它“绝对错误”,那么大多数数据库将不支持它。好的,大多数数据库在该FROM
子句中支持逗号,我认为这是“绝对错误”。但是对JSON的支持是新开发的,而不是向后兼容的“功能”。
一种明显的情况是,当JSON结构只是传递给应用程序的BLOB时。然后就没有争议了-除了存储JSON的开销外,对于在每个记录中具有公共字段的结构化数据,这是不必要的冗长。
另一种情况是“稀疏”列情况。您的行中包含许多可能的列,但是各行的行数不同。
另一种情况是您要将“嵌套”记录存储在记录中。JSON功能强大。
如果JSON在要查询的记录之间具有公共字段,那么通常最好将它们放在适当的数据库列中。但是,数据很复杂,并且存在诸如JSON之类的格式的地方。
我会挥舞我的魔杖。of!使用JSON的黄金法则:
如果MySQL不需要看里面的JSON,以及应用程序只需要的东西的集合,那么JSON是罚款,甚至可能更好。
如果要搜索内部数据,并且具有MariaDB 10.0.1或MySQL 5.7(具有JSON数据类型和函数),则JSON可能是实用的。MariaDB 5.3的“动态”列是此的变体。
如果您正在执行“ Entity-Attribute-Value”(实体-属性-值)工作,那么JSON不好,但是它是最少的弊端。 http://mysql.rjweb.org/doc.php/eav
对于按索引列进行搜索,将值埋在JSON中是一个很大的优势。
要按索引列上的范围或FULLTEXT
搜索或进行搜索SPATIAL
,则无法使用JSON。
对于WHERE a=1 AND b=2
“复合”索引来说INDEX(a,b)
是很棒的;JSON可能无法接近。
JSON适用于“稀疏”数据;INDEXing可以这样做,但效果不佳。(我指的是许多行的“丢失”或NULL值。)
JSON可以为您提供“数组”和“树”,而无需使用额外的表。但是只能在应用程序中而不是在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数据库(例如Azure DocumentDB或MongoDB)中创建的标准NoSQL集合,其中您仅具有代表ID的键和代表JSON的值。
请注意,NVARCHAR不仅是纯文本。SQL Server具有内置的文本压缩机制,可以透明地压缩存储在磁盘上的数据。压缩取决于语言,并且取决于您的数据,压缩可能高达50%(请参阅UNICODE压缩)。
SQL Server与其他普通NoSQL数据库之间的主要区别在于,SQL Server使您能够使用混合数据模型,在该模型中,您可以将多个JSON对象存储在同一“集合”中,并将它们与常规关系列组合。
例如,假设我们知道您的集合中的每个人都将具有FirstName和LastName,并且您可以将有关该人的一般信息存储为一个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列上添加了CHECK约束)。
如果将此结构与标准NoSQL集合进行比较,您可能会注意到,您将可以更快地访问强类型数据(名字和姓氏)。因此,此解决方案是混合模型的理想选择,在混合模型中,您可以识别在所有对象之间重复的某些信息,而其他变量信息可以存储为JSON。这样,您可以将灵活性和性能结合在一起。
如果将此结构与Person表AdventureWorks数据库的架构进行比较,您可能会注意到我们删除了许多相关表。
除了简化方案之外,与复杂的关系结构相比,您的数据访问操作也将更简单。现在,您可以读取单个表,而不必联接多个表。当您需要将具有相关信息(电子邮件地址,电话号码)的新人插入时,可以在一个表中插入一条记录,而不必在AdventureWorks Person表中插入一条记录,而是使用Identity列查找将用于存储电话的外键,电子邮件地址等。此外,在此模型中,您可以轻松地删除单人行,而无需使用外键关系进行级联删除。
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}')
请注意,CHECK约束可能会减慢插入/更新过程,因此如果需要更快的写入性能,则可以避免使用它们。
压缩的JSON存储如果您有较大的JSON文本,则可以使用内置的COMPRESS函数显式压缩JSON文本。在以下示例中,压缩的JSON内容存储为二进制数据,并且我们使用DECOMPRESS函数计算了将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))
COMPRESS和DECOMPRESS函数使用标准的GZip压缩。如果您的客户端可以处理GZip压缩(例如,了解gzip内容的浏览器),则可以直接返回压缩的内容。注意,这是性能/存储权衡。如果您经常查询压缩数据,则迁移的性能会降低,因为每次都必须对文本进行解压缩。
注意:JSON函数仅在SQL Server 2016+和Azure SQL数据库中可用。
可以从本文的源中阅读更多内容
https://blogs.msdn.microsoft.com/sqlserverstorageengine/2015/11/23/storing-json-in-sql-server/
我用一种简单的方式使用的“黄金法则”是,如果我需要原始格式的JSON,则可以存储。如果我必须特别分析它,那就不是。
例如,如果我正在创建一个发送原始JSON的API,并且无论出于何种原因,此值都不会改变,那么可以将其存储为原始JSON。如果我必须解析它,更改它,更新它,等等...那么就不那么多了。
您要问的问题是:
我是否只能使用此数据库?
做
别
Json在关系数据库方面并不出色。如果将json展开为列并存储在db中,那很好,但是将json存储为blob就是将其用作数据归档系统。
没有展开json并将其存储在单个列中可能有多种原因,但是由于该json字段中的值将不会用于任何查询(或者这些值已经展开为列),因此将做出决定。
另外,如果查询了所有字段,则大多数json处理将不在sql环境中,因为sql并非用于json处理。真正的问题就变成了:我在哪里存储这个json,我是否只是将其存储为平面文件,并在需要时通过其他系统(spark / hive / etc)对其进行查询。
我同意您的数据库艺术家的意见,请勿将RDBMS用于存档。有更便宜的选择。而且json blob可能会变得很大,并且会随着时间的推移而逐渐减少数据库磁盘空间。
PostgreSQL具有内置json
和jsonb
数据类型
这些是一些示例:
CREATE TABLE orders (
ID serial NOT NULL PRIMARY KEY,
info json NOT NULL
);
INSERT INTO orders (info)
VALUES
(
'{ "customer": "Lily Bush", "items": {"product": "Diaper","qty": 24}}'
),
(
'{ "customer": "Josh William", "items": {"product": "Toy Car","qty": 1}}'
),
(
'{ "customer": "Mary Clark", "items": {"product": "Toy Train","qty": 2}}'
);
PostgreSQL提供了两个本机运算符->
,->>
用于查询JSON数据。
运营商 ->
按键返回JSON对象字段。
运算符->>
按文本返回JSON对象字段。
SELECT
info -> 'customer' AS customer
FROM
orders;
SELECT
info ->> 'customer' AS customer
FROM
orders
WHERE
info -> 'items' ->> 'product' = 'Diaper'