在深入探讨 2026 年的技术图景之前,让我们先快速回顾一下基础。实体不过是可以在数据库中唯一标识的一条数据。例如,在大学数据库中,学生就是一个实体。老师也是一个实体,因为它可以被唯一标识。
- 属性是实体的特征。我们使用属性来提供关于实体的更多细节。
- 实体的特征仅存储在属性中。大多数时候,属性用于标识属性的实例。
- 一个没有任何属性的实体在数据库中是毫无用处的。
示例
让我们以一个学生实体为例。学生将拥有属性,如学号、姓名、班级等。
因此,这可以表示如下。
- 实体用矩形框表示,而属性用椭圆表示。
- 在上面的示例中,所有属性都放置在椭圆中,而学生实体用矩形显示。
- 数据库管理系统中存在多种类型的属性。在这里,我们将仅研究派生属性。
目录
什么是派生属性?
派生属性是指可以从其他属性派生出来的属性。这些属性在物理上并不存在于数据库中,但它们可以很容易地从其他属性派生出来。派生属性是在运行时计算的,因此可以说它们的值在性质上是变化的。
示例
- 让我们仍然以学生实体为例。假设它具有学号、姓名和出生日期等属性,如图所示。
- 如果我们取另一个属性“年龄”,我们可以从出生日期属性派生它。数据库中不需要存在年龄。派生属性用虚线椭圆表示,如图所示。
!Derived Attribute派生属性
让我们用具体的值来理解它。
- 我们将创建一个学生表,其中包含3个属性:roll_no(学号)、name(姓名)和 DOB(出生日期)。
- roll_no 用于标识给定学生实体的每个实例。
- 这里我们需要另一个属性,即 age(年龄)。但是 age 是一个派生属性,可以从给定学生表中的 DOB 属性派生。
学生表
name
—
kartik
yash
aditya
- 因此,利用 DOB 属性,我们可以很容易地派生出 age 属性。例如,
- 对于 roll_no 1,年龄 = 21岁(截至 2024年1月1日 的年龄)
- 对于 roll_no 2,年龄 = 20岁(截至 2024年1月1日 的年龄)
- 对于 roll_no 3,年龄 = 20岁零20天(截至 2024年1月1日 的年龄)
- 在这里,我们不需要 age 属性在数据库中的物理存在,但我们很容易从 DOB 属性派生了它。
- 因此,每当我们需要学生的年龄时,我们将在运行查询时根据 DOB 计算年龄。
- 这将不需要任何额外的内存来存储系统中的年龄属性。
- 这就被称为 派生属性。
派生属性的更多示例
- 百分比可以从获得的分数中派生。
- 城市可以从邮政编码中派生。
- 到期日期可以从借阅日期中派生。
- 利润可以从总成本和总收入中派生。
等等,还有很多。
派生属性的特征
- 它们不是物理存储的,它们是使用其他属性计算的,从而不需要存储成本。
- 它们依赖于数据库中的其他属性。
- 它们根据它们所依赖的属性的变化而变化。因此无需对派生属性进行显式更改。
派生属性的缺点
- 每次我们必须执行一组计算以获得派生属性的值时,计算量都会增加。
- 执行简单操作的时间增加,因为需要进行计算。
- 查询语言的复杂性增加,因为使用派生属性进行简单的实现时,我们必须每次都计算派生属性。
—
2026 深度视角:从数据库概念到企业级架构
既然我们已经回顾了教科书上的定义,让我们把目光投向 2026 年的现代开发环境。作为一名在一线摸爬滚打多年的技术专家,我必须告诉你,虽然定义未变,但在当今的高并发、云原生和 AI 驱动的应用架构中,处理派生属性的方式已经发生了革命性的变化。
在这篇文章的后续部分,我们将深入探讨在现代技术栈下,如何优雅地处理派生属性,以及我们是如何从“运行时计算”转向“预计算”与“实时推理”并行的混合架构的。让我们思考一下这个场景:当你面对每秒百万级的 QPS(每秒查询率),单纯依靠 SQL 的实时计算可能会导致数据库崩溃,这正是我们需要引入更先进的工程化手段的原因。
1. 现代开发范式:AI 辅助与“氛围编程”
在 2026 年,我们编写代码的方式已经发生了根本性的转变。随着 Agentic AI 和 Vibe Coding(氛围编程) 的兴起,我们不再是单纯地编写逻辑,而是在训练和编排智能体来处理这些派生关系。
AI 辅助下的 Schema 设计
你可能会问,AI 到底怎么帮我设计一个简单的派生属性?让我们来看一个实际的例子。假设我们要设计一个电商系统,其中“订单总价”是一个经典的派生属性,它依赖于单价、数量和折扣。
在传统模式下,我们会手写 SQL 或 ORM 逻辑。但在今天,我们更倾向于使用像 Cursor 或 Windsurf 这样的现代 AI IDE。我们不再只是告诉 AI “写一个 getter”,而是我们与 AI 进行对话式设计:
> 我们: “基于 Order 实体,生成一个 TypeScript 类。总价由单价乘以数量得出,但要考虑动态运费规则。同时,请为这个计算逻辑生成单元测试,特别是针对边界值(如负数库存)。”
生产级代码示例(TypeScript + 装饰器模式)
这是我们在生产环境中常见的处理方式,结合了类验证器和动态计算逻辑:
import { Entity, Column, PrimaryGeneratedColumn } from ‘typeorm‘;
import { IsNumber, IsPositive, Max } from ‘class-validator‘;
@Entity()
export class Order {
@PrimaryGeneratedColumn()
id: number;
@Column(‘decimal‘)
@IsNumber()
@IsPositive()
unitPrice: number; // 基础属性:单价
@Column(‘int‘)
@IsNumber()
quantity: number; // 基础属性:数量
@Column(‘decimal‘, { nullable: true })
@Max(100) // 折扣不能超过 100%
discountRate: number; // 基础属性:折扣率 (0.0 - 1.0)
// 派生属性:总价
// 注意:这里我们使用 getter 来模拟“虚拟列”,在 2026 年的 ORM 中非常普遍
public get totalPrice(): number {
// 1. 基础计算
const base = this.unitPrice * this.quantity;
// 2. 安全性检查:防止 NaN 或异常值
if (!this.discountRate || this.discountRate < 0) {
return parseFloat(base.toFixed(2));
}
// 3. 应用折扣逻辑
const discount = base * this.discountRate;
return parseFloat((base - discount).toFixed(2));
}
}
在这个例子中,我们不仅实现了计算,还利用 AI 辅助我们添加了验证装饰器。这就是 Vibe Coding 的精髓:我们关注数据流和业务规则的“氛围”,而让 AI 帮我们处理繁琐的样板代码和边界检查。
2. 工程化深度:性能优化与 CQRS 架构
虽然上面的代码在简单应用中运作良好,但在我们最近的一个金融科技项目中,简单地使用 Getter 进行实时计算导致了严重的性能瓶颈。当涉及到复杂的聚合(如计算用户的年度总支出,涉及数百万条交易记录)时,运行时计算不再可行。
真实场景分析:从计算到预计算
让我们思考一下这个场景:我们需要为每个用户实时展示“当前持仓盈亏”。这个属性派生自历史交易表和当前实时价格。
如果每次用户刷新 App 都去扫描全表,数据库 IO 会瞬间爆炸。我们如何解决这个问题?
我们采用了 CQRS(命令查询职责分离) 模式。在这个模式下,我们将“派生属性”从虚拟概念转化为物理存储,但通过事件驱动保持其更新。
#### 架构代码示例(Node.js + Redis + Event Sourcing)
const redis = require(‘redis‘);
const client = redis.createClient();
/**
* 获取用户的派生属性:净值
* 策略:优先读缓存,缓存未命中时计算并回写(Cache-Aside Pattern)
*/
async function getUserNetWorth(userId) {
const cacheKey = `user:${userId}:net_worth`;
// 1. 尝试从缓存获取(这是为了速度,Redis 延迟通常在 1ms 以内)
let cachedValue = await client.get(cacheKey);
if (cachedValue) {
console.log(`[Cache Hit] User ${userId} net worth fetched.`);
return parseFloat(cachedValue);
}
// 2. 缓存未命中,进行复杂计算(模拟数据库聚合)
// 在 2026 年,这里可能会调用一个专门的微服务或预计算的 Materialized View
console.log(`[Cache Miss] Calculating net worth for User ${userId}...`);
const calculatedValue = await performExpensiveAggregation(userId);
// 3. 将结果写回缓存
// 注意:我们设置了一个较短的过期时间(TTL),以平衡实时性和性能
// 对于金融数据,可能需要结合数据库触发器来主动失效缓存
await client.set(cacheKey, calculatedValue, ‘EX‘, 60);
return calculatedValue;
}
// 模拟复杂聚合函数
async function performExpensiveAggregation(userId) {
// 这里包含大量的 SQL JOIN 和 数学运算
// 在真实场景中,这可能是从 ClickHouse 或 Snowflake 中查询
return 1000000 + Math.random() * 1000; // 模拟值
}
关键决策:何时使用触发器?
在我们的项目中,如果数据一致性要求极高(如库存数量),我们会使用数据库触发器来维护派生属性。
代码示例(PostgreSQL 触发器):
-- 创建一个函数来更新派生属性
CREATE OR REPLACE FUNCTION update_total_inventory() RETURNS TRIGGER AS $$
BEGIN
-- 当库存表发生变化时,自动更新统计表中的总库存字段(派生属性)
UPDATE inventory_stats
SET total_items_count = total_items_count + (NEW.quantity - OLD.quantity)
WHERE product_id = NEW.product_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- 绑定触发器
CREATE TRIGGER trigger_update_inventory
AFTER UPDATE ON inventory_items
FOR EACH ROW
EXECUTE FUNCTION update_total_inventory();
常见陷阱与 2026 年解决方案
在处理派生属性时,我们踩过很多坑。以下是两个最常见的问题及现代解决方案:
- 精度丢失(The Floating Point Trap):
* 问题: 直接使用 INLINECODEbaf3a0e9 或 INLINECODE3ef3bf88 存储货币派生属性(如 0.1 + 0.2 !== 0.3)。
* 2026 解决方案: 在数据库层强制使用 INLINECODE5b623d38 类型。在代码层(JavaScript),我们使用专门的库如 INLINECODEf2afe5f4 或直接以“分”为单位存储整数(Int64),仅在展示层转换为元。
- 时区混乱:
* 问题: 派生“当前日期”或“到期时间”时,服务器时区与用户时区不一致导致的数据错误。
* 2026 解决方案: 我们的标准化做法是:所有数据库存储强制使用 UTC 时间戳(TIMESTAMPTZ)。派生属性的 UI 展示逻辑(如“还有 3 天到期”)全部在前端或 API 网关层根据用户的 Accept-Timezone 头进行处理。
3. 前沿技术:AI 原生应用中的“软”派生属性
这是最令人兴奋的前沿趋势。在 2026 年的 AI Native 应用中,派生属性的定义已经超越了数学计算,扩展到了 AI 语义推理。
从 SQL 计算到 LLM 推理
想象一个场景:用户上传了一张照片。我们存储的是原始图片(二进制数据)和它的 Embedding 向量。而“图片描述”、“情感标签”甚至“是否包含违规内容”,这些在 2026 年也被视为派生属性。
这些属性不是通过 SQL SELECT 计算的,而是通过调用 LLM 或 Vision Model 生成的。
架构实践:
# 伪代码:AI 驱动的派生属性生成
from openai import OpenAI
import json
class UserPost:
def __init__(self, content):
self.content = content # 原始数据
self._sentiment = None # 派生属性(惰性计算)
@property
def sentiment(self):
"""
惰性加载的派生属性。
只有在第一次访问时,才会调用 LLM 进行推理。
推理结果随后会被持久化到数据库,以节省 Token 成本。
"""
if self._sentiment is None:
# 检查缓存或数据库是否存在已计算的结果
# cached = db.get_sentiment(self.id)
# if cached: return cached
# 如果没有,调用 AI 进行计算
client = OpenAI()
response = client.chat.completions.create(
model="gpt-4o-2026",
messages=[{"role": "user", "content": f"Analyze sentiment: {self.content}"}]
)
self._sentiment = response.choices[0].message.content
# 异步保存结果,这就是“存储派生属性”
# db.save_sentiment(self.id, self._sentiment)
return self._sentiment
多模态开发中的挑战
在这种模式下,我们面临着新的挑战:
- 成本: 每次计算“情感”的成本远高于计算“年龄”。
- 非确定性: LLM 的输出可能不稳定。同一个文本,两次计算的派生属性可能略有不同。
- 解决方案: 我们采用 “一次推理,永久存储” 的策略。对于 AI 生成的派生属性,一旦计算出来,就将其作为物理列存入数据库,除非源数据发生重大变更,否则不再重新计算。
总结与未来展望
回顾这篇文章,我们从最基础的实体-属性模型出发,探讨了 派生属性 的定义。随后,我们深入到了 2026 年的技术栈,结合了 Vibe Coding、CQRS、Redis 缓存策略 以及 AI Native 架构,重新审视了这一经典概念。
在 2026 年,作为开发者,我们的核心任务不再是简单的编写 SQL 计算公式,而是要在 实时性、一致性、性能成本 和 AI 推理成本 之间做出最优的权衡。
- 对于简单逻辑(如年龄、总价):使用代码 Getter 或数据库 Views。
- 对于高频复杂逻辑:使用 CQRS 和 Redis 缓存。
- 对于 AI 逻辑:将其视为特殊的派生属性,并设计专门的异步持久化流程。
随着 AI 技术的进一步发展,我们预测未来的数据库将内置向量推理能力,届时“派生属性”与“AI 推理结果”的界限将彻底消失。希望这次深入探讨能帮助你在下一个项目中,更从容地设计数据架构。