SQL Server中JSON转关系模型:单表冗余还是关联实体?
我所在的项目架构无法完全自主决策,当前需要把SQL Server数据库表中的JSON varchar字段转换为关系数据模型。示例JSON有三类互斥结构:
{"TotalAmount":50000.0,"Level":2,"NavigationProperty3Id":2}{"AmountAdded":6,"ProgressValue":9,"IsCompleted":true,"NavigationProperty4Id":2,"AmountLeftToReach":49991}{"Code":"stringValue1","BaseAmount":200.0}
方案一:单表反范式(负责人推荐)
将JSON字段拆分为LargeEntity的新增字段,认为能提升性能,实体类代码如下:
[Table("LargeEntities")] public class LargeEntity { [Key] public long Id { get; set; } public int AccountId { get; set; } public DateTime CreationTime { get; set; } public long? NavigationProperty1Id { get; set; } public long? NavigationProperty2Id { get; set; } public ENavigationProperty3Type NavigationProperty3Type { get; set; } public string AdditionalData { get; set; } [ForeignKey(nameof(NavigationProperty1Id))] public virtual AccountRelatedEntity1 AccountRelatedEntity1 { get; set; } [ForeignKey(nameof(NavigationProperty2Id))] public virtual AccountRelatedEntity2 AccountRelatedEntity2 { get; set; } [ForeignKey(nameof(NavigationProperty3Id))] // 新增字段 public virtual AccountRelatedEntity3 AccountRelatedEntity3 { get; set; } // 新增字段 [ForeignKey(nameof(NavigationProperty4Id))] // 新增字段 public virtual AccountRelatedEntity4 AccountRelatedEntity4 { get; set; } // 新增字段 public decimal? TotalAmount { get; set; } // 新增字段 public int? Level { get; set; } // 新增字段 public int? NavigationProperty3Id { get; set; } // 新增字段 public bool? IsCompleted { get; set; } // 新增字段 public decimal? AmountLeftToReach { get; set; } // 新增字段 public decimal? AmountAdded { get; set; } // 新增字段 public decimal? ProgressValue { get; set; } // 新增字段 public bool? IsCalculatedCorrectly { get; set; } // 新增字段 public int? NavigationProperty4Id { get; set; } // 新增字段 public string? Code { get; set; } // 新增字段 public decimal? BaseAmount { get; set; } // 新增字段 [NotMapped] public string Description { get; set; } }
标注// 新增字段的为JSON反序列化得到的关系模型字段,且存在三类互斥字段场景:
TotalAmount, Level, NavigationProperty3Id非空,其余两类字段为空NavigationProperty4Id, IsCalculatedCorrectly, AmountAdded, ProgressValue, IsCompleted, AmountLeftToReach非空,其余两类字段为空Code, BaseAmount非空,其余两类字段为空
方案二:独立关联实体(我推荐)
创建独立的RelatedEntity关联表,对应三类互斥场景,实体类代码如下:
[Table("RelatedEntities")] public class RelatedEntity { public long LargeEntityId { get; set; } // 新增字段 [ForeignKey(nameof(LargeEntityId))] // 新增字段 public virtual LargeEntity LargeEntity { get; set; } // 新增字段 [ForeignKey(nameof(NavigationProperty3Id))] // 新增字段 public virtual AccountRelatedEntity3 AccountRelatedEntity3 { get; set; } // 新增字段 [ForeignKey(nameof(NavigationProperty4Id))] // 新增字段 public virtual AccountRelatedEntity4 AccountRelatedEntity4 { get; set; } // 新增字段 public decimal? TotalAmount { get; set; } // 新增字段 public int? Level { get; set; } // 新增字段 public int? NavigationProperty3Id { get; set; } // 新增字段 public bool? IsCompleted { get; set; } // 新增字段 public decimal? AmountLeftToReach { get; set; } // 新增字段 public decimal? AmountAdded { get; set; } // 新增字段 public decimal? ProgressValue { get; set; } // 新增字段 public bool? IsCalculatedCorrectly { get; set; } // 新增字段 public int? NavigationProperty4Id { get; set; } // 新增字段 public string? Code { get; set; } // 新增字段 public decimal? BaseAmount { get; set; } // 新增字段 }
核心疑问
- 单表方案的大量未使用NULL字段是否比关联实体方案占用更多磁盘空间?
- 单表真的比多表性能更高吗?
- 将JSON转为表字段是否真有收益?
解答
1. 磁盘空间对比
SQL Server对NULL值的存储做了优化:可变长度的可空字段(比如decimal?、int?)为NULL时,不会占用实际数据空间,仅在列的NULL位图中占用1位。但单表方案的问题在于,总列数更多会增加每行的元数据开销;如果涉及固定长度的可空字段,即使为NULL也会占用固定空间。
关联实体方案中,每行仅存储对应场景的非空字段,且只有存在JSON数据的LargeEntity才会有对应的RelatedEntity记录。整体磁盘占用大概率比单表方案更优,尤其是当三类场景分布均衡时。
2. 性能对比
单表的性能优势仅存在于无需关联查询的场景:比如只查询LargeEntity基础字段和对应场景的衍生字段时,无需JOIN,速度更快。但如果经常只需要部分场景的字段,单表会读取更多列数据(即使是NULL),反而降低性能——因为SQL Server按页读取数据,单表每行更长,一页容纳的行数更少,磁盘IO次数会增加。
关联实体方案需要JOIN,但如果在LargeEntityId上建立主键/索引,JOIN开销极低。此外,关联表可针对不同场景建立精准索引(比如给场景1的TotalAmount, Level建复合索引),而单表因字段多且互斥,索引利用率低,甚至会因索引过多导致写入性能下降。如果业务中经常需要筛选或排序JSON衍生字段,关联实体的索引策略会更灵活,性能反而优于单表。
3. JSON转表字段的收益
是否有收益取决于业务场景:
- 收益场景:如果经常需要对JSON字段做筛选、排序、聚合操作,或需要建索引加速查询,转成表字段能避免SQL Server解析JSON的开销,同时利用关系型数据库的索引优化,性能提升明显。此外,表字段的约束性更强(可设置外键、数据类型校验),能避免JSON中的非法数据。
- 无收益/负收益场景:如果只是偶尔读取JSON内容,或JSON结构经常变化,转成表字段会增加维护成本,每次结构变更都需修改表结构和实体类,反而不如直接存储JSON灵活。
结合你的场景,JSON有三类固定互斥结构,转成关系字段是有收益的,但关联实体方案更适合避免单表的冗余和维护问题。
内容的提问来源于stack exchange,提问作者Ivan Silkin

