在数据库三范式 文章中,我们分析了数据库的三范式,MySQL 在 5.7 版本中正式引入了原生的 JSON 数据类型,网上很多人关于 JSON 违反了数据库第一范式的争议,那么,JSON 真的违反了数据库第一范式吗?这篇文章,我们来聊一聊。
1. MySQL的JSON
MySQL 在 5.7 版本 中正式引入了原生的 JSON
数据类型,接下来我们来详细地介绍 JSON相关的信息。
1.1 MySQL 5.7 中的 JSON 支持
- 发布日期 :MySQL 5.7 正式发布于 2015 年10月。
- JSON 数据类型 :在此版本中,MySQL 引入了
JSON
类型,允许开发者在表中存储和操作 JSON 格式的数据。这不仅提高了处理半结构化数据的效率,还带来了更多的灵活性。 - 功能特性 :
- 验证 :MySQL 会自动验证存储在
JSON
列中的数据是否为有效的 JSON 格式。 - 高效存储 :
JSON
数据以二进制格式存储,优化了存储空间和访问速度。 - 内置函数 :MySQL 5.7 提供了一系列与 JSON 操作相关的函数,如
JSON_EXTRACT
、JSON_SET
、JSON_ARRAY
等,方便对 JSON 数据进行查询和修改。 - 索引支持 :虽然初期对
JSON
索引的支持有限,但通过生成虚拟列并在这些列上创建索引,可以提高查询性能。
- 验证 :MySQL 会自动验证存储在
1.2 后续版本的改进
- MySQL 8.0 :在随后的 MySQL 8.0 版本中,JSON 支持得到了进一步增强,包括:
- 更丰富的 JSON 函数 :新增了更多操作 JSON 数据的函数,如
JSON_TABLE
等。 - 性能优化:提升了 JSON 数据的处理性能,特别是在大规模数据集上的表现。
- 更好的与 SQL 标准的兼容性。
- 更丰富的 JSON 函数 :新增了更多操作 JSON 数据的函数,如
为了更好地理解 MySQL 的 JSON 数据类型,下面给出了一个使用 JSON 数据类型的简单示例。
sql
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
attributes JSON
);
INSERT INTO users (name, attributes) VALUES
('Alice', '{"age": 30, "city": "New York"}'),
('Bob', '{"age": 25, "city": "Los Angeles"}');
-- 查询 JSON 数据
SELECT name, JSON_EXTRACT(attributes, '$.city') AS city FROM users;
2. JSON 是否违反第一范式?
在关系数据库设计中,第一范式(1NF)要求每个表的每个字段都包含原子性 (不可再分)的值。这意味着每个字段只能存储单一值,不能包含集合、数组或其他复杂的数据结构。MySQL 的 JSON
数据类型允许在一个字段中存储复杂的嵌套数据结构,这在某些情况下可能违反 1NF,但在其他情况下又可能不违反。下面我们将分别举例来说明。
2.1 违反第一范式
为了说明 JSON违反1NF,这里以存储多个电话号码为例。假设我们有一个 users
表,每个用户可能有多个电话号码。如果我们将所有电话号码存储在一个 JSON
字段中,就违反了 1NF 的原子性要求。
sql
CREATE TABLE users_conflict (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
phone_numbers JSON
);
INSERT INTO users_conflict (name, phone_numbers) VALUES
('Alice', '["123-4567", "234-5678"]'),
('Bob', '["345-6789"]');
问题分析:
- 非原子性 :
phone_numbers
字段中包含了一个数组,存储了多个电话号码,违反了每个字段只能包含单一值的要求。 - 数据冗余与一致性:查询特定电话号码或更新某个电话号码变得复杂,且难以利用关系数据库的约束(如唯一性)来保证数据的一致性。
当我们要查询所有包含电话号码 "123-4567"
的用户时,SQL语句如下:
sql
SELECT name
FROM users_conflict
WHERE JSON_CONTAINS(phone_numbers, '"123-4567"', '$');
虽然 MySQL 提供了 JSON 函数,但这种查询复杂度高于标准的关系型查询,并且性能可能较低。
2.2 不违反第一范式
为了说明 JSON 不违反1NF,这里以存储可选的、结构化的属性为例。假设我们有一个 products
表,其中大部分产品都有固定的属性(如 id
、name
、price
),但某些产品可能有额外的可选属性(如 dimensions
、manufacturer_details
)。这些可选属性可以存储在一个 JSON
字段中,而不会违反 1NF。
sql
CREATE TABLE products_no_conflict (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100),
price DECIMAL(10,2),
attributes JSON
);
INSERT INTO products_no_conflict (name, price, attributes) VALUES
('Laptop', 999.99, '{"dimensions": {"width": 35, "height": 2, "depth": 25}, "manufacturer_details": {"name": "TechCorp", "warranty": "2 years"}}'),
('Smartphone', 599.99, '{"color": "black", "storage": "128GB"}'),
('Book', 19.99, NULL);
问题分析:
- 维持原子性 :
attributes
字段用于存储可选的、结构化的额外信息。每个attributes
字段本身被视为一个单一的 JSON 值,符合 1NF 的原子性要求。 - 数据灵活性:不需要为每种可能的属性创建单独的列,保持了表结构的简洁性。
- 查询与维护:尽管某些查询可能需要使用 JSON 函数,但由于这些属性是附加的、可选的,不会影响表的主要结构和核心数据的完整性。
当我们要查询所有宽度大于 30 的产品时,SQL语句如下:
sql
SELECT name, attributes
FROM products_no_conflict
WHERE JSON_EXTRACT(attributes, '$.dimensions.width') > 30;
虽然这种查询依赖于 JSON 函数,但由于 attributes
字段仅包含相关的附加信息,主表结构依然保持了 1NF 的原子性。
3. 总结
本文,我们分析了 MySQL 的 JSON
数据类型是否违反了数据库的第一范式(1NF),通过全文的分析,我们可以知道:JSON 是否违反 1NF 取决于具体的应用需求和数据模型设计:
-
与 1NF 冲突 :当
JSON
字段用于存储多值集合 (如数组、重复组)时,会违反 1NF 的原子性要求。例如,将多个电话号码存储在一个JSON
字段中。 -
不与 1NF 冲突 :当
JSON
字段用于存储单一的结构化对象,即使该对象内部包含多个键值对,也可以视为一个原子值,从而不违反 1NF。例如,存储产品的可选属性或配置信息。
需要注意的是,虽然在某些情况下使用 JSON
字段不会直接违反 1NF,但过度依赖 JSON
可能会带来查询复杂性、性能问题和数据一致性维护的挑战。因此,在设计数据库时,应权衡使用 JSON
字段的优势与潜在的规范化冲突,确保数据模型的可靠性和可维护性。
4. 学习交流
如果你觉得文章有帮助,请帮忙转发给更多的好友,或关注公众号:猿java,持续输出硬核文章。