Quy trình nhiều bước giống như "dao đa năng Thụy Sĩ" cho database vậy. Thường thì nó gồm có kiểm tra dữ liệu đầu vào, thực hiện thay đổi (ví dụ: cập nhật bản ghi, ghi log), và đôi khi còn có cả phân tích nữa. Nhưng vấn đề là: càng phức tạp thì càng dễ lỗi. Lỗi logic, truy vấn chậm, thiếu sót nhỏ — và mọi thứ có thể đi tong luôn.
Gỡ lỗi toàn diện gồm các khía cạnh sau:
- Phân tích dữ liệu đầu vào: tham số đã setup đúng chưa? Dữ liệu truyền vào có chuẩn không?
- Kiểm tra thực thi các bước chính: mọi bước trong quy trình có chạy đúng không?
- Ghi log kết quả trung gian: để biết chuyện gì đã xảy ra trước khi mọi thứ "toang".
- Tối ưu hóa điểm nghẽn hiệu suất: cải thiện chỗ yếu làm truy vấn bị "lag".
Đặt vấn đề: ví dụ về quy trình nhiều bước
Cho ví dụ thực tế, giả sử bạn đang làm việc với database của một cửa hàng online. Bạn cần tạo một quy trình xử lý đơn hàng. Nó sẽ thực hiện các bước sau:
- Kiểm tra hàng còn trong kho không.
- Đặt chỗ hàng.
- Cập nhật trạng thái đơn hàng.
- Ghi sự kiện (ví dụ: đặt chỗ thành công hoặc lỗi) vào bảng log.
Script cấu trúc database:
-- Bảng sản phẩm
CREATE TABLE products (
product_id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
stock_quantity INTEGER NOT NULL
);
-- Bảng đơn hàng
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
product_id INTEGER REFERENCES products(product_id),
order_status TEXT NOT NULL
);
-- Bảng log đơn hàng
CREATE TABLE order_logs (
log_id SERIAL PRIMARY KEY,
order_id INTEGER,
log_message TEXT,
log_time TIMESTAMP DEFAULT NOW()
);
Bước 1: Tạo quy trình nhiều bước
Giờ mình sẽ tạo một procedure cơ bản process_order. Nó sẽ nhận vào id đơn hàng và thực hiện tất cả các bước xử lý.
CREATE OR REPLACE FUNCTION process_order(p_order_id INTEGER)
RETURNS VOID LANGUAGE plpgsql AS $$
DECLARE
v_product_id INTEGER;
v_stock_quantity INTEGER;
BEGIN
-- 1. Lấy id sản phẩm và trạng thái đơn hàng
SELECT product_id INTO v_product_id
FROM orders
WHERE order_id = p_order_id;
IF v_product_id IS NULL THEN
RAISE EXCEPTION 'Đơn hàng % không tồn tại hoặc thiếu product_id', p_order_id;
END IF;
-- 2. Kiểm tra hàng còn trong kho không
SELECT stock_quantity INTO v_stock_quantity
FROM products
WHERE product_id = v_product_id;
IF v_stock_quantity <= 0 THEN
RAISE EXCEPTION 'Sản phẩm % đã hết hàng', v_product_id;
END IF;
-- 3. Cập nhật số lượng trong kho
UPDATE products
SET stock_quantity = stock_quantity - 1
WHERE product_id = v_product_id;
-- 4. Cập nhật trạng thái đơn hàng
UPDATE orders
SET order_status = 'Đã xử lý'
WHERE order_id = p_order_id;
-- 5. Ghi sự kiện thành công vào log
INSERT INTO order_logs(order_id, log_message)
VALUES (p_order_id, 'Đơn hàng xử lý thành công.');
END;
$$;
Bước 2: Ghi log lỗi với RAISE NOTICE và RAISE EXCEPTION
Đây là lúc "ma thuật" bắt đầu. Mình sẽ thêm ghi log các bước trung gian để bắt lỗi và hiểu chuyện gì đang diễn ra ở từng bước.
Code cập nhật có ghi log:
CREATE OR REPLACE FUNCTION process_order(p_order_id INTEGER)
RETURNS VOID LANGUAGE plpgsql AS $$
DECLARE
v_product_id INTEGER;
v_stock_quantity INTEGER;
BEGIN
RAISE NOTICE 'Đang xử lý đơn hàng %...', p_order_id;
-- 1. Lấy id sản phẩm
SELECT product_id INTO v_product_id
FROM orders
WHERE order_id = p_order_id;
IF v_product_id IS NULL THEN
RAISE EXCEPTION 'Đơn hàng % không tồn tại hoặc thiếu product_id', p_order_id;
END IF;
RAISE NOTICE 'Product ID cho đơn hàng %: %', p_order_id, v_product_id;
-- 2. Kiểm tra hàng còn trong kho không
SELECT stock_quantity INTO v_stock_quantity
FROM products
WHERE product_id = v_product_id;
IF v_stock_quantity <= 0 THEN
RAISE EXCEPTION 'Sản phẩm % đã hết hàng', v_product_id;
END IF;
RAISE NOTICE 'Số lượng trong kho của sản phẩm %: %', v_product_id, v_stock_quantity;
-- 3. Cập nhật số lượng trong kho
UPDATE products
SET stock_quantity = stock_quantity - 1
WHERE product_id = v_product_id;
-- 4. Cập nhật trạng thái đơn hàng
UPDATE orders
SET order_status = 'Đã xử lý'
WHERE order_id = p_order_id;
-- 5. Ghi log thành công
INSERT INTO order_logs(order_id, log_message)
VALUES (p_order_id, 'Đơn hàng xử lý thành công.');
RAISE NOTICE 'Đơn hàng % xử lý thành công.', p_order_id;
EXCEPTION WHEN OTHERS THEN
-- Ghi log lỗi
INSERT INTO order_logs(order_id, log_message)
VALUES (p_order_id, 'Lỗi: ' || SQLERRM);
RAISE;
END;
$$;
Bước 3: Tối ưu hóa bằng index
Nếu database có nhiều sản phẩm hoặc đơn hàng, việc tìm dòng phù hợp có thể là điểm nghẽn. Thêm index để tăng tốc truy vấn khi xử lý:
-- Index tăng tốc tìm kiếm trong bảng orders
CREATE INDEX idx_orders_product_id ON orders(product_id);
-- Index tăng tốc tìm kiếm trong bảng products
CREATE INDEX idx_products_stock_quantity ON products(stock_quantity);
Bước 4: Phân tích hiệu suất với EXPLAIN ANALYZE
Bây giờ mình sẽ kiểm tra tốc độ thực thi function. Gọi nó với phân tích hiệu suất:
EXPLAIN ANALYZE
SELECT process_order(1);
Kết quả sẽ cho biết mỗi bước tốn bao nhiêu thời gian. Bạn sẽ biết bước nào chậm nhất — từ đó tối ưu tiếp quy trình.
Bước 5: Cải thiện bằng transaction
Để tăng độ tin cậy, có thể gói toàn bộ quy trình vào transaction. Nếu có gì sai, mọi thay đổi sẽ rollback.
BEGIN;
-- Gọi function
SELECT process_order(1);
-- Commit transaction
COMMIT;
Trong code function, bạn có thể dùng SAVEPOINT và ROLLBACK TO SAVEPOINT để xử lý lỗi từng phần.
Bài tập thực tế: xử lý hàng loạt đơn hàng
Kết thúc bài giảng bằng ví dụ xử lý nhiều đơn hàng cùng lúc. Tạo function xử lý tất cả đơn hàng có trạng thái Chờ xử lý:
CREATE OR REPLACE FUNCTION process_all_orders()
RETURNS VOID LANGUAGE plpgsql AS $$
DECLARE
v_order_id INTEGER;
BEGIN
FOR v_order_id IN
SELECT order_id
FROM orders
WHERE order_status = 'Chờ xử lý'
LOOP
BEGIN
PERFORM process_order(v_order_id);
EXCEPTION WHEN OTHERS THEN
RAISE NOTICE 'Xử lý đơn hàng % thất bại: %', v_order_id, SQLERRM;
END;
END LOOP;
END;
$$;
Khi gọi function này, tất cả đơn hàng trạng thái Chờ xử lý sẽ được xử lý, mọi lỗi chỉ được ghi log thôi.
Như vậy, mình đã demo cách gỡ lỗi và tối ưu hóa quy trình phức tạp, giúp tăng độ tin cậy, hiệu suất và dễ đọc code. Kiến thức này sẽ cực kỳ hữu ích khi bạn làm dự án thực tế, nơi chất lượng procedure quyết định thành công của ứng dụng.
GO TO FULL VERSION