CodeGym /Các khóa học /SQL SELF /Gọi procedure và function bên trong transaction

Gọi procedure và function bên trong transaction

SQL SELF
Mức độ, Bài học
Có sẵn

Trong các hệ quản trị cơ sở dữ liệu hiện đại, business logic thường được viết phía server — dùng procedure và function. Khi làm việc với PostgreSQL, bạn cần hiểu rõ sự khác biệt giữa function và procedure (đặc biệt từ version 11+ có procedure) và cách chúng tương tác với transaction.

Dưới đây mình sẽ nói về các điểm chính liên quan đến cơ chế transaction, gọi lồng nhau và rollback một phần thay đổi trong procedure/function của PostgreSQL 17, dựa trên tài liệu chính thức và các giới hạn hiện tại.

Khái niệm chính: function vs procedure

Function (CREATE FUNCTION) — luôn chạy trong một transaction bên ngoài; bên trong function không được dùng lệnh transaction rõ ràng (BEGIN, COMMIT, ROLLBACK, SAVEPOINT).

  • Mọi thay đổi chỉ được commit hoặc rollback ở mức transaction bên ngoài.
  • Để "rollback một phần" trong function thì dùng BEGIN ... EXCEPTION ... END, nhưng không thể commit bên trong function.

Procedure (CREATE PROCEDURE) — xuất hiện để quản lý transaction trực tiếp trên server (ví dụ: commit từng phần, rollback từng bước, v.v.).

  • Trong procedure (PL/pgSQL) có thể dùng COMMIT, ROLLBACK, SAVEPOINT, RELEASE SAVEPOINT.
  • QUAN TRỌNG: không thể dùng ROLLBACK TO SAVEPOINT trong procedure PL/pgSQL (sẽ báo lỗi cú pháp).
  • Procedure chỉ gọi được bằng lệnh SQL riêng CALL ..., không gọi qua SELECT hay bên trong function khác.

Làm sao gọi procedure/function này từ cái khác?

Function có thể gọi function khác một cách "trong suốt" bằng tên như bình thường:

-- Ví dụ: function tính chiết khấu
CREATE OR REPLACE FUNCTION calculate_discount(order_total NUMERIC)
RETURNS NUMERIC AS $$
BEGIN
    IF order_total >= 100 THEN
        RETURN order_total * 0.1;
    ELSE
        RETURN 0;
    END IF;
END;
$$ LANGUAGE plpgsql;

-- Function xử lý đơn hàng gọi function khác
CREATE OR REPLACE FUNCTION process_order(order_id INT, order_total NUMERIC)
RETURNS VOID AS $$
DECLARE
    discount NUMERIC;
BEGIN
    discount := calculate_discount(order_total);
    RAISE NOTICE 'Chiết khấu: %', discount;
    INSERT INTO orders_log (order_id, order_total, discount)
    VALUES (order_id, order_total, discount);
END;
$$ LANGUAGE plpgsql;

Tất cả đều chạy trong một transaction bên ngoài! Nếu có lỗi ở bất kỳ function nào thì mọi thay đổi đều bị rollback.

Gọi procedure và transaction lồng nhau

Procedure có thể gọi trong procedure khác bằng lệnh CALL ... (PostgreSQL 17 cho phép stack gọi kiểu CALL proc1() -> CALL proc2()), nhưng quy tắc transaction vẫn giữ nguyên:

  • Lệnh transaction (COMMIT, ROLLBACK, SAVEPOINT, RELEASE SAVEPOINT) chỉ dùng được ở cấp cao nhất của procedure.
  • Nếu procedure có quản lý transaction được gọi bên trong một transaction đã active (ví dụ, qua client không autocommit), khi thực hiện COMMIT/SAVEPOINT sẽ báo lỗi.
QUAN TRỌNG:

không thể chạy procedure bên trong function hoặc block ẩn danh (DO ...). Chỉ được gọi bằng lệnh CALL riêng biệt

Ví dụ procedure quản lý transaction

-- Procedure commit từng bước (chỉ chạy được khi kết nối ở chế độ autocommit)
CREATE PROCEDURE process_batch_orders()
LANGUAGE plpgsql
AS $$
DECLARE
    rec RECORD;
BEGIN
    FOR rec IN SELECT order_id, order_total FROM incoming_orders LOOP
        BEGIN
            -- Lưu từng batch dữ liệu riêng biệt
            INSERT INTO orders (order_id, total) VALUES (rec.order_id, rec.order_total);
        EXCEPTION WHEN OTHERS THEN
            INSERT INTO order_errors(order_id, err_text) VALUES (rec.order_id, SQLERRM);
        END;
        COMMIT;
    END LOOP;
END;
$$;

-- Gọi procedure
CALL process_batch_orders();

Sau mỗi COMMIT sẽ tự động bắt đầu một transaction mới.

Rollback một phần (savepoint-like behavior) trong PL/pgSQL

PL/pgSQL (cả trong function lẫn procedure) không hỗ trợ lệnh ROLLBACK TO SAVEPOINT.

Để rollback một phần code chỉ dùng block BEGIN ... EXCEPTION ... END:

BEGIN
    -- một số thao tác
    BEGIN
        -- thao tác có thể lỗi
    EXCEPTION WHEN OTHERS THEN
        -- mọi thay đổi trong block này sẽ bị rollback
        RAISE NOTICE 'Rollback trong block!';
    END;
END;

Trong procedure cũng có thể dùng SAVEPOINT và RELEASE SAVEPOINT, nhưng không dùng ROLLBACK TO SAVEPOINT. Ý nghĩa là chia nhỏ các bước, nhưng chỉ quản lý được qua xử lý exception.

Giới hạn và best practice

  1. Function — chỉ cho thao tác nguyên tử: hoặc tất cả hoặc không có gì. Nếu có lỗi thì mọi thay đổi đều rollback.
  2. Procedure — chỉ gọi qua CALL: và chỉ bằng lệnh SQL riêng, không gọi từ SELECT/function. Quản lý transaction lồng nhau được, nhưng phải tuân thủ chặt chẽ giới hạn của PL/pgSQL.
  3. Rollback một phần — chỉ qua EXCEPTION: đây là cách chính thức và được hỗ trợ để rollback một phần (kiểu như SAVEPOINT).
  4. Procedure lồng nhau chỉ quản lý transaction khi gọi qua CALL: nếu không sẽ bị lỗi.

Câu hỏi về logic và transaction

Có thể tạo "transaction lồng nhau" trong function không?

Không. Tất cả đều chạy trong một transaction. Để rollback một phần — chỉ dùng block EXCEPTION.

Có thể COMMIT/ROLLBACK trong function hoặc block ẩn danh không?

Không, sẽ báo lỗi cú pháp. Hãy dùng procedure.

Có thể gọi procedure từ function không?

Không, chỉ được gọi bằng lệnh CALL. Không gọi từ function/SELECT.

Có thể dùng ROLLBACK TO SAVEPOINT trong procedure không?

Không! Trong PL/pgSQL thì cấm. Hãy dùng block EXCEPTION.

2
Nhiệm vụ
SQL SELF, mức độ, bài học
Đã khóa
Tạo và gọi các procedure để tính toán và ghi log
Tạo và gọi các procedure để tính toán và ghi log
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION