Files
medical-mall/pages/mall/admin/docs/sql/04_triggers_and_functions.md

5.9 KiB
Raw Blame History

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

触发效果示例:

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
  • 核心逻辑:当新行/更新行被设为默认时,把该用户其他地址全部置为非默认。

示例:

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

示例:

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

示例:

update public.ml_orders
set order_status = 2
where id = '...order_uuid...'::uuid;

-- paid_at 自动写入

销量累计示例(订单完成):

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

示例:

select public.generate_order_no();

建议:

  • 若订单号生成需要“并发唯一 + 分库分表友好”,可以进一步引入节点号/随机段。

5.2 public.generate_coupon_code()

  • 目的:生成券码(脚本中为 CP + 8 位随机字符)。

示例:

select public.generate_coupon_code();

注意事项:

  • 随机生成+唯一约束在极端高并发下可能出现冲突重试需求(一般可接受)。

6. 查询辅助函数

6.1 public.get_user_default_address(p_user_id uuid)

  • 目的:快速获取用户默认地址,并拼接 full_address

示例:

select *
from public.get_user_default_address('...user_uuid...'::uuid);

6.2 public.calculate_cart_total(p_user_id uuid)

  • 目的:计算用户购物车选中商品的总金额(按 SKU 价 * 数量)。

示例:

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 的可用库存。

示例:

-- 查 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()

示例:

select * from public.get_category_by_cid(1001);
select public.generate_seo_url('shop', 88, 'my-shop');

注意事项:

  • update_seo_slugs() 使用正则把名称转为 slug
    • 适合初始化/批处理
    • 需要注意多语言、重复 slug、空字符等边界