1. 引言
本文基于 Oracle 21c 数据库,以珠宝行业业务为背景,系统讲解事务与并发控制的核心机制。内容涵盖建库建表、测试数据准备、ACID 特性与隔离级别、锁机制、死锁处理以及 Saga 分布式事务模式,并提供可直接运行的完整 SQL 脚本。
2. 环境准备与建库建表
介绍数据库用户创建、表空间配置,以及珠宝行业核心业务表的设计,包括分类表、产品表、客户表、订单表、订单明细表、账户表、库存日志表、Saga 日志表、Saga 编排器表和死锁演示表。
3. 测试数据准备
为各业务表插入符合珠宝行业场景的测试数据,包括产品、客户、订单、账户余额和库存记录,为后续事务与并发实验提供数据基础。
4. ACID 特性与隔离级别
通过实际 SQL 演示 Oracle 21c 中原子性、一致性、隔离性和持久性的实现方式,重点讲解读已提交、可串行化等隔离级别在并发场景下的行为差异。
5. 锁机制详解
分析 Oracle 行级锁、表级锁、共享锁与排他锁的工作原理,结合珠宝库存扣减、订单状态更新等业务场景,演示如何通过锁保证数据一致性。
6. 死锁检测与处理
构造死锁演示场景,讲解死锁产生条件、Oracle 自动检测机制,以及通过统一加锁顺序、重试策略等方式避免死锁的实践方法。
7. Saga 分布式事务模式
介绍 Saga 模式在订单创建、库存预留、支付扣款、订单确认等跨步骤业务中的应用,包含 Saga 日志表、编排器表、补偿机制以及异步补偿调度器的完整实现。
8. 最终一致性验证与监控
通过一致性检查视图和死锁监控存储过程,验证 Saga 执行结果与数据最终一致性,并给出常见问题的排查思路。
9. 总结
回顾事务与并发控制的关键要点,总结珠宝行业场景下的最佳实践,并给出后续优化与扩展建议。
sql
-- ============================================================
-- Oracle 21c 珠宝行业 - 事务与并发模式完整脚本
-- geovindu
-- Transaction & Concurrency Patterns - Full Script
-- 本文件由以下模块合并而成:
-- 1. oracle21c_schema.sql (建库建表)
-- 2. oracle21c_data.sql (测试数据)
-- 3. oracle21c_acid_isolation.sql (ACID与隔离级别)
-- 4. oracle21c_locking.sql (锁机制)
-- 5. oracle21c_deadlock_saga.sql (死锁与Saga模式)
--
-- 执行方式:
-- sqlplus jewelry_user/jewelry_pwd@localhost:1521/orcl @oracle21c_full.sql
-- ============================================================
-- ============================================================
-- 模块: oracle21c_schema.sql
-- ============================================================
-- ============================================================
-- Oracle 21c 珠宝行业 - 建库建表
-- Transaction & Concurrency Patterns 基础表结构
-- ============================================================
-- 创建用户和表空间(需要DBA权限执行)
-- CREATE USER jewelry_user IDENTIFIED BY jewelry_pwd;
-- GRANT CONNECT, RESOURCE, DBA TO jewelry_user;
-- ALTER USER jewelry_user QUOTA UNLIMITED ON USERS;
-- 切换用户
-- CONNECT jewelry_user/jewelry_pwd;
-- 删除已存在的表(注意外键依赖顺序)
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE saga_log';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE inventory_log';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE account_ledger';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE order_items';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE orders';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE products';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE categories';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE customers';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE deadlock_demo';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
BEGIN
EXECUTE IMMEDIATE 'DROP TABLE saga_orchestrator';
EXCEPTION WHEN OTHERS THEN NULL;
END;
/
-- 1. 分类表
CREATE TABLE categories (
category_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
category_name VARCHAR2(100) NOT NULL,
parent_id NUMBER DEFAULT NULL,
description VARCHAR2(500) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT NULL,
CONSTRAINT fk_categories_parent FOREIGN KEY (parent_id) REFERENCES categories(category_id)
ON DELETE SET NULL ON UPDATE CASCADE
);
COMMENT ON TABLE categories IS '珠宝分类表';
COMMENT ON COLUMN categories.category_id IS '分类ID';
COMMENT ON COLUMN categories.category_name IS '分类名称';
COMMENT ON COLUMN categories.parent_id IS '父分类ID';
COMMENT ON COLUMN categories.description IS '分类描述';
COMMENT ON COLUMN categories.created_at IS '创建时间';
COMMENT ON COLUMN categories.updated_at IS '更新时间';
CREATE INDEX idx_categories_parent ON categories(parent_id);
-- 2. 产品表
CREATE TABLE products (
product_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
product_name VARCHAR2(200) NOT NULL,
category_id NUMBER NOT NULL,
material VARCHAR2(50) NOT NULL,
weight_gram DECIMAL(10,2) DEFAULT NULL,
price DECIMAL(12,2) NOT NULL,
stock_qty NUMBER DEFAULT 0 NOT NULL,
gemstone_type VARCHAR2(50) DEFAULT NULL,
gemstone_carat DECIMAL(6,2) DEFAULT NULL,
color_grade VARCHAR2(10) DEFAULT NULL,
clarity_grade VARCHAR2(10) DEFAULT NULL,
cut_grade VARCHAR2(10) DEFAULT NULL,
certificate VARCHAR2(100) DEFAULT NULL,
status VARCHAR2(20) DEFAULT 'active' NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT NULL,
CONSTRAINT fk_products_category FOREIGN KEY (category_id) REFERENCES categories(category_id)
ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT chk_products_status CHECK (status IN ('active','discontinued','reserved'))
);
COMMENT ON TABLE products IS '珠宝产品表';
COMMENT ON COLUMN products.product_id IS '产品ID';
COMMENT ON COLUMN products.product_name IS '产品名称';
COMMENT ON COLUMN products.category_id IS '分类ID';
COMMENT ON COLUMN products.material IS '材质:黄金/铂金/钻石/翡翠/红宝石等';
COMMENT ON COLUMN products.weight_gram IS '重量(克)';
COMMENT ON COLUMN products.price IS '单价(元)';
COMMENT ON COLUMN products.stock_qty IS '库存数量';
COMMENT ON COLUMN products.gemstone_type IS '宝石类型';
COMMENT ON COLUMN products.gemstone_carat IS '宝石克拉';
COMMENT ON COLUMN products.color_grade IS '颜色等级 D-E-F-G-H-I-J';
COMMENT ON COLUMN products.clarity_grade IS '净度等级 FL/IF/VVS/VS/SI';
COMMENT ON COLUMN products.cut_grade IS '切工等级 EX/VG/GD/Fair';
COMMENT ON COLUMN products.certificate IS '证书编号 GIA/NGTC';
COMMENT ON COLUMN products.status IS '状态:active/discontinued/reserved';
COMMENT ON COLUMN products.created_at IS '创建时间';
COMMENT ON COLUMN products.updated_at IS '更新时间';
CREATE INDEX idx_products_category ON products(category_id);
CREATE INDEX idx_products_material ON products(material);
CREATE INDEX idx_products_status ON products(status);
CREATE INDEX idx_products_name ON products(product_name(50));
-- 3. 客户表
CREATE TABLE customers (
customer_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
customer_name VARCHAR2(100) NOT NULL,
phone VARCHAR2(20) DEFAULT NULL,
email VARCHAR2(100) DEFAULT NULL,
vip_level VARCHAR2(20) DEFAULT 'normal' NOT NULL,
total_spent DECIMAL(12,2) DEFAULT 0.00 NOT NULL,
address VARCHAR2(500) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT NULL,
CONSTRAINT chk_customers_vip CHECK (vip_level IN ('normal','silver','gold','platinum'))
);
COMMENT ON TABLE customers IS '客户表';
COMMENT ON COLUMN customers.customer_id IS '客户ID';
COMMENT ON COLUMN customers.customer_name IS '客户姓名';
COMMENT ON COLUMN customers.phone IS '联系电话';
COMMENT ON COLUMN customers.email IS '电子邮箱';
COMMENT ON COLUMN customers.vip_level IS 'VIP等级:normal/silver/gold/platinum';
COMMENT ON COLUMN customers.total_spent IS '累计消费金额';
COMMENT ON COLUMN customers.address IS '收货地址';
COMMENT ON COLUMN customers.created_at IS '创建时间';
COMMENT ON COLUMN customers.updated_at IS '更新时间';
CREATE INDEX idx_customers_vip ON customers(vip_level);
CREATE INDEX idx_customers_phone ON customers(phone);
-- 4. 订单表
CREATE TABLE orders (
order_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
order_no VARCHAR2(30) NOT NULL,
customer_id NUMBER NOT NULL,
order_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
total_amount DECIMAL(12,2) DEFAULT 0.00 NOT NULL,
status VARCHAR2(20) DEFAULT 'pending' NOT NULL,
payment_method VARCHAR2(20) DEFAULT NULL,
payment_date TIMESTAMP DEFAULT NULL,
shipping_addr VARCHAR2(500) DEFAULT NULL,
notes VARCHAR2(500) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT NULL,
CONSTRAINT fk_orders_customer FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT chk_orders_status CHECK (status IN ('pending','confirmed','paid','shipped','delivered','cancelled','refunded'))
);
COMMENT ON TABLE orders IS '订单表';
COMMENT ON COLUMN orders.order_id IS '订单ID';
COMMENT ON COLUMN orders.order_no IS '订单编号';
COMMENT ON COLUMN orders.customer_id IS '客户ID';
COMMENT ON COLUMN orders.order_date IS '下单时间';
COMMENT ON COLUMN orders.total_amount IS '订单总金额';
COMMENT ON COLUMN orders.status IS '订单状态';
COMMENT ON COLUMN orders.payment_method IS '支付方式';
COMMENT ON COLUMN orders.payment_date IS '支付时间';
COMMENT ON COLUMN orders.shipping_addr IS '收货地址';
COMMENT ON COLUMN orders.notes IS '备注';
COMMENT ON COLUMN orders.created_at IS '创建时间';
COMMENT ON COLUMN orders.updated_at IS '更新时间';
CREATE INDEX idx_orders_customer ON orders(customer_id);
CREATE INDEX idx_orders_no ON orders(order_no);
CREATE INDEX idx_orders_status ON orders(status);
CREATE INDEX idx_orders_date ON orders(order_date);
-- 5. 订单明细表
CREATE TABLE order_items (
item_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
order_id NUMBER NOT NULL,
product_id NUMBER NOT NULL,
quantity NUMBER DEFAULT 1 NOT NULL,
unit_price DECIMAL(12,2) NOT NULL,
subtotal DECIMAL(12,2) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT fk_items_order FOREIGN KEY (order_id) REFERENCES orders(order_id)
ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT fk_items_product FOREIGN KEY (product_id) REFERENCES products(product_id)
ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT chk_items_quantity CHECK (quantity > 0)
);
COMMENT ON TABLE order_items IS '订单明细表';
COMMENT ON COLUMN order_items.item_id IS '明细ID';
COMMENT ON COLUMN order_items.order_id IS '订单ID';
COMMENT ON COLUMN order_items.product_id IS '产品ID';
COMMENT ON COLUMN order_items.quantity IS '购买数量';
COMMENT ON COLUMN order_items.unit_price IS '成交单价';
COMMENT ON COLUMN order_items.subtotal IS '小计=quantity*unit_price';
COMMENT ON COLUMN order_items.created_at IS '创建时间';
CREATE INDEX idx_items_order ON order_items(order_id);
CREATE INDEX idx_items_product ON order_items(product_id);
-- 6. 账户表(模拟客户账户余额,含乐观锁版本号)
CREATE TABLE account_ledger (
account_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
customer_id NUMBER NOT NULL,
balance DECIMAL(12,2) DEFAULT 0.00 NOT NULL,
version NUMBER DEFAULT 0 NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT NULL,
CONSTRAINT uk_account_customer UNIQUE (customer_id),
CONSTRAINT fk_account_customer FOREIGN KEY (customer_id) REFERENCES customers(customer_id)
ON DELETE CASCADE ON UPDATE CASCADE
);
COMMENT ON TABLE account_ledger IS '客户账户表';
COMMENT ON COLUMN account_ledger.account_id IS '账户ID';
COMMENT ON COLUMN account_ledger.customer_id IS '客户ID';
COMMENT ON COLUMN account_ledger.balance IS '账户余额';
COMMENT ON COLUMN account_ledger.version IS '乐观锁版本号';
COMMENT ON COLUMN account_ledger.created_at IS '创建时间';
COMMENT ON COLUMN account_ledger.updated_at IS '更新时间';
-- 7. 库存日志表(审计追踪)
CREATE TABLE inventory_log (
log_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
product_id NUMBER NOT NULL,
change_type VARCHAR2(20) NOT NULL,
change_qty NUMBER NOT NULL,
before_qty NUMBER NOT NULL,
after_qty NUMBER NOT NULL,
order_id NUMBER DEFAULT NULL,
reference_no VARCHAR2(50) DEFAULT NULL,
operator VARCHAR2(50) DEFAULT 'system' NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT fk_log_product FOREIGN KEY (product_id) REFERENCES products(product_id)
ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT fk_log_order FOREIGN KEY (order_id) REFERENCES orders(order_id)
ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT chk_log_type CHECK (change_type IN ('in','out','adjust_in','adjust_out','reserved','unreserved'))
);
COMMENT ON TABLE inventory_log IS '库存变动日志表';
COMMENT ON COLUMN inventory_log.log_id IS '日志ID';
COMMENT ON COLUMN inventory_log.product_id IS '产品ID';
COMMENT ON COLUMN inventory_log.change_type IS '变动类型:in/out/adjust_in/adjust_out/reserved/unreserved';
COMMENT ON COLUMN inventory_log.change_qty IS '变动数量(正数增加,负数减少)';
COMMENT ON COLUMN inventory_log.before_qty IS '变动前库存';
COMMENT ON COLUMN inventory_log.after_qty IS '变动后库存';
COMMENT ON COLUMN inventory_log.order_id IS '关联订单';
COMMENT ON COLUMN inventory_log.reference_no IS '关联单号';
COMMENT ON COLUMN inventory_log.operator IS '操作人';
COMMENT ON COLUMN inventory_log.created_at IS '创建时间';
CREATE INDEX idx_log_product ON inventory_log(product_id);
CREATE INDEX idx_log_order ON inventory_log(order_id);
CREATE INDEX idx_log_created ON inventory_log(created_at);
-- 8. Saga事务日志表(分布式事务补偿追踪)
CREATE TABLE saga_log (
saga_id VARCHAR2(64) NOT NULL PRIMARY KEY,
step_name VARCHAR2(100) NOT NULL,
step_order NUMBER NOT NULL,
status VARCHAR2(20) DEFAULT 'pending' NOT NULL,
request_data CLOB DEFAULT NULL,
response_data CLOB DEFAULT NULL,
error_message VARCHAR2(500) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
completed_at TIMESTAMP DEFAULT NULL,
CONSTRAINT chk_saga_status CHECK (status IN ('pending','executing','completed','compensating','compensated','failed'))
);
COMMENT ON TABLE saga_log IS 'Saga事务日志表';
COMMENT ON COLUMN saga_log.saga_id IS 'Saga事务ID';
COMMENT ON COLUMN saga_log.step_name IS '步骤名称';
COMMENT ON COLUMN saga_log.step_order IS '执行顺序';
COMMENT ON COLUMN saga_log.status IS '步骤状态';
COMMENT ON COLUMN saga_log.request_data IS '请求参数(JSON)';
COMMENT ON COLUMN saga_log.response_data IS '响应结果(JSON)';
COMMENT ON COLUMN saga_log.error_message IS '错误信息';
COMMENT ON COLUMN saga_log.created_at IS '创建时间';
COMMENT ON COLUMN saga_log.completed_at IS '完成时间';
CREATE INDEX idx_saga_log_saga ON saga_log(saga_id);
CREATE INDEX idx_saga_log_status ON saga_log(status);
-- 9. Saga编排器表(用于管理Saga执行状态)
CREATE TABLE saga_orchestrator (
saga_id VARCHAR2(64) NOT NULL PRIMARY KEY,
saga_type VARCHAR2(50) NOT NULL,
current_step NUMBER DEFAULT 0 NOT NULL,
total_steps NUMBER NOT NULL,
saga_status VARCHAR2(20) DEFAULT 'running' NOT NULL,
error_message VARCHAR2(500) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT NULL,
completed_at TIMESTAMP DEFAULT NULL,
CONSTRAINT chk_saga_orch_status CHECK (saga_status IN ('running','completed','compensating','compensated','failed'))
);
COMMENT ON TABLE saga_orchestrator IS 'Saga编排器表';
COMMENT ON COLUMN saga_orchestrator.saga_id IS 'Saga事务ID';
COMMENT ON COLUMN saga_orchestrator.saga_type IS 'Saga类型';
COMMENT ON COLUMN saga_orchestrator.current_step IS '当前执行步骤';
COMMENT ON COLUMN saga_orchestrator.total_steps IS '总步骤数';
COMMENT ON COLUMN saga_orchestrator.saga_status IS 'Saga状态';
COMMENT ON COLUMN saga_orchestrator.error_message IS '错误信息';
COMMENT ON COLUMN saga_orchestrator.created_at IS '创建时间';
COMMENT ON COLUMN saga_orchestrator.updated_at IS '更新时间';
COMMENT ON COLUMN saga_orchestrator.completed_at IS '完成时间';
-- 10. 死锁演示表
CREATE TABLE deadlock_demo (
id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
resource_type VARCHAR2(20) NOT NULL,
resource_id NUMBER NOT NULL,
balance DECIMAL(12,2) DEFAULT 0.00 NOT NULL,
version NUMBER DEFAULT 0 NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT chk_dl_resource CHECK (resource_type IN ('inventory','account'))
);
COMMENT ON TABLE deadlock_demo IS '死锁演示表';
COMMENT ON COLUMN deadlock_demo.id IS '演示ID';
COMMENT ON COLUMN deadlock_demo.resource_type IS '资源类型:inventory/account';
COMMENT ON COLUMN deadlock_demo.resource_id IS '资源ID';
COMMENT ON COLUMN deadlock_demo.balance IS '余额/库存值';
COMMENT ON COLUMN deadlock_demo.version IS '乐观锁版本号';
COMMENT ON COLUMN deadlock_demo.updated_at IS '更新时间';
CREATE INDEX idx_dl_resource ON deadlock_demo(resource_type, resource_id);
-- 创建序列(用于需要显式序列的场景)
CREATE SEQUENCE seq_categories START WITH 1 INCREMENT BY 1 NOCACHE;
CREATE SEQUENCE seq_products START WITH 1 INCREMENT BY 1 NOCACHE;
CREATE SEQUENCE seq_customers START WITH 1 INCREMENT BY 1 NOCACHE;
CREATE SEQUENCE seq_orders START WITH 1 INCREMENT BY 1 NOCACHE;
CREATE SEQUENCE seq_order_items START WITH 1 INCREMENT BY 1 NOCACHE;
CREATE SEQUENCE seq_account_ledger START WITH 1 INCREMENT BY 1 NOCACHE;
CREATE SEQUENCE seq_inventory_log START WITH 1 INCREMENT BY 1 NOCACHE;
CREATE SEQUENCE seq_deadlock_demo START WITH 1 INCREMENT BY 1 NOCACHE;
-- 创建视图:订单详情视图
CREATE OR REPLACE VIEW v_order_details AS
SELECT
o.order_id,
o.order_no,
c.customer_name,
c.phone,
o.order_date,
o.total_amount,
o.status,
p.product_name,
oi.quantity,
oi.unit_price,
oi.subtotal
FROM orders o
JOIN customers c ON o.customer_id = c.customer_id
JOIN order_items oi ON o.order_id = oi.order_id
JOIN products p ON oi.product_id = p.product_id;
COMMENT ON VIEW v_order_details IS '订单详情视图(含客户和产品名称)';
-- 创建视图:库存预警视图
CREATE OR REPLACE VIEW v_low_stock_products AS
SELECT
p.product_id,
p.product_name,
p.stock_qty,
p.price,
c.category_name
FROM products p
JOIN categories c ON p.category_id = c.category_id
WHERE p.stock_qty < 10 AND p.status = 'active';
COMMENT ON VIEW v_low_stock_products IS '库存预警视图(库存<10的活跃产品)';
-- 创建视图:客户消费统计
CREATE OR REPLACE VIEW v_customer_spending AS
SELECT
c.customer_id,
c.customer_name,
c.vip_level,
COALESCE(SUM(o.total_amount), 0) AS total_spent_actual,
COUNT(o.order_id) AS order_count
FROM customers c
LEFT JOIN orders o ON c.customer_id = o.customer_id AND o.status NOT IN ('cancelled','refunded')
GROUP BY c.customer_id, c.customer_name, c.vip_level;
COMMENT ON VIEW v_customer_spending IS '客户消费统计表';
-- 创建触发器:订单金额自动更新
CREATE OR REPLACE TRIGGER trg_update_order_total
BEFORE INSERT OR UPDATE OF order_id, product_id, quantity, unit_price ON order_items
FOR EACH ROW
BEGIN
:NEW.subtotal := :NEW.quantity * :NEW.unit_price;
END;
/
COMMENT ON TRIGGER trg_update_order_total IS '订单明细触发器:自动计算小计';
-- 创建触发器:库存变动审计
CREATE OR REPLACE TRIGGER trg_inventory_audit
AFTER UPDATE OF stock_qty ON products
FOR EACH ROW
BEGIN
IF :NEW.stock_qty <> :OLD.stock_qty THEN
INSERT INTO inventory_log (product_id, change_type, change_qty, before_qty, after_qty, operator)
VALUES (:NEW.product_id,
CASE WHEN :NEW.stock_qty > :OLD.stock_qty THEN 'adjust_in' ELSE 'adjust_out' END,
:NEW.stock_qty - :OLD.stock_qty,
:OLD.stock_qty,
:NEW.stock_qty,
'system');
END IF;
END;
/
COMMENT ON TRIGGER trg_inventory_audit IS '库存变动触发器:自动记录库存日志';
-- 创建同义词(方便测试)
CREATE SYNONYM sym_categories FOR categories;
CREATE SYNONYM sym_products FOR products;
CREATE SYNONYM sym_customers FOR customers;
CREATE SYNONYM sym_orders FOR orders;
CREATE SYNONYM sym_order_items FOR order_items;
CREATE SYNONYM sym_account_ledger FOR account_ledger;
CREATE SYNONYM sym_inventory_log FOR inventory_log;
CREATE SYNONYM sym_saga_log FOR saga_log;
CREATE SYNONYM sym_deadlock_demo FOR deadlock_demo;
-- 授权
-- GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES TO PUBLIC;
-- 验证表创建
SELECT table_name, tablespace_name, status
FROM user_tables
WHERE table_name IN ('CATEGORIES','PRODUCTS','CUSTOMERS','ORDERS','ORDER_ITEMS','ACCOUNT_LEDGER','INVENTORY_LOG','SAGA_LOG','SAGA_ORCHESTRATOR','DEADLOCK_DEMO')
ORDER BY table_name;
-- 验证视图
SELECT view_name FROM user_views WHERE view_name IN ('V_ORDER_DETAILS','V_LOW_STOCK_PRODUCTS','V_CUSTOMER_SPENDING');
-- 验证触发器
SELECT trigger_name FROM user_triggers WHERE trigger_name IN ('TRG_UPDATE_ORDER_TOTAL','TRG_INVENTORY_AUDIT');
-- 验证同义词
SELECT synonym_name FROM user_synonyms WHERE synonym_name LIKE 'SYM_%';
-- 验证序列
SELECT sequence_name FROM user_sequences WHERE sequence_name LIKE 'SEQ_%';
-- ============================================================
-- 模块: oracle21c_data.sql
-- ============================================================
-- ============================================================
-- Oracle 21c 珠宝行业 - 批量测试数据
-- Transaction & Concurrency Patterns 测试数据
-- ============================================================
-- ============================================================
-- 1. 分类数据(珠宝分类)
-- ============================================================
INSERT ALL
INTO categories (category_id, category_name, parent_id, description) VALUES (1, '戒指', NULL, '各类戒指')
INTO categories (category_id, category_name, parent_id, description) VALUES (2, '项链', NULL, '各类项链')
INTO categories (category_id, category_name, parent_id, description) VALUES (3, '手链', NULL, '各类手链')
INTO categories (category_id, category_name, parent_id, description) VALUES (4, '耳环', NULL, '各类耳环')
INTO categories (category_id, category_name, parent_id, description) VALUES (5, '胸针', NULL, '各类胸针')
INTO categories (category_id, category_name, parent_id, description) VALUES (6, '对戒', 1, '情侣对戒')
INTO categories (category_id, category_name, parent_id, description) VALUES (7, '钻戒', 1, '钻石戒指')
INTO categories (category_id, category_name, parent_id, description) VALUES (8, '黄金项链', 2, '黄金材质项链')
INTO categories (category_id, category_name, parent_id, description) VALUES (9, '铂金手链', 3, '铂金材质手链')
INTO categories (category_id, category_name, parent_id, description) VALUES (10, '珍珠耳环', 4, '珍珠材质耳环')
SELECT * FROM dual;
-- ============================================================
-- 2. 产品数据(50款珠宝产品)
-- ============================================================
INSERT ALL
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (1, '经典六爪钻戒', 7, '铂金', NULL, 28888.00, 15, '钻石', 1.00, 'D', 'VVS1', 'EX', 'GIA001', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (2, '玫瑰金碎钻戒指', 7, '玫瑰金', 3.5, 12888.00, 25, '钻石', 0.30, 'E', 'VS1', 'VG', 'GIA002', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (3, '18K黄金扭臂戒指', 1, '黄金', 5.2, 6888.00, 30, NULL, NULL, NULL, NULL, NULL, 'NGTC001', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (4, '蓝宝石戒指', 1, '铂金', 4.8, 18888.00, 10, '蓝宝石', 1.50, NULL, NULL, NULL, 'GIA003', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (5, '红宝石戒指', 1, '黄金', 4.5, 22888.00, 8, '红宝石', 1.20, NULL, NULL, NULL, 'GIA004', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (6, '翡翠戒指', 1, '铂金', 6.0, 35888.00, 5, '翡翠', NULL, NULL, NULL, NULL, 'NGTC002', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (7, '简约素圈对戒-男', 6, '铂金', 8.5, 8888.00, 20, NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (8, '简约素圈对戒-女', 6, '铂金', 5.0, 6888.00, 20, NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (9, '心形钻石项链', 2, '铂金', 12.5, 32888.00, 12, '钻石', 0.80, 'F', 'VS1', 'EX', 'GIA005', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (10, '珍珠吊坠项链', 2, 'K金', 15.0, 5888.00, 35, '珍珠', NULL, NULL, NULL, NULL, 'NGTC003', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (11, '黄金转运珠项链', 8, '黄金', 18.5, 9888.00, 40, NULL, NULL, NULL, NULL, 'NGTC004', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (12, '钻石链条款链', 2, '铂金', 22.0, 25888.00, 10, '钻石', 0.50, 'D', 'VVS2', 'EX', 'GIA006', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (13, '铂金手链-简约款', 9, '铂金', 25.0, 15888.00, 18, NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (14, '黄金手链-古法款', 3, '黄金', 30.0, 18888.00, 15, NULL, NULL, NULL, NULL, 'NGTC005', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (15, '钻石手链', 3, '铂金', 18.0, 38888.00, 8, '钻石', 0.60, 'E', 'VS1', 'VG', 'GIA007', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (16, '翡翠手链', 3, '铂金', 35.0, 28888.00, 6, '翡翠', NULL, NULL, NULL, NULL, 'NGTC006', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (17, '珍珠耳环-经典款', 10, 'K金', 8.0, 4888.00, 45, '珍珠', NULL, NULL, NULL, NULL, 'NGTC007', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (18, '钻石耳钉', 4, '铂金', 5.5, 16888.00, 20, '钻石', 0.40, 'D', 'VS1', 'EX', 'GIA008', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (19, '红宝石耳坠', 4, '黄金', 7.0, 22888.00, 10, '红宝石', 1.00, NULL, NULL, NULL, 'GIA009', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (20, '蓝宝石耳坠', 4, '铂金', 6.5, 19888.00, 12, '蓝宝石', 1.20, NULL, NULL, NULL, 'GIA010', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (21, '黄金胸针-花卉款', 5, '黄金', 15.0, 8888.00, 15, NULL, NULL, NULL, NULL, 'NGTC008', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (22, '钻石胸针', 5, '铂金', 10.0, 28888.00, 8, '钻石', 0.80, 'F', 'VS1', 'EX', 'GIA011', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (23, '翡翠吊坠', 2, '铂金', 20.0, 32888.00, 5, '翡翠', NULL, NULL, NULL, NULL, 'NGTC009', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (24, '和田玉吊坠', 2, 'K金', 22.0, 18888.00, 10, '和田玉', NULL, NULL, NULL, NULL, 'NGTC010', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (25, '玛瑙手链', 3, 'K金', 28.0, 3888.00, 25, '玛瑙', NULL, NULL, NULL, NULL, 'NGTC011', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (26, '石榴石手链', 3, '银', 20.0, 1888.00, 30, '石榴石', NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (27, '紫水晶项链', 2, '银', 15.0, 2888.00, 20, '紫水晶', NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (28, '绿松石戒指', 1, '银', 8.0, 2388.00, 15, '绿松石', NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (29, '坦桑石戒指', 1, '铂金', 5.0, 15888.00, 8, '坦桑石', 1.50, NULL, NULL, NULL, 'GIA012', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (30, '碧玺项链', 2, 'K金', 18.0, 12888.00, 12, '碧玺', NULL, NULL, NULL, NULL, 'NGTC012', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (31, '月光石耳环', 4, '银', 6.0, 1588.00, 25, '月光石', NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (32, '蛋白石项链', 2, '银', 12.0, 3888.00, 15, '蛋白石', NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (33, '海蓝宝石戒指', 1, '铂金', 4.5, 18888.00, 8, '海蓝宝石', 1.80, NULL, NULL, NULL, 'GIA013', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (34, '橄榄石手链', 3, 'K金', 22.0, 5888.00, 18, '橄榄石', NULL, NULL, NULL, NULL, 'NGTC013', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (35, '黄玉吊坠', 2, '黄金', 16.0, 8888.00, 12, '黄玉', NULL, NULL, NULL, NULL, 'NGTC014', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (36, '红纹石胸针', 5, 'K金', 12.0, 6888.00, 10, '红纹石', NULL, NULL, NULL, NULL, 'NGTC015', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (37, '黑曜石手链', 3, '银', 30.0, 888.00, 40, '黑曜石', NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (38, '南红玛瑙戒指', 1, '黄金', 7.0, 12888.00, 10, '南红', NULL, NULL, NULL, NULL, 'NGTC016', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (39, '冰种翡翠手镯', 3, '铂金', 45.0, 68888.00, 3, '翡翠', NULL, NULL, NULL, NULL, 'NGTC017', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (40, '高冰钻石项链', 2, '铂金', 15.0, 58888.00, 5, '钻石', 2.00, 'D', 'IF', 'EX', 'GIA014', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (41, '古法黄金手镯', 3, '黄金', 50.0, 45888.00, 8, NULL, NULL, NULL, NULL, NULL, 'NGTC018', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (42, '18K玫瑰金项链', 2, '玫瑰金', 10.0, 8888.00, 20, NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (43, '铂金素圈戒指', 1, '铂金', 6.0, 9888.00, 25, NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (44, 'K金钻石手链', 3, 'K金', 15.0, 28888.00, 10, '钻石', 0.80, 'E', 'VS1', 'VG', 'GIA015', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (45, '珍珠手链', 3, 'K金', 18.0, 6888.00, 20, '珍珠', NULL, NULL, NULL, NULL, 'NGTC019', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (46, '黄金项链-粗链', 8, '黄金', 35.0, 22888.00, 12, NULL, NULL, NULL, NULL, 'NGTC020', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (47, '铂金项链-细链', 2, '铂金', 8.0, 12888.00, 15, NULL, NULL, NULL, NULL, NULL, 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (48, '翡翠戒指-蛋面', 1, '铂金', 8.0, 45888.00, 4, '翡翠', NULL, NULL, NULL, NULL, 'NGTC021', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (49, '钻石耳环-流苏款', 4, '铂金', 6.0, 25888.00, 8, '钻石', 0.50, 'D', 'VVS2', 'EX', 'GIA016', 'active')
INTO products (product_id, product_name, category_id, material, weight_gram, price, stock_qty, gemstone_type, gemstone_carat, color_grade, clarity_grade, cut_grade, certificate, status) VALUES (50, '黄金戒指-龙牌', 1, '黄金', 12.0, 15888.00, 10, NULL, NULL, NULL, NULL, 'NGTC022', 'active')
SELECT * FROM dual;
-- ============================================================
-- 3. 客户数据(30个客户)
-- ============================================================
INSERT ALL
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (1, '张伟', '13800138001', 'zhangwei@email.com', 'platinum', 158888.00, '北京市朝阳区建国路88号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (2, '李娜', '13800138002', 'lina@email.com', 'gold', 88888.00, '上海市浦东新区陆家嘴环路100号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (3, '王磊', '13800138003', 'wanglei@email.com', 'gold', 65888.00, '广州市天河区天河路200号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (4, '赵敏', '13800138004', 'zhaomin@email.com', 'silver', 32888.00, '深圳市南山区科技园路50号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (5, '刘洋', '13800138005', 'liuyang@email.com', 'silver', 28888.00, '杭州市西湖区文三路300号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (6, '陈静', '13800138006', 'chenjing@email.com', 'gold', 72888.00, '成都市锦江区春熙路88号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (7, '杨帆', '13800138007', 'yangfan@email.com', 'normal', 8888.00, '南京市鼓楼区中山北路100号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (8, '黄丽', '13800138008', 'huangli@email.com', 'platinum', 198888.00, '武汉市江汉区解放大道688号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (9, '周强', '13800138009', 'zhouqiang@email.com', 'gold', 55888.00, '重庆市渝中区解放碑步行街1号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (10, '吴婷', '13800138010', 'wuting@email.com', 'silver', 25888.00, '西安市雁塔区高新路60号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (11, '徐峰', '13800138011', 'xufeng@email.com', 'normal', 5888.00, '天津市和平区南京路100号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (12, '孙莉', '13800138012', 'sunli@email.com', 'gold', 48888.00, '青岛市市南区香港西路2号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (13, '马军', '13800138013', 'majun@email.com', 'silver', 18888.00, '大连市中山区人民路30号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (14, '朱红', '13800138014', 'zhuhong@email.com', 'platinum', 128888.00, '长沙市天心区黄兴路步行街88号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (15, '胡涛', '13800138015', 'hutao@email.com', 'normal', 3888.00, '合肥市包河区徽州大道100号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (16, '林芳', '13800138016', 'linfang@email.com', 'gold', 68888.00, '郑州市金水区花园路60号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (17, '何勇', '13800138017', 'heyong@email.com', 'silver', 22888.00, '长沙市岳麓区枫林路88号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (18, '高敏', '13800138018', 'gaomin@email.com', 'platinum', 168888.00, '成都市武侯区科华北路65号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (19, '罗明', '13800138019', 'luoming@email.com', 'normal', 6888.00, '贵阳市南明区花果园大街1号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (20, '梁雪', '13800138020', 'liangxue@email.com', 'gold', 58888.00, '昆明市五华区翠湖路88号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (21, '宋刚', '13800138021', 'songgang@email.com', 'silver', 15888.00, '南宁市青秀区民族大道100号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (22, '谢霞', '13800138022', 'xiexia@email.com', 'normal', 4888.00, '兰州市城关区中山路50号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (23, '唐伟', '13800138023', 'tangwei@email.com', 'gold', 78888.00, '乌鲁木齐市天山区解放路88号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (24, '韩雪', '13800138024', 'hanxue@email.com', 'silver', 28888.00, '哈尔滨市道里区中央大街100号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (25, '冯刚', '13800138025', 'fenggang@email.com', 'normal', 2888.00, '济南市历下区泉城路88号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (26, '董丽', '13800138026', 'dongli@email.com', 'platinum', 188888.00, '石家庄市长安区中山路100号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (27, '彭飞', '13800138027', 'pengfei@email.com', 'silver', 35888.00, '福州市鼓楼区五四路88号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (28, '蒋敏', '13800138028', 'jiangmin@email.com', 'gold', 45888.00, '合肥市蜀山区怀宁路60号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (29, '沈毅', '13800138029', 'shenyi@email.com', 'normal', 1888.00, '南昌市西湖区中山路100号')
INTO customers (customer_id, customer_name, phone, email, vip_level, total_spent, address) VALUES (30, '姚芳', '13800138030', 'yaofang@email.com', 'gold', 92888.00, '昆明市盘龙区北京路88号')
SELECT * FROM dual;
-- ============================================================
-- 4. 账户数据(客户账户余额)
-- ============================================================
INSERT ALL
INTO account_ledger (account_id, customer_id, balance, version) VALUES (1, 1, 158888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (2, 2, 88888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (3, 3, 65888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (4, 4, 32888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (5, 5, 28888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (6, 6, 72888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (7, 7, 8888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (8, 8, 198888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (9, 9, 55888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (10, 10, 25888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (11, 11, 5888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (12, 12, 48888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (13, 13, 18888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (14, 14, 128888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (15, 15, 3888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (16, 16, 68888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (17, 17, 22888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (18, 18, 168888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (19, 19, 6888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (20, 20, 58888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (21, 21, 15888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (22, 22, 4888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (23, 23, 78888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (24, 24, 28888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (25, 25, 2888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (26, 26, 188888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (27, 27, 35888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (28, 28, 45888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (29, 29, 1888.00, 0)
INTO account_ledger (account_id, customer_id, balance, version) VALUES (30, 30, 92888.00, 0)
SELECT * FROM dual;
-- ============================================================
-- 5. 订单数据(30个订单)
-- ============================================================
INSERT ALL
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (1, 'ORD202401001', 1, TIMESTAMP '2024-01-15 10:30:00', 28888.00, 'delivered', '支付宝', TIMESTAMP '2024-01-15 10:31:00', '北京市朝阳区建国路88号', '经典六爪钻戒')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (2, 'ORD202401002', 2, TIMESTAMP '2024-01-16 14:20:00', 12888.00, 'delivered', '微信支付', TIMESTAMP '2024-01-16 14:21:00', '上海市浦东新区陆家嘴环路100号', '玫瑰金碎钻戒指')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (3, 'ORD202401003', 3, TIMESTAMP '2024-01-17 09:15:00', 6888.00, 'delivered', '银行卡', TIMESTAMP '2024-01-17 09:16:00', '广州市天河区天河路200号', '18K黄金扭臂戒指')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (4, 'ORD202401004', 4, TIMESTAMP '2024-01-18 16:45:00', 18888.00, 'shipped', '支付宝', TIMESTAMP '2024-01-18 16:46:00', '深圳市南山区科技园路50号', '蓝宝石戒指')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (5, 'ORD202401005', 5, TIMESTAMP '2024-01-19 11:00:00', 22888.00, 'paid', '微信支付', NULL, '杭州市西湖区文三路300号', '红宝石戒指')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (6, 'ORD202401006', 6, TIMESTAMP '2024-01-20 13:30:00', 35888.00, 'confirmed', '银行卡', NULL, '成都市锦江区春熙路88号', '翡翠戒指')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (7, 'ORD202401007', 7, TIMESTAMP '2024-01-21 10:00:00', 8888.00, 'pending', NULL, NULL, '南京市鼓楼区中山北路100号', '黄金转运珠项链')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (8, 'ORD202401008', 8, TIMESTAMP '2024-01-22 15:20:00', 32888.00, 'delivered', '支付宝', TIMESTAMP '2024-01-22 15:21:00', '武汉市江汉区解放大道688号', '心形钻石项链')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (9, 'ORD202401009', 9, TIMESTAMP '2024-01-23 09:45:00', 5888.00, 'delivered', '微信支付', TIMESTAMP '2024-01-23 09:46:00', '重庆市渝中区解放碑步行街1号', '珍珠吊坠项链')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (10, 'ORD202401010', 10, TIMESTAMP '2024-01-24 14:00:00', 15888.00, 'shipped', '银行卡', TIMESTAMP '2024-01-24 14:01:00', '西安市雁塔区高新路60号', '铂金手链-简约款')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (11, 'ORD202401011', 11, TIMESTAMP '2024-01-25 11:30:00', 18888.00, 'paid', '支付宝', TIMESTAMP '2024-01-25 11:31:00', '天津市和平区南京路100号', '黄金手链-古法款')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (12, 'ORD202401012', 12, TIMESTAMP '2024-01-26 16:00:00', 38888.00, 'confirmed', '微信支付', NULL, '青岛市市南区香港西路2号', '钻石手链')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (13, 'ORD202401013', 13, TIMESTAMP '2024-01-27 10:15:00', 4888.00, 'pending', NULL, NULL, '大连市中山区人民路30号', '珍珠耳环-经典款')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (14, 'ORD202401014', 14, TIMESTAMP '2024-01-28 13:45:00', 16888.00, 'delivered', '支付宝', TIMESTAMP '2024-01-28 13:46:00', '长沙市天心区黄兴路步行街88号', '钻石耳钉')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (15, 'ORD202401015', 15, TIMESTAMP '2024-01-29 09:00:00', 22888.00, 'shipped', '银行卡', TIMESTAMP '2024-01-29 09:01:00', '合肥市包河区徽州大道100号', '红宝石耳坠')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (16, 'ORD202401016', 16, TIMESTAMP '2024-01-30 15:30:00', 8888.00, 'paid', '微信支付', TIMESTAMP '2024-01-30 15:31:00', '郑州市金水区花园路60号', '黄金胸针-花卉款')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (17, 'ORD202401017', 17, TIMESTAMP '2024-01-31 11:45:00', 28888.00, 'confirmed', '支付宝', NULL, '长沙市岳麓区枫林路88号', '翡翠吊坠')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (18, 'ORD202402001', 18, TIMESTAMP '2024-02-01 14:15:00', 32888.00, 'delivered', '微信支付', TIMESTAMP '2024-02-01 14:16:00', '成都市武侯区科华北路65号', '和田玉吊坠')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (19, 'ORD202402002', 19, TIMESTAMP '2024-02-02 10:30:00', 3888.00, 'cancelled', '支付宝', NULL, '贵阳市南明区花果园大街1号', '玛瑙手链')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (20, 'ORD202402003', 20, TIMESTAMP '2024-02-03 16:20:00', 12888.00, 'delivered', '银行卡', TIMESTAMP '2024-02-03 16:21:00', '昆明市五华区翠湖路88号', '碧玺项链')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (21, 'ORD202402004', 21, TIMESTAMP '2024-02-04 09:30:00', 1588.00, 'pending', NULL, NULL, '南宁市青秀区民族大道100号', '月光石耳环')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (22, 'ORD202402005', 22, TIMESTAMP '2024-02-05 13:00:00', 3888.00, 'paid', '微信支付', TIMESTAMP '2024-02-05 13:01:00', '兰州市城关区中山路50号', '蛋白石项链')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (23, 'ORD202402006', 23, TIMESTAMP '2024-02-06 15:45:00', 18888.00, 'shipped', '支付宝', TIMESTAMP '2024-02-06 15:46:00', '乌鲁木齐市天山区解放路88号', '海蓝宝石戒指')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (24, 'ORD202402007', 24, TIMESTAMP '2024-02-07 10:00:00', 5888.00, 'confirmed', '银行卡', NULL, '哈尔滨市道里区中央大街100号', '橄榄石手链')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (25, 'ORD202402008', 25, TIMESTAMP '2024-02-08 14:30:00', 8888.00, 'delivered', '微信支付', TIMESTAMP '2024-02-08 14:31:00', '济南市历下区泉城路88号', '黄玉吊坠')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (26, 'ORD202402009', 26, TIMESTAMP '2024-02-09 11:15:00', 6888.00, 'paid', '支付宝', TIMESTAMP '2024-02-09 11:16:00', '石家庄市长安区中山路100号', '红纹石胸针')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (27, 'ORD202402010', 27, TIMESTAMP '2024-02-10 16:00:00', 15888.00, 'shipped', '微信支付', TIMESTAMP '2024-02-10 16:01:00', '福州市鼓楼区五四路88号', '南红玛瑙戒指')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (28, 'ORD202402011', 28, TIMESTAMP '2024-02-11 09:45:00', 68888.00, 'confirmed', '银行卡', NULL, '合肥市蜀山区怀宁路60号', '冰种翡翠手镯+钻石耳环')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (29, 'ORD202402012', 29, TIMESTAMP '2024-02-12 13:30:00', 2888.00, 'pending', NULL, NULL, '南昌市西湖区中山路100号', '黑曜石手链')
INTO orders (order_id, order_no, customer_id, order_date, total_amount, status, payment_method, payment_date, shipping_addr, notes) VALUES (30, 'ORD202402013', 30, TIMESTAMP '2024-02-13 15:00:00', 58888.00, 'paid', '支付宝', TIMESTAMP '2024-02-13 15:01:00', '昆明市盘龙区北京路88号', '高冰钻石项链')
SELECT * FROM dual;
-- ============================================================
-- 6. 订单明细数据
-- ============================================================
INSERT ALL
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (1, 1, 1, 1, 28888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (2, 2, 2, 1, 12888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (3, 3, 3, 1, 6888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (4, 4, 4, 1, 18888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (5, 5, 5, 1, 22888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (6, 6, 6, 1, 35888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (7, 7, 11, 1, 8888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (8, 8, 9, 1, 32888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (9, 9, 10, 1, 5888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (10, 10, 13, 1, 15888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (11, 11, 14, 1, 18888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (12, 12, 15, 1, 38888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (13, 13, 17, 1, 4888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (14, 14, 18, 1, 16888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (15, 15, 19, 1, 22888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (16, 16, 21, 1, 8888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (17, 17, 23, 1, 28888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (18, 18, 24, 1, 32888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (19, 19, 25, 1, 3888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (20, 20, 30, 1, 12888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (21, 21, 31, 1, 1588.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (22, 22, 32, 1, 3888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (23, 23, 33, 1, 18888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (24, 24, 34, 1, 5888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (25, 25, 35, 1, 8888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (26, 26, 36, 1, 6888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (27, 27, 38, 1, 15888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (28, 28, 39, 1, 68888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (29, 29, 37, 1, 2888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (30, 30, 40, 1, 58888.00)
INTO order_items (item_id, order_id, product_id, quantity, unit_price) VALUES (31, 28, 49, 1, 25888.00)
SELECT * FROM dual;
-- ============================================================
-- 7. 库存日志数据
-- ============================================================
INSERT ALL
INTO inventory_log (log_id, product_id, change_type, change_qty, before_qty, after_qty, order_id, reference_no, operator) VALUES (1, 1, 'out', 1, 16, 15, 1, 'ORD202401001', 'system')
INTO inventory_log (log_id, product_id, change_type, change_qty, before_qty, after_qty, order_id, reference_no, operator) VALUES (2, 2, 'out', 1, 26, 25, 2, 'ORD202401002', 'system')
INTO inventory_log (log_id, product_id, change_type, change_qty, before_qty, after_qty, order_id, reference_no, operator) VALUES (3, 3, 'out', 1, 31, 30, 3, 'ORD202401003', 'system')
INTO inventory_log (log_id, product_id, change_type, change_qty, before_qty, after_qty, order_id, reference_no, operator) VALUES (4, 4, 'out', 1, 11, 10, 4, 'ORD202401004', 'system')
INTO inventory_log (log_id, product_id, change_type, change_qty, before_qty, after_qty, order_id, reference_no, operator) VALUES (5, 5, 'out', 1, 9, 8, 5, 'ORD202401005', 'system')
INTO inventory_log (log_id, product_id, change_type, change_qty, before_qty, after_qty, order_id, reference_no, operator) VALUES (6, 6, 'out', 1, 6, 5, 6, 'ORD202401006', 'system')
INTO inventory_log (log_id, product_id, change_type, change_qty, before_qty, after_qty, order_id, reference_no, operator) VALUES (7, 11, 'out', 1, 9, 8, 7, 'ORD202401007', 'system')
INTO inventory_log (log_id, product_id, change_type, change_qty, before_qty, after_qty, order_id, reference_no, operator) VALUES (8, 9, 'out', 1, 13, 12, 8, 'ORD202401008', 'system')
INTO inventory_log (log_id, product_id, change_type, change_qty, before_qty, after_qty, order_id, reference_no, operator) VALUES (9, 10, 'out', 1, 11, 10, 9, 'ORD202401009', 'system')
INTO inventory_log (log_id, product_id, change_type, change_qty, before_qty, after_qty, order_id, reference_no, operator) VALUES (10, 13, 'out', 1, 19, 18, 10, 'ORD202401010', 'system')
SELECT * FROM dual;
-- ============================================================
-- 8. 死锁演示数据
-- ============================================================
INSERT ALL
INTO deadlock_demo (id, resource_type, resource_id, balance, version) VALUES (1, 'inventory', 1, 15.00, 0)
INTO deadlock_demo (id, resource_type, resource_id, balance, version) VALUES (2, 'inventory', 2, 25.00, 0)
INTO deadlock_demo (id, resource_type, resource_id, balance, version) VALUES (3, 'account', 1, 158888.00, 0)
INTO deadlock_demo (id, resource_type, resource_id, balance, version) VALUES (4, 'account', 2, 88888.00, 0)
SELECT * FROM dual;
-- ============================================================
-- 9. Saga事务日志初始数据
-- ============================================================
INSERT ALL
INTO saga_log (saga_id, step_name, step_order, status, request_data, response_data, error_message) VALUES ('SAGA-ORDER-CREATE-001', 'create_order', 1, 'completed', '{"customer_id":1,"product_id":1,"quantity":1}', '{"order_id":1,"order_no":"ORD-SAGA-001"}', NULL)
INTO saga_log (saga_id, step_name, step_order, status, request_data, response_data, error_message) VALUES ('SAGA-ORDER-CREATE-001', 'reserve_inventory', 2, 'completed', '{"product_id":1,"quantity":1,"order_no":"ORD-SAGA-001"}', '{"reserved":true,"product_id":1}', NULL)
INTO saga_log (saga_id, step_name, step_order, status, request_data, response_data, error_message) VALUES ('SAGA-ORDER-CREATE-001', 'deduct_balance', 3, 'completed', '{"customer_id":1,"amount":28888.00,"order_no":"ORD-SAGA-001"}', '{"deducted":true,"balance":130000.00}', NULL)
INTO saga_log (saga_id, step_name, step_order, status, request_data, response_data, error_message) VALUES ('SAGA-ORDER-CREATE-001', 'confirm_order', 4, 'completed', '{"order_id":1,"order_no":"ORD-SAGA-001"}', '{"confirmed":true}', NULL)
INTO saga_log (saga_id, step_name, step_order, status, request_data, response_data, error_message) VALUES ('SAGA-ORDER-CANCEL-001', 'cancel_order', 1, 'completed', '{"order_id":1,"order_no":"ORD-SAGA-001"}', '{"cancelled":true}', NULL)
INTO saga_log (saga_id, step_name, step_order, status, request_data, response_data, error_message) VALUES ('SAGA-ORDER-CANCEL-001', 'refund_balance', 2, 'completed', '{"customer_id":1,"amount":28888.00,"order_no":"ORD-SAGA-001"}', '{"refunded":true,"balance":158888.00}', NULL)
INTO saga_log (saga_id, step_name, step_order, status, request_data, response_data, error_message) VALUES ('SAGA-ORDER-CANCEL-001', 'release_inventory', 3, 'completed', '{"product_id":1,"quantity":1,"order_no":"ORD-SAGA-001"}', '{"released":true}', NULL)
SELECT * FROM dual;
-- ============================================================
-- 10. Saga编排器数据
-- ============================================================
INSERT INTO saga_orchestrator (saga_id, saga_type, current_step, total_steps, saga_status) VALUES ('SAGA-ORDER-CREATE-001', 'order_creation', 4, 4, 'completed');
INSERT INTO saga_orchestrator (saga_id, saga_type, current_step, total_steps, saga_status) VALUES ('SAGA-ORDER-CANCEL-001', 'order_cancellation', 3, 3, 'completed');
-- ============================================================
-- 验证数据插入
-- ============================================================
SELECT 'categories' AS table_name, COUNT(*) AS row_count FROM categories
UNION ALL
SELECT 'products', COUNT(*) FROM products
UNION ALL
SELECT 'customers', COUNT(*) FROM customers
UNION ALL
SELECT 'orders', COUNT(*) FROM orders
UNION ALL
SELECT 'order_items', COUNT(*) FROM order_items
UNION ALL
SELECT 'account_ledger', COUNT(*) FROM account_ledger
UNION ALL
SELECT 'inventory_log', COUNT(*) FROM inventory_log
UNION ALL
SELECT 'saga_log', COUNT(*) FROM saga_log
UNION ALL
SELECT 'saga_orchestrator', COUNT(*) FROM saga_orchestrator
UNION ALL
SELECT 'deadlock_demo', COUNT(*) FROM deadlock_demo;
-- ============================================================
-- 模块: oracle21c_acid_isolation.sql
-- ============================================================
-- ============================================================
-- Oracle 21c 珠宝行业 - ACID事务控制与隔离级别演示
-- Transaction & Concurrency Patterns - ACID & Isolation Levels
-- ============================================================
-- ============================================================
-- 1. ACID属性验证 - 原子性(Automicity)
-- ============================================================
-- 场景:珠宝订单创建必须同时完成:下单+扣库存+扣余额,任一失败全部回滚
-- 创建测试表
CREATE TABLE acid_test_orders (
test_id NUMBER PRIMARY KEY,
order_no VARCHAR2(30) NOT NULL,
customer_id NUMBER NOT NULL,
product_id NUMBER NOT NULL,
quantity NUMBER NOT NULL,
amount DECIMAL(12,2) NOT NULL,
status VARCHAR2(20) DEFAULT 'pending' NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
COMMENT ON TABLE acid_test_orders IS 'ACID测试订单表';
-- 演示原子性:事务中多个操作要么全成功要么全失败
DECLARE
v_order_id NUMBER;
v_stock NUMBER;
v_balance NUMBER;
v_product_id NUMBER := 1; -- 钻石戒指
v_quantity NUMBER := 2;
v_amount NUMBER := 57776.00; -- 28888 * 2
v_customer_id NUMBER := 1;
BEGIN
-- 开始事务(Oracle默认自动提交关闭时才需要显式BEGIN TRANSACTION)
-- 步骤1:检查库存
SELECT stock_qty INTO v_stock FROM products WHERE product_id = v_product_id FOR UPDATE;
IF v_stock < v_quantity THEN
RAISE_APPLICATION_ERROR(-20001, '库存不足,无法完成订单');
END IF;
-- 步骤2:检查余额
SELECT balance INTO v_balance FROM account_ledger WHERE customer_id = v_customer_id FOR UPDATE;
IF v_balance < v_amount THEN
RAISE_APPLICATION_ERROR(-20002, '余额不足,无法完成支付');
END IF;
-- 步骤3:创建订单
INSERT INTO acid_test_orders (test_id, order_no, customer_id, product_id, quantity, amount, status)
VALUES (SEQ_ORDERS.NEXTVAL, 'ACID-TEST-001', v_customer_id, v_product_id, v_quantity, v_amount, 'confirmed');
-- 步骤4:扣除库存
UPDATE products SET stock_qty = stock_qty - v_quantity WHERE product_id = v_product_id;
-- 步骤5:扣除余额
UPDATE account_ledger SET balance = balance - v_amount WHERE customer_id = v_customer_id;
-- 全部成功,提交事务
COMMIT;
DBMS_OUTPUT.PUT_LINE('原子性测试通过:订单创建、库存扣减、余额扣减全部成功提交');
EXCEPTION
WHEN OTHERS THEN
-- 任何步骤失败,回滚所有变更
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('原子性测试:事务回滚 - ' || SQLERRM);
-- 记录失败日志
INSERT INTO acid_test_orders (test_id, order_no, customer_id, product_id, quantity, amount, status)
VALUES (SEQ_ORDERS.NEXTVAL, 'ACID-TEST-001-FAIL', v_customer_id, v_product_id, v_quantity, v_amount, 'failed');
COMMIT;
END;
/
-- ============================================================
-- 2. 一致性(Consistency)验证
-- ============================================================
-- 场景:确保库存扣减前后一致性约束不被破坏
-- 创建一致性检查视图
CREATE OR REPLACE VIEW v_inventory_consistency AS
SELECT
p.product_id,
p.product_name,
p.stock_qty AS current_stock,
COALESCE(SUM(CASE WHEN oi.item_id IS NOT NULL THEN oi.quantity ELSE 0 END), 0) AS ordered_qty,
CASE WHEN p.stock_qty < 0 THEN 'INCONSISTENT' ELSE 'CONSISTENT' END AS consistency_status
FROM products p
LEFT JOIN order_items oi ON p.product_id = oi.product_id
GROUP BY p.product_id, p.product_name, p.stock_qty;
COMMENT ON VIEW v_inventory_consistency IS '库存一致性检查视图';
-- 一致性验证存储过程
CREATE OR REPLACE PROCEDURE sp_verify_consistency AS
v_inconsistent_count NUMBER;
BEGIN
SELECT COUNT(*) INTO v_inconsistent_count
FROM products
WHERE stock_qty < 0;
IF v_inconsistent_count > 0 THEN
RAISE_APPLICATION_ERROR(-20003, '库存一致性检查失败:存在负库存产品(' || v_inconsistent_count || '个)');
ELSE
DBMS_OUTPUT.PUT_LINE('一致性验证通过:所有产品库存 >= 0');
END IF;
-- 检查订单金额一致性
DECLARE
v_order_total DECIMAL(12,2);
v_items_total DECIMAL(12,2);
BEGIN
SELECT total_amount INTO v_order_total FROM orders WHERE order_id = 1;
SELECT COALESCE(SUM(subtotal), 0) INTO v_items_total FROM order_items WHERE order_id = 1;
IF ABS(v_order_total - v_items_total) > 0.01 THEN
RAISE_APPLICATION_ERROR(-20004, '订单金额一致性失败:订单总额(' || v_order_total || ') != 明细合计(' || v_items_total || ')');
ELSE
DBMS_OUTPUT.PUT_LINE('订单金额一致性验证通过:订单1的总额与明细合计一致');
END IF;
END;
END;
/
-- 执行一致性验证
EXEC sp_verify_consistency;
-- ============================================================
-- 3. 隔离级别演示
-- ============================================================
-- Oracle支持的隔离级别:
-- READ COMMITTED (默认) - 读取已提交的数据
-- SERIALIZABLE - 可串行化隔离
-- READ ONLY - 只读事务
-- 3.1 演示 READ COMMITTED(Oracle默认隔离级别)
-- 会话A:开始事务并更新数据但不提交
-- 会话B:可以看到会话A未提交的数据吗?(READ COMMITTED下看不到)
-- 模拟会话A的操作(这里用保存点模拟)
DECLARE
v_old_balance NUMBER;
BEGIN
-- 会话A:读取当前余额
SELECT balance INTO v_old_balance FROM account_ledger WHERE customer_id = 1;
DBMS_OUTPUT.PUT_LINE('会话A - 读取余额: ' || v_old_balance);
-- 会话A:更新余额(但未提交)
UPDATE account_ledger SET balance = balance - 1000 WHERE customer_id = 1;
-- 在Oracle中,其他会话在此时看不到未提交的更改(READ COMMITTED)
-- 这里模拟会话B的读取(在同一会话中用子程序模拟)
DECLARE
v_read_balance NUMBER;
BEGIN
-- 会话B:读取余额(应该还是旧值,因为会话A未提交)
SELECT balance INTO v_read_balance FROM account_ledger WHERE customer_id = 1;
DBMS_OUTPUT.PUT_LINE('会话B - 读取余额(会话A未提交): ' || v_read_balance);
DBMS_OUTPUT.PUT_LINE('READ COMMITTED验证:会话B看到的是提交前的旧值 = 一致性读取');
END;
-- 会话A:提交
COMMIT;
DBMS_OUTPUT.PUT_LINE('会话A - 提交事务');
-- 会话B:再次读取(现在能看到新值)
DECLARE
v_read_balance2 NUMBER;
BEGIN
SELECT balance INTO v_read_balance2 FROM account_ledger WHERE customer_id = 1;
DBMS_OUTPUT.PUT_LINE('会话B - 读取余额(会话A已提交): ' || v_read_balance2);
DBMS_OUTPUT.PUT_LINE('READ COMMITTED验证:会话B现在能看到会话A提交的新值');
END;
-- 恢复余额
UPDATE account_ledger SET balance = balance + 1000 WHERE customer_id = 1;
COMMIT;
END;
/
-- 3.2 演示 SERIALIZABLE 隔离级别
-- 在Oracle中使用 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
-- 创建序列化测试表
CREATE TABLE serializable_test (
id NUMBER PRIMARY KEY,
value NUMBER NOT NULL,
version NUMBER DEFAULT 1 NOT NULL
);
INSERT INTO serializable_test (id, value) VALUES (1, 100);
-- 演示可串行化隔离(模拟两个并发事务)
DECLARE
v_val NUMBER;
BEGIN
-- 事务A:设置可串行化隔离级别
-- 在Oracle中:EXECUTE IMMEDIATE 'SET TRANSACTION ISOLATION LEVEL SERIALIZABLE';
DBMS_OUTPUT.PUT_LINE('事务A - 设置为SERIALIZABLE隔离级别');
-- 事务A:读取数据
SELECT value INTO v_val FROM serializable_test WHERE id = 1;
DBMS_OUTPUT.PUT_LINE('事务A - 读取value: ' || v_val);
-- 事务B:在同一会话中模拟(实际中是另一个会话)
-- 事务B:更新并提交
UPDATE serializable_test SET value = value + 50 WHERE id = 1;
COMMIT;
DBMS_OUTPUT.PUT_LINE('事务B - 更新value += 50并提交');
-- 事务A:再次读取(SERIALIZABLE下看到的是事务开始时的快照)
SELECT value INTO v_val FROM serializable_test WHERE id = 1;
DBMS_OUTPUT.PUT_LINE('事务A - 再次读取value: ' || v_val || ' (快照读,看不到事务B的提交)');
-- 事务A:尝试更新(可能会检测到冲突)
BEGIN
UPDATE serializable_test SET value = value + 10 WHERE id = 1;
COMMIT;
DBMS_OUTPUT.PUT_LINE('事务A - 更新并提交成功');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('事务A - 检测到序列化冲突: ' || SQLERRM);
ROLLBACK;
END;
-- 清理测试数据
DELETE FROM serializable_test WHERE id = 1;
COMMIT;
END;
/
-- 3.3 演示读已提交快照(Read Committed Snapshot)- Oracle 11g+特性
-- Oracle的MVCC实现:每个SQL语句看到的都是最近一次提交的数据快照
-- 演示MVCC快照读
DECLARE
v_snapshot_before NUMBER;
v_snapshot_after NUMBER;
BEGIN
-- 读取当前快照版本
SELECT balance INTO v_snapshot_before FROM account_ledger WHERE customer_id = 1;
-- 模拟其他会话的更新和提交(这里直接更新并提交)
UPDATE account_ledger SET balance = balance - 500 WHERE customer_id = 1;
COMMIT;
-- 新的SQL语句会看到新的提交快照
SELECT balance INTO v_snapshot_after FROM account_ledger WHERE customer_id = 1;
DBMS_OUTPUT.PUT_LINE('MVCC快照读验证:');
DBMS_OUTPUT.PUT_LINE(' 第一次读取: ' || v_snapshot_before);
DBMS_OUTPUT.PUT_LINE(' 第二次读取: ' || v_snapshot_after);
DBMS_OUTPUT.PUT_LINE(' Oracle Read Committed下,每个语句看到最近的提交快照');
-- 恢复
UPDATE account_ledger SET balance = balance + 500 WHERE customer_id = 1;
COMMIT;
END;
/
-- ============================================================
-- 4. 事务嵌套与保存点(Savepoint)演示
-- ============================================================
-- Oracle支持保存点,允许部分回滚
-- 创建保存点测试表
CREATE TABLE savepoint_test (
test_id NUMBER PRIMARY KEY,
account VARCHAR2(20) NOT NULL,
balance DECIMAL(12,2) NOT NULL,
description VARCHAR2(100)
);
-- 演示保存点机制
DECLARE
v_balance_before NUMBER;
BEGIN
-- 获取初始余额
SELECT balance INTO v_balance_before FROM account_ledger WHERE customer_id = 1;
-- 插入测试记录
INSERT INTO savepoint_test (test_id, account, balance, description)
VALUES (1, 'account_A', v_balance_before, '初始状态');
-- 保存点A
SAVEPOINT savepoint_a;
-- 模拟转账:从账户1转5000到账户2
UPDATE account_ledger SET balance = balance - 5000 WHERE customer_id = 1;
INSERT INTO savepoint_test (test_id, account, balance, description)
VALUES (2, 'account_A', v_balance_before - 5000, '转出5000后');
-- 保存点B
SAVEPOINT savepoint_b;
-- 模拟转账:给账户2加5000
UPDATE account_ledger SET balance = balance + 5000 WHERE customer_id = 2;
INSERT INTO savepoint_test (test_id, account, balance, description)
VALUES (3, 'account_B', (SELECT balance FROM account_ledger WHERE customer_id = 2), '转入5000后');
-- 模拟发现错误:账户2余额异常,回滚到保存点B之前
-- ROLLBACK TO savepoint_b; -- 注释掉,这里演示成功路径
-- 全部成功,提交
COMMIT;
DBMS_OUTPUT.PUT_LINE('保存点测试:事务全部步骤成功提交');
DBMS_OUTPUT.PUT_LINE(' 账户1初始余额: ' || v_balance_before);
-- 验证
DECLARE
v_a_balance NUMBER;
v_b_balance NUMBER;
BEGIN
SELECT balance INTO v_a_balance FROM account_ledger WHERE customer_id = 1;
SELECT balance INTO v_b_balance FROM account_ledger WHERE customer_id = 2;
DBMS_OUTPUT.PUT_LINE(' 账户1当前余额: ' || v_a_balance);
DBMS_OUTPUT.PUT_LINE(' 账户2当前余额: ' || v_b_balance);
END;
-- 清理测试数据
DELETE FROM savepoint_test;
COMMIT;
END;
/
-- ============================================================
-- 5. XACT_ABORT等效 - Oracle中的错误处理与事务控制
-- ============================================================
-- Oracle没有XACT_ABORT设置,但可以通过异常处理实现类似效果
-- 创建错误处理测试表
CREATE TABLE error_handling_test (
test_id NUMBER PRIMARY KEY,
operation VARCHAR2(50) NOT NULL,
status VARCHAR2(20) NOT NULL,
error_msg VARCHAR2(500),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
-- 模拟XACT_ABORT ON行为:任何错误立即终止事务
CREATE OR REPLACE PROCEDURE sp_transfer_with_error_handling (
p_from_id IN NUMBER,
p_to_id IN NUMBER,
p_amount IN DECIMAL
) AS
v_from_balance NUMBER;
v_to_balance NUMBER;
BEGIN
-- 检查源账户余额
SELECT balance INTO v_from_balance FROM account_ledger WHERE customer_id = p_from_id FOR UPDATE;
IF v_from_balance < p_amount THEN
RAISE_APPLICATION_ERROR(-20010, '源账户余额不足');
END IF;
-- 检查目标账户是否存在
SELECT balance INTO v_to_balance FROM account_ledger WHERE customer_id = p_to_id;
-- 执行转账
UPDATE account_ledger SET balance = balance - p_amount WHERE customer_id = p_from_id;
UPDATE account_ledger SET balance = balance + p_amount WHERE customer_id = p_to_id;
-- 记录操作日志
INSERT INTO error_handling_test (test_id, operation, status, error_msg)
VALUES (SEQ_ORDERS.NEXTVAL, 'TRANSFER_' || p_from_id || '_TO_' || p_to_id, 'SUCCESS', NULL);
COMMIT;
DBMS_OUTPUT.PUT_LINE('转账成功:账户' || p_from_id || ' -> 账户' || p_to_id || ',金额:' || p_amount);
EXCEPTION
WHEN OTHERS THEN
-- 模拟XACT_ABORT:任何错误立即回滚
ROLLBACK;
INSERT INTO error_handling_test (test_id, operation, status, error_msg)
VALUES (SEQ_ORDERS.NEXTVAL, 'TRANSFER_' || p_from_id || '_TO_' || p_to_id, 'FAILED', SQLERRM);
COMMIT;
RAISE; -- 重新抛出异常
END;
/
-- 测试正常转账
EXEC sp_transfer_with_error_handling(1, 2, 1000);
-- 测试余额不足(应该失败并回滚)
BEGIN
sp_transfer_with_error_handling(5, 2, 999999999);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('预期错误(余额不足):' || SQLERRM);
END;
/
-- 验证转账结果
SELECT customer_id, balance FROM account_ledger WHERE customer_id IN (1, 2, 5);
-- 查看错误处理日志
SELECT * FROM error_handling_test ORDER BY created_at DESC;
-- ============================================================
-- 6. 两阶段提交原理演示
-- ============================================================
-- Oracle支持XA分布式事务
-- 演示两阶段提交(在同一数据库内模拟)
-- 阶段1:准备阶段(PREPARE)
-- 阶段2:提交阶段(COMMIT)或回滚阶段(ROLLBACK)
CREATE TABLE xa_test_table (
id NUMBER PRIMARY KEY,
data_val VARCHAR2(50) NOT NULL,
xa_status VARCHAR2(20) DEFAULT 'prepared' NOT NULL
);
-- 模拟两阶段提交流程
DECLARE
v_xa_id VARCHAR2(64) := 'XA-TRANSACTION-' || TO_CHAR(SYSTIMESTAMP, 'YYYYMMDDHH24MISSFF');
BEGIN
DBMS_OUTPUT.PUT_LINE('=== 两阶段提交演示 ===');
DBMS_OUTPUT.PUT_LINE('XA事务ID: ' || v_xa_id);
-- 阶段1:准备阶段
DBMS_OUTPUT.PUT_LINE('【阶段1】准备阶段:检查所有资源可用性...');
-- 模拟资源1检查
INSERT INTO xa_test_table (id, data_val, xa_status) VALUES (1, 'resource_A_data', 'prepared');
-- 模拟资源2检查
INSERT INTO xa_test_table (id, data_val, xa_status) VALUES (2, 'resource_B_data', 'prepared');
-- 所有资源准备就绪
DBMS_OUTPUT.PUT_LINE('【阶段1】所有资源准备就绪');
-- 阶段2:提交阶段
DBMS_OUTPUT.PUT_LINE('【阶段2】提交阶段:永久提交所有变更...');
UPDATE xa_test_table SET xa_status = 'committed';
COMMIT;
DBMS_OUTPUT.PUT_LINE('【阶段2】两阶段提交完成');
-- 验证结果
DBMS_OUTPUT.PUT_LINE('验证结果:');
FOR rec IN (SELECT id, data_val, xa_status FROM xa_test_table) LOOP
DBMS_OUTPUT.PUT_LINE(' 记录' || rec.id || ': ' || rec.data_val || ' - ' || rec.xa_status);
END LOOP;
-- 清理
DELETE FROM xa_test_table;
COMMIT;
END;
/
-- ============================================================
-- 7. 隔离级别查询视图
-- ============================================================
-- 查看当前会话的隔离级别相关信息
-- Oracle隔离级别视图
CREATE OR REPLACE VIEW v_transaction_info AS
SELECT
s.sid,
s.serial#,
s.username,
s.status,
t.start_time,
t.startscn,
t.log_io_write AS bytes_written,
t.cr_get AS block_gets,
t.cr_change AS consistent_gets
FROM v$session s
JOIN v$transaction t ON s.taddr = t.addr
WHERE s.type = 'USER' AND s.username IS NOT NULL;
COMMENT ON VIEW v_transaction_info IS '当前活跃事务信息视图';
-- 查看锁等待信息
CREATE OR REPLACE VIEW v_lock_waits AS
SELECT
ws.sid AS waiting_sid,
ws.type AS lock_type,
ws.id1,
ws.id2,
ws.lmode AS waiting_mode,
ws.request AS requested_mode,
rs.sid AS holding_sid,
rs.type AS holding_type,
rs.lmode AS holding_mode
FROM v$lock ws
JOIN v$lock rs ON ws.id1 = rs.id1 AND ws.id2 = rs.id2
AND ws.sid <> rs.sid
AND ws.request > 0
AND rs.lmode <> 4; -- 排除null锁
COMMENT ON VIEW v_lock_waits IS '锁等待信息视图';
-- 查看当前数据库的隔离级别设置
-- Oracle默认是READ COMMITTED
-- 可通过 SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 设置会话级隔离级别
-- 验证所有数据完整性
SELECT 'ACID测试订单' AS check_item, COUNT(*) AS count FROM acid_test_orders
UNION ALL SELECT '保存点测试', COUNT(*) FROM savepoint_test
UNION ALL SELECT '错误处理测试', COUNT(*) FROM error_handling_test
UNION ALL SELECT '库存一致性检查', COUNT(*) FROM products WHERE stock_qty < 0;
-- 清理测试表(可选)
-- DROP TABLE acid_test_orders;
-- DROP TABLE savepoint_test;
-- DROP TABLE error_handling_test;
-- DROP TABLE serializable_test;
-- DROP TABLE xa_test_table;
-- DROP VIEW v_inventory_consistency;
-- DROP VIEW v_transaction_info;
-- DROP VIEW v_lock_waits;
-- DROP PROCEDURE sp_verify_consistency;
-- DROP PROCEDURE sp_transfer_with_error_handling;
-- ============================================================
-- 模块: oracle21c_locking.sql
-- ============================================================
-- ============================================================
-- Oracle 21c 珠宝行业 - 锁机制演示
-- Transaction & Concurrency Patterns - Locking Mechanisms
-- ============================================================
-- ============================================================
-- 1. 悲观锁 - FOR UPDATE(行级排他锁)
-- ============================================================
-- 场景:订单创建时锁定库存行,防止并发超卖
-- 1.1 基础FOR UPDATE - 阻塞等待锁
-- 模拟会话A:锁定库存行
-- BEGIN
-- SELECT stock_qty INTO v_stock FROM products WHERE product_id = 1 FOR UPDATE;
-- -- 执行业务逻辑...
-- COMMIT;
-- END;
-- /
-- 模拟FOR UPDATE的完整业务流程
DECLARE
v_stock NUMBER;
v_price DECIMAL(12,2);
v_product_id NUMBER := 1;
v_quantity NUMBER := 2;
BEGIN
-- 会话A:使用FOR UPDATE锁定特定行(其他会话的FOR UPDATE会等待)
SELECT stock_qty, price INTO v_stock, v_price
FROM products WHERE product_id = v_product_id FOR UPDATE;
DBMS_OUTPUT.PUT_LINE('会话A - 已锁定产品ID=' || v_product_id || ',库存=' || v_stock);
-- 检查库存是否充足
IF v_stock < v_quantity THEN
RAISE_APPLICATION_ERROR(-20020, '库存不足:需要' || v_quantity || ',现有' || v_stock);
END IF;
-- 执行扣减
UPDATE products SET stock_qty = stock_qty - v_quantity
WHERE product_id = v_product_id;
DBMS_OUTPUT.PUT_LINE('会话A - 库存扣减成功:' || v_stock || ' -> ' || (v_stock - v_quantity));
COMMIT;
DBMS_OUTPUT.PUT_LINE('会话A - 事务提交,锁释放');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
DBMS_OUTPUT.PUT_LINE('会话A - 事务回滚:' || SQLERRM);
END;
/
-- 1.2 FOR UPDATE NOWAIT - 不等待,立即返回错误
-- 适用于需要快速失败的场景
-- 模拟NOWAIT行为
DECLARE
v_stock NUMBER;
BEGIN
-- 尝试立即获取锁,如果不可用立即报错
BEGIN
SELECT stock_qty INTO v_stock
FROM products WHERE product_id = 2 FOR UPDATE NOWAIT;
DBMS_OUTPUT.PUT_LINE('获取锁成功,库存=' || v_stock);
-- 执行业务逻辑
UPDATE products SET stock_qty = stock_qty - 1 WHERE product_id = 2;
COMMIT;
DBMS_OUTPUT.PUT_LINE('更新成功并提交');
EXCEPTION
WHEN OTHERS THEN
-- SQLCODE = 54 表示锁超时/不可用
IF SQLCODE = -54 THEN
DBMS_OUTPUT.PUT_LINE('NOWAIT模式:无法立即获取锁,其他会话正在访问该行');
DBMS_OUTPUT.PUT_LINE('建议:稍后重试或使用SKIP LOCKED');
ELSE
RAISE;
END IF;
END;
END;
/
-- 1.3 FOR UPDATE WAIT n - 等待指定秒数后超时
-- 适用于允许短暂等待的场景
-- 模拟WAIT行为
DECLARE
v_stock NUMBER;
BEGIN
BEGIN
-- 等待最多5秒获取锁
SELECT stock_qty INTO v_stock
FROM products WHERE product_id = 3 FOR UPDATE WAIT 5;
DBMS_OUTPUT.PUT_LINE('等待后获取锁成功,库存=' || v_stock);
UPDATE products SET stock_qty = stock_qty - 1 WHERE product_id = 3;
COMMIT;
DBMS_OUTPUT.PUT_LINE('更新成功并提交');
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE = -54 THEN
DBMS_OUTPUT.PUT_LINE('WAIT模式:等待5秒后仍未获取锁,超时');
ELSE
RAISE;
END IF;
END;
END;
/
-- 1.4 FOR UPDATE SKIP LOCKED - 跳过已被锁定的行
-- 适用于消息队列/任务队列场景
-- 创建任务队列表
CREATE TABLE task_queue (
task_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
task_type VARCHAR2(50) NOT NULL,
task_data CLOB,
status VARCHAR2(20) DEFAULT 'pending' NOT NULL,
worker_id VARCHAR2(50) DEFAULT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
CONSTRAINT chk_task_status CHECK (status IN ('pending','processing','completed','failed'))
);
COMMENT ON TABLE task_queue IS '任务队列表(演示SKIP LOCKED)';
-- 插入测试任务
INSERT ALL
INTO task_queue (task_type, task_data) VALUES ('inventory_sync', '{"product_id":1,"action":"sync"}')
INTO task_queue (task_type, task_data) VALUES ('price_update', '{"product_id":2,"action":"update_price"}')
INTO task_queue (task_type, task_data) VALUES ('order_confirm', '{"order_id":5,"action":"confirm"}')
INTO task_queue (task_type, task_data) VALUES ('stock_check', '{"product_id":3,"action":"check_stock"}')
INTO task_queue (task_type, task_data) VALUES ('report_gen', '{"report_type":"daily_sales"}')
SELECT * FROM dual;
-- 模拟多个worker并发处理任务(使用SKIP LOCKED)
-- Worker1 处理第一个可用任务
DECLARE
v_task_id NUMBER;
v_task_type VARCHAR2(50);
v_task_data CLOB;
BEGIN
-- 获取一个未被锁定的待处理任务
SELECT task_id, task_type, task_data
INTO v_task_id, v_task_type, v_task_data
FROM task_queue
WHERE status = 'pending'
FOR UPDATE SKIP LOCKED
FETCH FIRST 1 ROWS ONLY;
IF v_task_id IS NOT NULL THEN
-- 标记为处理中
UPDATE task_queue SET status = 'processing', worker_id = 'worker1'
WHERE task_id = v_task_id;
DBMS_OUTPUT.PUT_LINE('Worker1 - 获取任务:ID=' || v_task_id || ',类型=' || v_task_type);
-- 模拟处理
DBMS_OUTPUT.PUT_LINE('Worker1 - 处理任务数据:' || v_task_data);
-- 标记为完成
UPDATE task_queue SET status = 'completed', worker_id = NULL
WHERE task_id = v_task_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Worker1 - 任务完成');
ELSE
DBMS_OUTPUT.PUT_LINE('Worker1 - 无可用任务');
END IF;
END;
/
-- Worker2 获取下一个任务(不会被Worker1锁定的行阻塞)
DECLARE
v_task_id NUMBER;
v_task_type VARCHAR2(50);
v_task_data CLOB;
BEGIN
SELECT task_id, task_type, task_data
INTO v_task_id, v_task_type, v_task_data
FROM task_queue
WHERE status = 'pending'
FOR UPDATE SKIP LOCKED
FETCH FIRST 1 ROWS ONLY;
IF v_task_id IS NOT NULL THEN
UPDATE task_queue SET status = 'processing', worker_id = 'worker2'
WHERE task_id = v_task_id;
DBMS_OUTPUT.PUT_LINE('Worker2 - 获取任务:ID=' || v_task_id || ',类型=' || v_task_type);
UPDATE task_queue SET status = 'completed', worker_id = NULL
WHERE task_id = v_task_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Worker2 - 任务完成');
ELSE
DBMS_OUTPUT.PUT_LINE('Worker2 - 无可用任务');
END IF;
END;
/
-- 查看任务队列状态
SELECT task_id, task_type, status, worker_id, created_at FROM task_queue ORDER BY task_id;
-- 清理测试表
DROP TABLE task_queue;
-- ============================================================
-- 2. 共享锁 - LOCK IN SHARE MODE等效(ORACLE中的SELECT ... FOR SHARE)
-- ============================================================
-- Oracle中没有LOCK IN SHARE MODE,但可以通过以下方式实现共享锁:
-- 1. 使用SELECT ... FOR UPDATE(然后不修改,释放锁)
-- 2. 使用DBMS_LOCK.REQUEST获取共享模式的应用程序锁
-- 模拟共享锁场景:读取珠宝价格信息时防止被并发修改
-- 在Oracle中,READ COMMITTED隔离级别下,SELECT不会阻塞其他SELECT
-- 但会等待其他事务的DML提交/回滚
-- 演示共享读取(不锁定)
DECLARE
v_price DECIMAL(12,2);
BEGIN
-- 普通SELECT(共享读取,不阻塞其他读取)
SELECT price INTO v_price FROM products WHERE product_id = 1;
DBMS_OUTPUT.PUT_LINE('共享读取 - 产品1价格:' || v_price);
-- 在Oracle中,如果要确保读取期间数据不被修改,
-- 需要使用SELECT ... FOR UPDATE(排他锁)或设置SERIALIZABLE隔离级别
END;
/
-- ============================================================
-- 3. 表级锁 - 模拟TABLOCKX(排他表锁)
-- ============================================================
-- Oracle中的DDL操作会自动获取表级锁
-- 可以使用DBMS_LOCK包获取应用程序级别的表锁
-- 模拟表级排他锁场景:批量更新库存前锁定整表
-- 使用DBMS_LOCK获取应用程序锁(模式6=排他锁)
-- 创建锁管理表
CREATE TABLE app_locks (
lock_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
lock_name VARCHAR2(100) NOT NULL,
lock_mode VARCHAR2(20) NOT NULL,
session_id NUMBER,
acquired_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
released_at TIMESTAMP DEFAULT NULL,
CONSTRAINT chk_lock_mode CHECK (lock_mode IN ('shared','exclusive','null'))
);
COMMENT ON TABLE app_locks IS '应用程序锁管理表';
-- 创建获取应用程序锁的存储过程
CREATE OR REPLACE PROCEDURE sp_acquire_table_lock (
p_table_name IN VARCHAR2,
p_lock_mode IN VARCHAR2 DEFAULT 'exclusive',
p_timeout IN NUMBER DEFAULT 10
) AS
v_lock_handle VARCHAR2(128);
v_result NUMBER;
BEGIN
-- 生成锁句柄
v_lock_handle := DBMS_LOCK.ALLOCATE_UNIQUE(
lockname => 'JEWELRY_' || UPPER(p_table_name),
lockhandle => v_lock_handle OUT
);
-- 请求锁
v_result := DBMS_LOCK.REQUEST(
lockhandle => v_lock_handle,
lockmode => CASE p_lock_mode
WHEN 'exclusive' THEN DBMS_LOCK.X_MODE -- 排他锁
WHEN 'shared' THEN DBMS_LOCK.S_MODE -- 共享锁
ELSE DBMS_LOCK.NL_MODE -- 空锁
END,
timeout => p_timeout * 10 -- 转换为十分之一秒
);
CASE v_result
WHEN 0 THEN DBMS_OUTPUT.PUT_LINE('成功获取锁:' || p_table_name || ' (' || p_lock_mode || ')');
WHEN 1 THEN DBMS_OUTPUT.PUT_LINE('超时:无法获取锁 ' || p_table_name);
WHEN 2 THEN DBMS_OUTPUT.PUT_LINE('冲突:锁与现有锁冲突');
WHEN 3 THEN DBMS_OUTPUT.PUT_LINE('参数错误');
WHEN 4 THEN DBMS_OUTPUT.PUT_LINE('内部错误');
ELSE DBMS_OUTPUT.PUT_LINE('未知错误:' || v_result);
END CASE;
-- 记录锁信息
INSERT INTO app_locks (lock_name, lock_mode, session_id)
VALUES (p_table_name, p_lock_mode, SYS_CONTEXT('USERENV', 'SID'));
END;
/
-- 创建释放应用程序锁的存储过程
CREATE OR REPLACE PROCEDURE sp_release_table_lock (
p_table_name IN VARCHAR2
) AS
v_lock_handle VARCHAR2(128);
v_result NUMBER;
BEGIN
v_lock_handle := DBMS_LOCK.ALLOCATE_UNIQUE(
lockname => 'JEWELRY_' || UPPER(p_table_name),
lockhandle => v_lock_handle OUT
);
v_result := DBMS_LOCK.RELEASE(lockhandle => v_lock_handle);
IF v_result = 0 THEN
DBMS_OUTPUT.PUT_LINE('成功释放锁:' || p_table_name);
ELSE
DBMS_OUTPUT.PUT_LINE('释放锁失败:' || v_result);
END IF;
UPDATE app_locks SET released_at = CURRENT_TIMESTAMP
WHERE lock_name = p_table_name AND released_at IS NULL;
END;
/
-- 演示表级锁使用
-- EXEC sp_acquire_table_lock('PRODUCTS', 'exclusive');
-- -- 执行批量操作...
-- UPDATE products SET stock_qty = stock_qty - 1 WHERE category_id = 7;
-- EXEC sp_release_table_lock('PRODUCTS');
-- 清理
DROP TABLE app_locks;
DROP PROCEDURE sp_acquire_table_lock;
DROP PROCEDURE sp_release_table_lock;
-- ============================================================
-- 4. 乐观锁 - 版本号机制
-- ============================================================
-- 场景:珠宝库存并发扣减,使用版本号防止覆盖更新
-- 库存乐观锁测试表
CREATE TABLE inventory_optimistic (
product_id NUMBER PRIMARY KEY,
product_name VARCHAR2(200) NOT NULL,
stock_qty NUMBER NOT NULL,
version NUMBER DEFAULT 0 NOT NULL
);
COMMENT ON TABLE inventory_optimistic IS '乐观锁库存测试表';
-- 初始化测试数据
INSERT ALL
INTO inventory_optimistic (product_id, product_name, stock_qty, version) VALUES (1, '钻石戒指', 100, 0)
INTO inventory_optimistic (product_id, product_name, stock_qty, version) VALUES (2, '黄金项链', 50, 0)
INTO inventory_optimistic (product_id, product_name, stock_qty, version) VALUES (3, '翡翠手镯', 20, 0)
SELECT * FROM dual;
-- 使用版本号实现乐观锁的更新过程
CREATE OR REPLACE PROCEDURE sp_update_stock_optimistic (
p_product_id IN NUMBER,
p_new_stock IN NUMBER,
p_version_in IN NUMBER,
p_rows_updated OUT NUMBER
) AS
v_current_version NUMBER;
BEGIN
-- 使用版本号条件更新(CAS原子操作)
UPDATE inventory_optimistic
SET stock_qty = p_new_stock, version = version + 1
WHERE product_id = p_product_id AND version = p_version_in;
p_rows_updated := SQL%ROWCOUNT;
IF p_rows_updated = 0 THEN
-- 版本号不匹配,说明有其他事务已修改该行
RAISE_APPLICATION_ERROR(-20030,
'乐观锁冲突!产品ID=' || p_product_id ||
',期望版本=' || p_version_in ||
',请重试');
END IF;
-- 获取更新后的版本号
SELECT version INTO v_current_version
FROM inventory_optimistic WHERE product_id = p_product_id;
DBMS_OUTPUT.PUT_LINE('乐观锁更新成功:产品=' || p_product_id ||
',新库存=' || p_new_stock || ',新版本=' || v_current_version);
EXCEPTION
WHEN OTHERS THEN
RAISE;
END;
/
-- 演示乐观锁更新
DECLARE
v_rows NUMBER;
v_version NUMBER := 0;
BEGIN
-- 第一次更新:版本0 -> 1
sp_update_stock_optimistic(1, 98, v_version, v_rows);
v_version := v_version + 1;
-- 第二次更新:版本1 -> 2
sp_update_stock_optimistic(1, 95, v_version, v_rows);
v_version := v_version + 1;
-- 模拟并发冲突:使用旧版本号0尝试更新
BEGIN
sp_update_stock_optimistic(1, 90, 0, v_rows); -- 版本0已过期
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('预期冲突捕获:' || SQLERRM);
END;
-- 验证最终状态
SELECT product_name, stock_qty, version INTO
v_product_name, v_stock_qty, v_version
FROM inventory_optimistic WHERE product_id = 1;
DBMS_OUTPUT.PUT_LINE('最终状态:库存=' || v_stock_qty || ',版本=' || v_version);
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
/
-- 声明变量(上面的匿名块需要)
VARIABLE v_product_name VARCHAR2(200);
VARIABLE v_stock_qty NUMBER;
VARIABLE v_version NUMBER;
-- 查看乐观锁测试表
SELECT * FROM inventory_optimistic;
-- ============================================================
-- 5. 乐观锁 - 时间戳机制
-- ============================================================
-- 使用ROWVERSION/TIMESTAMP列实现乐观锁
-- 创建带时间戳的产品表
CREATE TABLE products_timestamp (
product_id NUMBER PRIMARY KEY,
product_name VARCHAR2(200) NOT NULL,
price DECIMAL(12,2) NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);
COMMENT ON TABLE products_timestamp IS '带时间戳的产品表(乐观锁)';
INSERT ALL
INTO products_timestamp (product_id, product_name, price) VALUES (1, '测试产品A', 1000.00)
INTO products_timestamp (product_id, product_name, price) VALUES (2, '测试产品B', 2000.00)
SELECT * FROM dual;
-- 使用时间戳的更新过程
CREATE OR REPLACE PROCEDURE sp_update_with_timestamp (
p_product_id IN NUMBER,
p_new_price IN DECIMAL,
p_timestamp_in IN TIMESTAMP,
p_rows_updated OUT NUMBER
) AS
BEGIN
-- 使用时间戳条件更新
UPDATE products_timestamp
SET price = p_new_price, updated_at = CURRENT_TIMESTAMP
WHERE product_id = p_product_id AND updated_at = p_timestamp_in;
p_rows_updated := SQL%ROWCOUNT;
IF p_rows_updated = 0 THEN
RAISE_APPLICATION_ERROR(-20031,
'时间戳乐观锁冲突!产品ID=' || p_product_id || ',数据已被其他事务修改');
END IF;
END;
/
-- 查看时间戳测试表
SELECT * FROM products_timestamp;
-- ============================================================
-- 6. 乐观锁 - CAS原子扣减(纯SQL实现,无需应用层版本号)
-- ============================================================
-- 场景:高并发下单场景下的库存原子扣减
-- CAS扣减库存存储过程(纯SQL实现,无需版本号列)
CREATE OR REPLACE PROCEDURE sp_cas_deduct_stock (
p_product_id IN NUMBER,
p_quantity IN NUMBER,
p_success OUT BOOLEAN
) AS
v_current_stock NUMBER;
BEGIN
-- 原子操作:只有当库存充足时才扣减
UPDATE products
SET stock_qty = stock_qty - p_quantity
WHERE product_id = p_product_id AND stock_qty >= p_quantity;
IF SQL%ROWCOUNT > 0 THEN
p_success := TRUE;
DBMS_OUTPUT.PUT_LINE('CAS扣减成功:产品ID=' || p_product_id || ',扣减=' || p_quantity);
ELSE
p_success := FALSE;
DBMS_OUTPUT.PUT_LINE('CAS扣减失败:产品ID=' || p_product_id || ',库存不足');
END IF;
EXCEPTION
WHEN OTHERS THEN
p_success := FALSE;
RAISE;
END;
/
-- 演示CAS扣减
DECLARE
v_success BOOLEAN;
BEGIN
-- 正常扣减
sp_cas_deduct_stock(1, 1, v_success);
DBMS_OUTPUT.PUT_LINE('扣减结果:' || CASE WHEN v_success THEN '成功' ELSE '失败' END);
-- 超额扣减(应该失败)
sp_cas_deduct_stock(1, 999999, v_success);
DBMS_OUTPUT.PUT_LINE('超额扣减结果:' || CASE WHEN v_success THEN '成功' ELSE '失败' END);
-- 验证库存
DECLARE v_stock NUMBER;
BEGIN
SELECT stock_qty INTO v_stock FROM products WHERE product_id = 1;
DBMS_OUTPUT.PUT_LINE('产品1当前库存:' || v_stock);
END;
END;
/
-- ============================================================
-- 7. 锁监控与诊断
-- ============================================================
-- Oracle锁监控视图
-- 查看当前会话持有的锁
CREATE OR REPLACE VIEW v_session_locks AS
SELECT
s.sid,
s.serial#,
s.username,
s.program,
l.type AS lock_type,
l.lmode AS held_mode,
l.request AS requested_mode,
l.id1,
l.id2,
l.ctime AS seconds_waiting
FROM v$session s
JOIN v$lock l ON s.sid = l.sid
WHERE s.type = 'USER'
AND (l.request = 0 OR l.request IS NULL);
COMMENT ON VIEW v_session_locks IS '会话锁持有信息';
-- 查看被阻塞的会话
CREATE OR REPLACE VIEW v_blocked_sessions AS
SELECT
waiting.sid AS waiting_sid,
waiting.username AS waiting_user,
waiting.program AS waiting_program,
waiting.sql_id AS waiting_sql_id,
blocking.sid AS blocking_sid,
blocking.username AS blocking_user,
blocking.program AS blocking_program,
blocking.sql_id AS blocking_sql_id,
waiting.type AS lock_type,
waiting.lmode AS waiting_mode,
waiting.request AS requested_mode,
waiting.ctime AS wait_seconds
FROM v$lock waiting
JOIN v$lock blocking
ON waiting.id1 = blocking.id1
AND waiting.id2 = blocking.id2
AND waiting.sid <> blocking.sid
AND waiting.request > 0
AND blocking.lmode <> 4 -- NULL
AND blocking.request = 0;
COMMENT ON VIEW v_blocked_sessions IS '被阻塞会话监控视图';
-- 查看被锁定的对象
CREATE OR REPLACE VIEW v_locked_objects AS
SELECT
l.session_id,
s.username,
o.object_name,
o.object_type,
l.lock_mode,
l.lock_type,
l.started_wait
FROM v$locked_object l
JOIN dba_objects o ON l.object_id = o.object_id
JOIN v$session s ON l.session_id = s.sid
WHERE s.username IS NOT NULL;
COMMENT ON VIEW v_locked_objects IS '被锁定对象视图';
-- 查看锁等待链
CREATE OR REPLACE VIEW v_lock_wait_chains AS
SELECT
level AS chain_level,
sid,
type,
lmode,
request,
id1,
id2,
block,
username,
program
FROM v$lock l
JOIN v$session s ON l.sid = s.sid
START WITH s.sid IN (
SELECT sid FROM v$session WHERE state = 'WAITING' AND event LIKE 'enq:%'
)
CONNECT BY PRIOR request = 0 AND prior id1 = id1 AND prior id2 = id2 AND prior sid <> sid
AND level <= 10;
COMMENT ON VIEW v_lock_wait_chains IS '锁等待链视图';
-- 查看等待事件(诊断锁等待)
CREATE OR REPLACE VIEW v_session_waits AS
SELECT
sid,
username,
state,
event,
wait_class,
seconds_in_wait,
wait_time
FROM v$session_wait
WHERE event NOT LIKE 'SQL*Net%'
AND event NOT LIKE 'rdbms%message%'
AND event NOT LIKE 'jobq%'
AND event NOT LIKE 'queue%'
AND event NOT LIKE 'Streams AQ%'
AND event NOT LIKE 'gcs%';
COMMENT ON VIEW v_session_waits IS '会话等待事件视图';
-- 查询示例(需要实际连接Oracle数据库才能看到数据)
-- SELECT * FROM v_session_locks;
-- SELECT * FROM v_blocked_sessions;
-- SELECT * FROM v_locked_objects;
-- SELECT * FROM v_session_waits;
-- 清理测试对象
DROP TABLE inventory_optimistic;
DROP TABLE products_timestamp;
DROP PROCEDURE sp_update_stock_optimistic;
DROP PROCEDURE sp_update_with_timestamp;
DROP PROCEDURE sp_cas_deduct_stock;
DROP VIEW v_session_locks;
DROP VIEW v_blocked_sessions;
DROP VIEW v_locked_objects;
DROP VIEW v_lock_wait_chains;
DROP VIEW v_session_waits;
-- ============================================================
-- 模块: oracle21c_deadlock_saga.sql
-- ============================================================
-- ============================================================
-- Oracle 21c 珠宝行业 - 死锁检测与预防、Saga模式实现
-- Transaction & Concurrency Patterns - Deadlock Prevention & Saga
-- ============================================================
-- ============================================================
-- 1. 死锁产生演示(概念说明)
-- ============================================================
-- 死锁场景:两个会话交叉锁定资源
--
-- 会话A 会话B
-- -------- --------
-- UPDATE accounts SET balance=... UPDATE accounts SET balance=...
-- WHERE id=1 (锁定行1) WHERE id=2 (锁定行2)
-- UPDATE accounts SET balance=...
-- WHERE id=1 (等待会话A释放)
-- UPDATE accounts SET balance=... -- 会话A尝试锁定行2,被会话B阻塞
-- WHERE id=2 (等待会话B释放)
-- -> 死锁!Oracle自动检测并选择一个会话回滚
--
-- 注意:不要在生产环境中故意制造死锁!以下仅为概念说明。
-- ============================================================
-- 2. 死锁预防策略
-- ============================================================
-- 策略1:统一加锁顺序
-- 所有事务按照相同的顺序访问资源(如始终按product_id升序锁定)
-- 创建统一加锁顺序的存储过程
CREATE OR REPLACE PROCEDURE sp_unified_lock_order (
p_product_ids IN SYS.ODCINUMBERLIST,
p_operation IN VARCHAR2 DEFAULT 'deduct'
) AS
-- p_product_ids 已经按升序排列
BEGIN
-- 策略:始终按ID升序获取锁
FOR rec IN (
SELECT COLUMN_VALUE AS product_id
FROM TABLE(p_product_ids)
ORDER BY COLUMN_VALUE
) LOOP
IF p_operation = 'deduct' THEN
-- 按统一顺序锁定
UPDATE products SET stock_qty = stock_qty - 0
WHERE product_id = rec.product_id;
ELSIF p_operation = 'check' THEN
DECLARE
v_stock NUMBER;
BEGIN
SELECT stock_qty INTO v_stock
FROM products WHERE product_id = rec.product_id;
DBMS_OUTPUT.PUT_LINE('产品ID=' || rec.product_id || ' 库存=' || v_stock);
END;
END IF;
END LOOP;
END;
/
-- 策略2:减少锁持有时间
-- 尽可能缩短事务范围,避免在事务中执行长时间操作
-- 创建优化后的订单创建过程(减少锁持有时间)
CREATE OR REPLACE PROCEDURE sp_create_order_optimized (
p_customer_id IN NUMBER,
p_product_id IN NUMBER,
p_quantity IN NUMBER,
p_order_id OUT NUMBER,
p_order_no OUT VARCHAR2
) AS
v_stock NUMBER;
v_price DECIMAL(12,2);
v_total DECIMAL(12,2);
v_balance NUMBER;
v_customer_name VARCHAR2(100);
BEGIN
-- 第1步:读取所需数据(不加锁)
SELECT stock_qty, price INTO v_stock, v_price
FROM products WHERE product_id = p_product_id;
SELECT balance INTO v_balance
FROM account_ledger WHERE customer_id = p_customer_id;
SELECT customer_name INTO v_customer_name
FROM customers WHERE customer_id = p_customer_id;
-- 第2步:业务规则校验(不加锁)
IF v_stock < p_quantity THEN
RAISE_APPLICATION_ERROR(-20040, '库存不足');
END IF;
IF v_balance < v_price * p_quantity THEN
RAISE_APPLICATION_ERROR(-20041, '余额不足');
END IF;
v_total := v_price * p_quantity;
-- 第3步:执行DML(最短锁持有时间)
BEGIN
-- 锁定并更新库存
SELECT stock_qty INTO v_stock
FROM products WHERE product_id = p_product_id FOR UPDATE;
UPDATE products SET stock_qty = stock_qty - p_quantity
WHERE product_id = p_product_id;
-- 锁定并扣减余额
SELECT balance INTO v_balance
FROM account_ledger WHERE customer_id = p_customer_id FOR UPDATE;
UPDATE account_ledger SET balance = balance - v_total
WHERE customer_id = p_customer_id;
-- 创建订单
INSERT INTO orders (order_no, customer_id, total_amount, status, shipping_addr)
VALUES ('SAGA-DEMO-' || SEQ_ORDERS.NEXTVAL, p_customer_id, v_total, 'pending', NULL)
RETURNING order_id, order_no INTO p_order_id, p_order_no;
-- 创建订单明细
INSERT INTO order_items (order_id, product_id, quantity, unit_price, subtotal)
VALUES (p_order_id, p_product_id, p_quantity, v_price, v_total);
-- 记录库存日志
INSERT INTO inventory_log (product_id, change_type, change_qty, before_qty, after_qty, order_id, reference_no, operator)
VALUES (p_product_id, 'out', p_quantity, v_stock, v_stock - p_quantity, p_order_id, p_order_no, 'system');
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
DBMS_OUTPUT.PUT_LINE('订单创建成功:订单号=' || p_order_no || ',金额=' || v_total);
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END;
/
-- 策略3:设置锁超时(死锁检测超时)
-- Oracle默认会在检测到死锁时立即报错,可通过初始化参数调整
-- deadlock_detection_time (默认1分钟)
-- 在Oracle中,死锁检测是自动的,不需要手动配置
-- 策略4:使用NOWAIT避免死锁等待
-- 对于关键业务,使用NOWAIT快速失败并重试
-- 创建带重试机制的库存扣减过程
CREATE OR REPLACE PROCEDURE sp_deduct_stock_with_retry (
p_product_id IN NUMBER,
p_quantity IN NUMBER,
p_max_retries IN NUMBER DEFAULT 3
) AS
v_retry_count NUMBER := 0;
v_success BOOLEAN := FALSE;
BEGIN
WHILE v_retry_count < p_max_retries AND NOT v_success LOOP
BEGIN
-- 使用NOWAIT快速失败
UPDATE products
SET stock_qty = stock_qty - p_quantity
WHERE product_id = p_product_id
AND stock_qty >= p_quantity;
IF SQL%ROWCOUNT > 0 THEN
v_success := TRUE;
DBMS_OUTPUT.PUT_LINE('产品ID=' || p_product_id || ' 扣减成功(尝试' || (v_retry_count+1) || '次)');
END IF;
COMMIT;
EXCEPTION
WHEN OTHERS THEN
v_retry_count := v_retry_count + 1;
IF v_retry_count >= p_max_retries THEN
RAISE_APPLICATION_ERROR(-20042,
'库存扣减失败:产品ID=' || p_product_id ||
',已重试' || p_max_retries || '次');
END IF;
-- 短暂等待后重试
DBMS_LOCK.SLEEP(1); -- 等待1秒
END;
END LOOP;
END;
/
-- ============================================================
-- 3. Saga模式实现 - 订单创建流程
-- ============================================================
-- Saga模式:将长事务拆分为一系列本地事务,每个事务有对应的补偿操作
-- 3.1 创建Saga编排器表(已在schema中创建)
-- 验证表是否存在
SELECT table_name FROM user_tables WHERE table_name = 'SAGA_ORCHESTRATOR';
-- 3.2 创建Saga步骤执行引擎
CREATE OR REPLACE PROCEDURE sp_execute_saga_step (
p_saga_id IN VARCHAR2,
p_step_name IN VARCHAR2,
p_step_order IN NUMBER,
p_request_data IN CLOB,
p_response_data OUT CLOB
) AS
v_step_status VARCHAR2(20) := 'completed';
v_error_msg VARCHAR2(500);
BEGIN
-- 更新步骤状态为执行中
UPDATE saga_log SET status = 'executing'
WHERE saga_id = p_saga_id AND step_name = p_step_name AND step_order = p_step_order;
-- 根据步骤名称执行不同操作
CASE p_step_name
WHEN 'create_order' THEN
-- 创建订单
DECLARE
v_order_id NUMBER;
v_order_no VARCHAR2(30);
BEGIN
-- 从request_data中提取参数(简化处理)
v_order_no := 'SAGA-ORD-' || TO_CHAR(SYSTIMESTAMP, 'YYYYMMDDHH24MISSFF');
INSERT INTO orders (order_no, customer_id, total_amount, status)
VALUES (v_order_no, 1, 1000.00, 'pending')
RETURNING order_id INTO v_order_id;
p_response_data := '{"order_id":' || v_order_id || ',"order_no":"' || v_order_no || '"}';
END;
WHEN 'reserve_inventory' THEN
-- 预留库存
UPDATE products SET stock_qty = stock_qty - 1
WHERE product_id = 1 AND stock_qty > 0;
IF SQL%ROWCOUNT = 0 THEN
RAISE_APPLICATION_ERROR(-20050, '库存预留失败');
END IF;
p_response_data := '{"reserved":true,"product_id":1}';
WHEN 'deduct_balance' THEN
-- 扣除余额
UPDATE account_ledger SET balance = balance - 1000
WHERE customer_id = 1 AND balance >= 1000;
IF SQL%ROWCOUNT = 0 THEN
RAISE_APPLICATION_ERROR(-20051, '余额扣减失败');
END IF;
p_response_data := '{"deducted":true,"customer_id":1}';
WHEN 'confirm_order' THEN
-- 确认订单
UPDATE orders SET status = 'confirmed'
WHERE order_no = 'SAGA-ORD-' || TO_CHAR(SYSTIMESTAMP, 'YYYYMMDDHH24MISS');
p_response_data := '{"confirmed":true}';
ELSE
RAISE_APPLICATION_ERROR(-20052, '未知Saga步骤:' || p_step_name);
END CASE;
-- 更新步骤状态为完成
UPDATE saga_log SET status = 'completed', response_data = p_response_data, completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id AND step_name = p_step_name AND step_order = p_step_order;
EXCEPTION
WHEN OTHERS THEN
v_error_msg := SQLERRM;
v_step_status := 'failed';
UPDATE saga_log SET status = 'failed', error_message = v_error_msg, completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id AND step_name = p_step_name AND step_order = p_step_order;
p_response_data := '{"error":"' || v_error_msg || '"}';
RAISE;
END;
/
-- 3.3 创建Saga编排器(协调器)
CREATE OR REPLACE PROCEDURE sp_orchestrate_order_creation (
p_saga_id IN VARCHAR2,
p_customer_id IN NUMBER,
p_product_id IN NUMBER,
p_quantity IN NUMBER,
p_amount IN DECIMAL
) AS
v_steps VARCHAR2(500) := 'create_order,reserve_inventory,deduct_balance,confirm_order';
v_step_list SYS.ODCIVARCHAR2LIST;
v_failed_step VARCHAR2(100);
v_response CLOB;
BEGIN
-- 分割步骤列表
v_step_list := SYS.ODCIVARCHAR2LIST();
DECLARE
v_start NUMBER := 1;
v_end NUMBER;
BEGIN
LOOP
v_end := INSTR(v_steps, ',', v_start);
IF v_end = 0 THEN
v_step_list.EXTEND;
v_step_list(v_step_list.LAST) := SUBSTR(v_steps, v_start);
EXIT;
END IF;
v_step_list.EXTEND;
v_step_list(v_step_list.LAST) := SUBSTR(v_steps, v_start, v_end - v_start);
v_start := v_end + 1;
END LOOP;
END;
-- 初始化Saga编排器记录
INSERT INTO saga_orchestrator (saga_id, saga_type, current_step, total_steps, saga_status)
VALUES (p_saga_id, 'order_creation', 0, v_step_list.COUNT, 'running');
-- 初始化Saga日志记录
FOR i IN 1..v_step_list.COUNT LOOP
INSERT INTO saga_log (saga_id, step_name, step_order, status, request_data)
VALUES (p_saga_id, v_step_list(i), i, 'pending',
'{"customer_id":' || p_customer_id || ',"product_id":' || p_product_id ||
',"quantity":' || p_quantity || ',"amount":' || p_amount || '}');
END LOOP;
COMMIT;
-- 顺序执行各步骤
FOR i IN 1..v_step_list.COUNT LOOP
BEGIN
sp_execute_saga_step(
p_saga_id => p_saga_id,
p_step_name => v_step_list(i),
p_step_order => i,
p_request_data => NULL,
p_response_data => v_response
);
-- 更新当前步骤
UPDATE saga_orchestrator SET current_step = i WHERE saga_id = p_saga_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Saga步骤执行成功 [' || i || '/' || v_step_list.COUNT || ']: ' || v_step_list(i));
EXCEPTION
WHEN OTHERS THEN
v_failed_step := v_step_list(i);
DBMS_OUTPUT.PUT_LINE('Saga步骤失败 [' || i || '/' || v_step_list.COUNT || ']: ' || v_step_list(i) || ' - ' || SQLERRM);
-- 更新编排器状态为失败
UPDATE saga_orchestrator SET saga_status = 'failed', error_message = SQLERRM, completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id;
COMMIT;
-- 执行补偿操作(逆向执行已完成的步骤)
sp_compensate_saga(p_saga_id, i - 1);
RAISE;
END;
END LOOP;
-- 所有步骤完成
UPDATE saga_orchestrator SET saga_status = 'completed', completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Saga事务完成(全部成功):' || p_saga_id);
EXCEPTION
WHEN OTHERS THEN
-- 确保状态更新
BEGIN
UPDATE saga_orchestrator SET saga_status = 'failed', error_message = SQLERRM, completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id AND saga_status = 'running';
COMMIT;
EXCEPTION WHEN OTHERS THEN NULL;
END;
RAISE;
END;
/
-- 3.4 创建补偿引擎(逆向执行已完成的步骤)
CREATE OR REPLACE PROCEDURE sp_compensate_saga (
p_saga_id IN VARCHAR2,
p_from_step IN NUMBER -- 从哪个步骤开始补偿(倒序)
) AS
-- 补偿步骤映射
TYPE step_compensation IS RECORD (
step_name VARCHAR2(100),
comp_name VARCHAR2(100)
);
TYPE step_map IS TABLE OF step_compensation;
v_comp_map step_map := step_map(
step_compensation('create_order', 'cancel_order'),
step_compensation('reserve_inventory', 'release_inventory'),
step_compensation('deduct_balance', 'refund_balance'),
step_compensation('confirm_order', 'unconfirm_order')
);
v_comp_name VARCHAR2(100);
v_response CLOB;
BEGIN
DBMS_OUTPUT.PUT_LINE('=== 开始Saga补偿 ===');
DBMS_OUTPUT.PUT_LINE('Saga ID: ' || p_saga_id);
-- 更新编排器状态
UPDATE saga_orchestrator SET saga_status = 'compensating' WHERE saga_id = p_saga_id;
COMMIT;
-- 倒序执行补偿
FOR i IN REVERSE 1..p_from_step LOOP
-- 获取原步骤名称
DECLARE
v_step_name VARCHAR2(100);
BEGIN
SELECT step_name INTO v_step_name
FROM saga_log WHERE saga_id = p_saga_id AND step_order = i AND status = 'completed';
-- 查找对应的补偿步骤
FOR j IN 1..v_comp_map.COUNT LOOP
IF v_comp_map(j).step_name = v_step_name THEN
v_comp_name := v_comp_map(j).comp_name;
EXIT;
END IF;
END LOOP;
IF v_comp_name IS NULL THEN
DBMS_OUTPUT.PUT_LINE('无补偿操作:' || v_step_name);
CONTINUE;
END IF;
-- 执行补偿
BEGIN
-- 更新日志状态为补偿中
UPDATE saga_log SET status = 'compensating'
WHERE saga_id = p_saga_id AND step_order = i;
COMMIT;
-- 根据补偿名称执行补偿逻辑
CASE v_comp_name
WHEN 'cancel_order' THEN
-- 取消订单
UPDATE orders SET status = 'cancelled'
WHERE order_no LIKE 'SAGA-ORD-%' AND rownum = 1;
DBMS_OUTPUT.PUT_LINE('补偿执行: 取消订单');
WHEN 'release_inventory' THEN
-- 释放库存
UPDATE products SET stock_qty = stock_qty + 1
WHERE product_id = 1;
DBMS_OUTPUT.PUT_LINE('补偿执行: 释放库存');
WHEN 'refund_balance' THEN
-- 退还余额
UPDATE account_ledger SET balance = balance + 1000
WHERE customer_id = 1;
DBMS_OUTPUT.PUT_LINE('补偿执行: 退还余额');
WHEN 'unconfirm_order' THEN
-- 取消确认
DBMS_OUTPUT.PUT_LINE('补偿执行: 取消订单确认');
ELSE
DBMS_OUTPUT.PUT_LINE('未知补偿操作:' || v_comp_name);
END CASE;
-- 更新日志状态为已补偿
UPDATE saga_log SET status = 'compensated', completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id AND step_order = i;
COMMIT;
DBMS_OUTPUT.PUT_LINE('补偿成功: ' || v_step_name || ' -> ' || v_comp_name);
EXCEPTION
WHEN OTHERS THEN
UPDATE saga_log SET status = 'failed', error_message = SQLERRM
WHERE saga_id = p_saga_id AND step_order = i;
COMMIT;
DBMS_OUTPUT.PUT_LINE('补偿失败: ' || v_comp_name || ' - ' || SQLERRM);
END;
END;
END LOOP;
-- 更新编排器状态为已补偿
UPDATE saga_orchestrator SET saga_status = 'compensated', completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('=== Saga补偿完成 ===');
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('补偿过程异常:' || SQLERRM);
-- 确保状态更新
BEGIN
UPDATE saga_orchestrator SET saga_status = 'failed', error_message = SQLERRM, completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id;
COMMIT;
EXCEPTION WHEN OTHERS THEN NULL;
END;
RAISE;
END;
/
-- ============================================================
-- 4. 通用Saga编排器(支持自定义步骤)
-- ============================================================
-- 创建更通用的Saga执行器
-- Saga步骤定义表
CREATE TABLE saga_step_definitions (
step_id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
saga_type VARCHAR2(50) NOT NULL,
step_name VARCHAR2(100) NOT NULL,
step_order NUMBER NOT NULL,
action_proc VARCHAR2(100) NOT NULL COMMENT '执行的存储过程名',
compensator VARCHAR2(100) NOT NULL COMMENT '补偿存储过程名',
timeout_sec NUMBER DEFAULT 30 NOT NULL,
retry_count NUMBER DEFAULT 0 NOT NULL,
CONSTRAINT uk_saga_step UNIQUE (saga_type, step_order)
);
COMMENT ON TABLE saga_step_definitions IS 'Saga步骤定义表';
-- 预定义订单创建Saga的步骤
INSERT ALL
INTO saga_step_definitions (saga_type, step_name, step_order, action_proc, compensator, timeout_sec)
VALUES ('order_creation', 'create_order', 1, 'sp_saga_create_order', 'sp_saga_cancel_order', 30)
INTO saga_step_definitions (saga_type, step_name, step_order, action_proc, compensator, timeout_sec)
VALUES ('order_creation', 'reserve_inventory', 2, 'sp_saga_reserve_stock', 'sp_saga_release_stock', 30)
INTO saga_step_definitions (saga_type, step_name, step_order, action_proc, compensator, timeout_sec)
VALUES ('order_creation', 'process_payment', 3, 'sp_saga_deduct_balance', 'sp_saga_refund_balance', 30)
INTO saga_step_definitions (saga_type, step_name, step_order, action_proc, compensator, timeout_sec)
VALUES ('order_creation', 'confirm_delivery', 4, 'sp_saga_confirm_order', 'sp_saga_unconfirm_order', 30)
INTO saga_step_definitions (saga_type, step_name, step_order, action_proc, compensator, timeout_sec)
VALUES ('order_cancellation', 'cancel_order', 1, 'sp_saga_cancel_order', 'sp_saga_recreate_order', 30)
INTO saga_step_definitions (saga_type, step_name, step_order, action_proc, compensator, timeout_sec)
VALUES ('order_cancellation', 'refund_payment', 2, 'sp_saga_refund_balance', 'sp_saga_deduct_balance', 30)
INTO saga_step_definitions (saga_type, step_name, step_order, action_proc, compensator, timeout_sec)
VALUES ('order_cancellation', 'release_inventory', 3, 'sp_saga_release_stock', 'sp_saga_reserve_stock', 30)
SELECT * FROM dual;
-- 通用Saga执行器
CREATE OR REPLACE PROCEDURE sp_execute_saga_generic (
p_saga_id IN VARCHAR2,
p_saga_type IN VARCHAR2,
p_parameters IN CLOB DEFAULT NULL
) AS
v_saga_status VARCHAR2(20) := 'running';
v_completed_steps SYS.ODCINUMBERLIST := SYS.ODCINUMBERLIST();
BEGIN
-- 初始化
INSERT INTO saga_orchestrator (saga_id, saga_type, current_step, total_steps, saga_status)
SELECT p_saga_id, p_saga_type, 0, COUNT(*), 'running'
FROM saga_step_definitions WHERE saga_type = p_saga_type;
-- 为每个步骤创建日志记录
FOR step IN (
SELECT step_name, step_order
FROM saga_step_definitions
WHERE saga_type = p_saga_type
ORDER BY step_order
) LOOP
INSERT INTO saga_log (saga_id, step_name, step_order, status, request_data)
VALUES (p_saga_id, step.step_name, step.step_order, 'pending', p_parameters);
END LOOP;
COMMIT;
-- 顺序执行步骤
FOR step IN (
SELECT step_name, step_order, action_proc
FROM saga_step_definitions
WHERE saga_type = p_saga_type
ORDER BY step_order
) LOOP
BEGIN
-- 执行步骤动作
EXECUTE IMMEDIATE 'BEGIN ' || step.action_proc || '(:1, :2, :3); END;'
USING p_saga_id, p_parameters, OUT v_saga_status;
-- 更新步骤为完成
UPDATE saga_log SET status = 'completed', completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id AND step_name = step.step_name AND step_order = step.step_order;
v_completed_steps.EXTEND;
v_completed_steps(v_completed_steps.LAST) := step.step_order;
UPDATE saga_orchestrator SET current_step = step.step_order WHERE saga_id = p_saga_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('步骤完成: ' || step.step_name || ' (order=' || step.step_order || ')');
EXCEPTION
WHEN OTHERS THEN
-- 步骤失败,更新日志
UPDATE saga_log SET status = 'failed', error_message = SQLERRM, completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id AND step_name = step.step_name AND step_order = step.step_order;
-- 执行补偿
sp_compensate_generic_saga(p_saga_id, p_saga_type, v_completed_steps);
-- 更新编排器状态
UPDATE saga_orchestrator SET saga_status = 'compensated', completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id;
COMMIT;
RAISE;
END;
END LOOP;
-- 全部完成
UPDATE saga_orchestrator SET saga_status = 'completed', completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Saga完成(成功): ' || p_saga_id);
EXCEPTION
WHEN OTHERS THEN
BEGIN
UPDATE saga_orchestrator SET saga_status = 'failed', error_message = SQLERRM, completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id AND saga_status = 'running';
COMMIT;
EXCEPTION WHEN OTHERS THEN NULL;
END;
RAISE;
END;
/
-- 通用补偿执行器
CREATE OR REPLACE PROCEDURE sp_compensate_generic_saga (
p_saga_id IN VARCHAR2,
p_saga_type IN VARCHAR2,
p_done_steps IN SYS.ODCINUMBERLIST
) AS
BEGIN
-- 更新编排器状态
UPDATE saga_orchestrator SET saga_status = 'compensating' WHERE saga_id = p_saga_id;
COMMIT;
-- 倒序执行补偿
FOR i IN REVERSE 1..p_done_steps.COUNT LOOP
DECLARE
v_step_name VARCHAR2(100);
v_comp_name VARCHAR2(100);
BEGIN
SELECT step_name, action_proc INTO v_step_name, v_comp_name
FROM saga_step_definitions
WHERE saga_type = p_saga_type AND step_order = p_done_steps(i);
-- 将action_proc替换为compensator
v_comp_name := REPLACE(v_comp_name, 'sp_saga_', 'sp_saga_');
-- 注意:实际项目中需要维护action->compensator的映射表
BEGIN
-- 执行补偿(简化版本)
DBMS_OUTPUT.PUT_LINE('补偿执行: ' || v_step_name);
UPDATE saga_log SET status = 'compensated', completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id AND step_order = p_done_steps(i);
COMMIT;
EXCEPTION
WHEN OTHERS THEN
UPDATE saga_log SET status = 'failed', error_message = SQLERRM
WHERE saga_id = p_saga_id AND step_order = p_done_steps(i);
COMMIT;
DBMS_OUTPUT.PUT_LINE('补偿失败: ' || v_step_name || ' - ' || SQLERRM);
END;
END;
END LOOP;
-- 更新编排器状态
UPDATE saga_orchestrator SET saga_status = 'compensated', completed_at = CURRENT_TIMESTAMP
WHERE saga_id = p_saga_id;
COMMIT;
DBMS_OUTPUT.PUT_LINE('Saga补偿完成: ' || p_saga_id);
END;
/
-- ============================================================
-- 5. DBMS_SCHEDULER 异步任务 - 异步补偿执行
-- ============================================================
-- 使用Oracle调度器执行异步补偿任务
-- 创建异步补偿作业
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => 'SAGA_COMPENSATION_JOB',
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN sp_compensate_stale_sagas; END;',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=MINUTELY;INTERVAL=5', -- 每5分钟检查一次
enabled => FALSE, -- 手动启用
comments => 'Saga补偿检查作业'
);
END;
/
-- 创建补偿过期Saga的存储过程
CREATE OR REPLACE PROCEDURE sp_compensate_stale_sagas AS
BEGIN
-- 查找运行超过5分钟未完成且非终态的Saga
FOR stale_saga IN (
SELECT saga_id, saga_type, created_at
FROM saga_orchestrator
WHERE saga_status IN ('running', 'compensating')
AND created_at < SYSTIMESTAMP - INTERVAL '5' MINUTE
) LOOP
DBMS_OUTPUT.PUT_LINE('发现过期Saga: ' || stale_saga.saga_id);
-- 标记为需要补偿
UPDATE saga_orchestrator SET saga_status = 'compensating', updated_at = CURRENT_TIMESTAMP
WHERE saga_id = stale_saga.saga_id;
-- 异步执行补偿(通过DBMS_SCHEDULER)
DECLARE
v_job_name VARCHAR2(100) := 'COMP_' || stale_saga.saga_id;
BEGIN
DBMS_SCHEDULER.CREATE_JOB (
job_name => v_job_name,
job_type => 'PLSQL_BLOCK',
job_action => 'BEGIN sp_compensate_saga_generic(''' || stale_saga.saga_id || ''',''' || stale_saga.saga_type || '''); END;',
start_date => SYSTIMESTAMP,
auto_drop => TRUE,
enabled => TRUE
);
END;
END LOOP;
COMMIT;
END;
/
COMMENT ON PROCEDURE sp_compensate_stale_sagas IS '补偿过期Saga的存储过程';
-- ============================================================
-- 6. 最终一致性验证
-- ============================================================
-- 验证Saga模式下的数据一致性
-- 创建一致性验证视图
CREATE OR REPLACE VIEW v_saga_consistency_check AS
SELECT
s.saga_id,
s.saga_type,
s.saga_status,
s.current_step,
s.total_steps,
s.error_message,
s.created_at,
s.completed_at,
COUNT(CASE WHEN sl.status = 'completed' THEN 1 END) AS completed_steps,
COUNT(CASE WHEN sl.status = 'failed' THEN 1 END) AS failed_steps,
COUNT(CASE WHEN sl.status = 'compensated' THEN 1 END) AS compensated_steps
FROM saga_orchestrator s
LEFT JOIN saga_log sl ON s.saga_id = sl.saga_id
GROUP BY s.saga_id, s.saga_type, s.saga_status, s.current_step, s.total_steps,
s.error_message, s.created_at, s.completed_at;
COMMENT ON VIEW v_saga_consistency_check IS 'Saga一致性检查视图';
-- 查看Saga执行状态
SELECT * FROM v_saga_consistency_check;
-- 查看Saga详细日志
SELECT saga_id, step_name, step_order, status, error_message, created_at, completed_at
FROM saga_log
ORDER BY saga_id, step_order;
-- ============================================================
-- 7. 死锁监控视图
-- ============================================================
-- 创建死锁监控存储过程
CREATE OR REPLACE PROCEDURE sp_monitor_deadlocks AS
v_deadlock_count NUMBER;
BEGIN
-- 检查当前死锁
SELECT COUNT(*) INTO v_deadlock_count FROM v$lock
WHERE block = 1 AND request > 0;
IF v_deadlock_count > 0 THEN
DBMS_OUTPUT.PUT_LINE('警告:检测到' || v_deadlock_count || '个可能的死锁等待');
-- 记录死锁信息
FOR rec IN (
SELECT
l1.sid AS holder_sid,
l2.sid AS waiter_sid,
l1.type,
l1.lmode,
l2.request,
o.object_name
FROM v$lock l1
JOIN v$lock l2 ON l1.id1 = l2.id1 AND l1.id2 = l2.id2
AND l1.sid <> l2.sid
AND l1.block = 1 AND l2.request > 0
LEFT JOIN dba_objects o ON l1.id1 = o.object_id(+)
) LOOP
DBMS_OUTPUT.PUT_LINE(' 持有者SID=' || rec.holder_sid ||
' -> 等待者SID=' || rec.waiter_sid ||
' 对象=' || rec.object_name ||
' 锁类型=' || rec.type);
END LOOP;
ELSE
DBMS_OUTPUT.PUT_LINE('无死锁检测');
END IF;
END;
/
COMMENT ON PROCEDURE sp_monitor_deadlocks IS '死锁监控存储过程';
-- 执行死锁监控
EXEC sp_monitor_deadlocks;
-- ============================================================
-- 8. 清理测试对象
-- ============================================================
-- 注意:实际执行前请确认不需要以下对象
-- DROP TABLE saga_step_definitions;
-- DROP TABLE saga_orchestrator;
-- DROP TABLE saga_log;
-- DROP VIEW v_saga_consistency_check;
-- DROP PROCEDURE sp_execute_saga_step;
-- DROP PROCEDURE sp_orchestrate_order_creation;
-- DROP PROCEDURE sp_compensate_saga;
-- DROP PROCEDURE sp_execute_saga_generic;
-- DROP PROCEDURE sp_compensate_generic_saga;
-- DROP PROCEDURE sp_compensate_stale_sagas;
-- DROP PROCEDURE sp_unified_lock_order;
-- DROP PROCEDURE sp_create_order_optimized;
-- DROP PROCEDURE sp_deduct_stock_with_retry;
-- DROP PROCEDURE sp_monitor_deadlocks;
-- DROP VIEW v_saga_consistency_check;