数据库文档编写,开发规范文档,数据库接入
This commit is contained in:
240
docs/sql/04_triggers_and_functions.md
Normal file
240
docs/sql/04_triggers_and_functions.md
Normal file
@@ -0,0 +1,240 @@
|
||||
# 04 触发器与函数(数据库承载的业务规则)
|
||||
|
||||
本节汇总数据库内实现的关键触发器与函数,说明:
|
||||
|
||||
- 它们解决什么业务问题
|
||||
- 触发时机是什么
|
||||
- 对数据一致性与性能的影响
|
||||
- 典型使用/触发示例
|
||||
|
||||
---
|
||||
|
||||
## 1. 通用触发器:自动维护 `updated_at`
|
||||
|
||||
### 1.1 `public.update_updated_at_column()`(complete 脚本)
|
||||
|
||||
- **目的**:统一把 `updated_at` 设置为当前时间,避免应用层漏写。
|
||||
- **触发时机**:`BEFORE UPDATE`
|
||||
|
||||
典型触发表(complete 脚本中出现):
|
||||
|
||||
- `ml_user_profiles`
|
||||
- `ml_user_addresses`
|
||||
- `ml_products`
|
||||
- `ml_product_skus`
|
||||
- `ml_shops`
|
||||
- `ml_orders`
|
||||
- `ml_shopping_cart`
|
||||
|
||||
触发效果示例:
|
||||
|
||||
```sql
|
||||
update public.ml_products
|
||||
set name = '新标题'
|
||||
where id = '...product_uuid...'::uuid;
|
||||
|
||||
-- updated_at 会自动变为 now()
|
||||
```
|
||||
|
||||
### 1.2 `public.set_updated_at()`(订阅脚本)
|
||||
|
||||
订阅模块在 `doc_mall/create_mall_subscription_tables.sql` 里定义了一个更轻量的 `set_updated_at()`,并对:
|
||||
|
||||
- `ml_subscription_plans`
|
||||
- `ml_user_subscriptions`
|
||||
|
||||
设置 `BEFORE UPDATE` 触发器。
|
||||
|
||||
---
|
||||
|
||||
## 2. 地址一致性:默认地址唯一
|
||||
|
||||
### `public.ensure_single_default_address()`
|
||||
|
||||
- **目的**:保证同一个用户最多只有一个 `is_default = true` 的地址。
|
||||
- **触发时机**:`BEFORE INSERT OR UPDATE ON ml_user_addresses`
|
||||
- **核心逻辑**:当新行/更新行被设为默认时,把该用户其他地址全部置为非默认。
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
update public.ml_user_addresses
|
||||
set is_default = true
|
||||
where id = '...address_uuid...'::uuid;
|
||||
|
||||
-- 触发器会把同 user_id 的其他地址 is_default 设为 false
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 这是“业务规则下沉 DB”的典型。
|
||||
- 若存在并发更新两条地址为默认,最终仍会收敛为“最后提交事务的那条为默认”。
|
||||
|
||||
---
|
||||
|
||||
## 3. 库存汇总:SKU 维护,SPU 汇总
|
||||
|
||||
### `public.update_product_stock()`
|
||||
|
||||
- **目的**:当 SKU 改变时,自动汇总刷新商品表的库存字段,避免每次展示都 `join + group by`。
|
||||
- **触发时机**:`AFTER INSERT OR UPDATE OR DELETE ON ml_product_skus`
|
||||
- **影响字段**:
|
||||
- `ml_products.total_stock`
|
||||
- `ml_products.available_stock`
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
update public.ml_product_skus
|
||||
set stock = 8
|
||||
where id = '...sku_uuid...'::uuid;
|
||||
|
||||
-- 触发器会汇总该 product_id 下所有 status=1 的 SKU stock
|
||||
-- 并更新到 ml_products.total_stock / available_stock
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 这种“汇总字段”设计利于读性能,但写入 SKU 会产生额外更新 SPU 的成本。
|
||||
- 若 SKU 更新频率很高,需要评估热点商品写放大。
|
||||
|
||||
---
|
||||
|
||||
## 4. 订单状态副作用(complete 脚本)
|
||||
|
||||
### `public.handle_order_status_change()`
|
||||
|
||||
- **目的**:订单状态变化时,自动写入关键时间点,并在完成时累计销量。
|
||||
- **触发时机**:`BEFORE UPDATE ON ml_orders`
|
||||
|
||||
核心行为(按脚本逻辑):
|
||||
|
||||
- 从 `order_status: 1 -> 2`:写入 `paid_at = now()`
|
||||
- 从 `order_status: 2 -> 3`:写入 `shipped_at = now()`
|
||||
- 从 `order_status: 3 -> 4`:写入 `delivered_at/completed_at = now()`,并累计 `ml_products.sale_count`
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
update public.ml_orders
|
||||
set order_status = 2
|
||||
where id = '...order_uuid...'::uuid;
|
||||
|
||||
-- paid_at 自动写入
|
||||
```
|
||||
|
||||
销量累计示例(订单完成):
|
||||
|
||||
```sql
|
||||
update public.ml_orders
|
||||
set order_status = 4
|
||||
where id = '...order_uuid...'::uuid;
|
||||
|
||||
-- 会对订单明细涉及的商品 sale_count 做累加
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 若业务上“支付状态”与“订单状态”不是严格 1->2 的映射,可能需要调整触发条件。
|
||||
- 累计销量属于“统计字段”,适合下沉 DB;但需要考虑退款/取消是否回滚销量(脚本未体现)。
|
||||
|
||||
---
|
||||
|
||||
## 5. 生成类函数
|
||||
|
||||
### 5.1 `public.generate_order_no()` + `public.ml_order_seq`
|
||||
|
||||
- **目的**:生成业务订单号(形如 `MLYYYYMMDD000001`)。
|
||||
- **依赖**:序列 `ml_order_seq`
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
select public.generate_order_no();
|
||||
```
|
||||
|
||||
建议:
|
||||
|
||||
- 若订单号生成需要“并发唯一 + 分库分表友好”,可以进一步引入节点号/随机段。
|
||||
|
||||
### 5.2 `public.generate_coupon_code()`
|
||||
|
||||
- **目的**:生成券码(脚本中为 `CP` + 8 位随机字符)。
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
select public.generate_coupon_code();
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 随机生成+唯一约束在极端高并发下可能出现冲突重试需求(一般可接受)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 查询辅助函数
|
||||
|
||||
### 6.1 `public.get_user_default_address(p_user_id uuid)`
|
||||
|
||||
- **目的**:快速获取用户默认地址,并拼接 `full_address`。
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
select *
|
||||
from public.get_user_default_address('...user_uuid...'::uuid);
|
||||
```
|
||||
|
||||
### 6.2 `public.calculate_cart_total(p_user_id uuid)`
|
||||
|
||||
- **目的**:计算用户购物车选中商品的总金额(按 SKU 价 * 数量)。
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
select public.calculate_cart_total('...user_uuid...'::uuid);
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- 该函数读取多表(cart + sku + product),并包含状态过滤。
|
||||
- 若购物车行数很多或频繁调用,需要关注执行计划与索引。
|
||||
|
||||
### 6.3 `public.get_product_available_stock(p_product_id uuid, p_sku_id uuid default null)`
|
||||
|
||||
- **目的**:查询商品或指定 SKU 的可用库存。
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
-- 查 SKU
|
||||
select public.get_product_available_stock('...product_uuid...'::uuid, '...sku_uuid...'::uuid);
|
||||
|
||||
-- 查商品汇总
|
||||
select public.get_product_available_stock('...product_uuid...'::uuid, null);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. SEO 相关函数(complete 脚本)
|
||||
|
||||
- `get_product_by_cid(p_cid int)`
|
||||
- `get_category_by_cid(p_cid int)`
|
||||
- `get_brand_by_cid(p_cid int)`
|
||||
- `get_shop_by_cid(p_cid int)`
|
||||
- `generate_seo_url(p_type, p_cid, p_slug)`
|
||||
- `update_seo_slugs()`
|
||||
|
||||
示例:
|
||||
|
||||
```sql
|
||||
select * from public.get_category_by_cid(1001);
|
||||
select public.generate_seo_url('shop', 88, 'my-shop');
|
||||
```
|
||||
|
||||
注意事项:
|
||||
|
||||
- `update_seo_slugs()` 使用正则把名称转为 slug:
|
||||
- 适合初始化/批处理
|
||||
- 需要注意多语言、重复 slug、空字符等边界
|
||||
Reference in New Issue
Block a user