数据库文档编写,开发规范文档,数据库接入

This commit is contained in:
comlibmb
2026-02-02 18:09:30 +08:00
parent 19970db288
commit 21149dd3fe
36 changed files with 3245 additions and 89 deletions

View 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、空字符等边界