Oracle 2 — PL/SQL: package, cursor, bulk & trigger

13 thg 7, 2026 2 lượt xem
#sql
#oracle
#bulk-collect
#plsql
#package
#cursor

Oracle 2 — PL/SQL: package, cursor, bulk & trigger

bài kiến trúc Oracle, ta xem Oracle như một cỗ máy lưu trữ và truy vấn. Nhưng thứ khiến Oracle bám rễ sâu trong ngân hàng, viễn thông, ERP suốt nhiều thập kỷ không chỉ là engine SQL — mà là PL/SQL: một ngôn ngữ lập trình thủ tục đầy đủ, chạy ngay bên trong database, sát cạnh dữ liệu. Bạn không chỉ gửi câu SQL vào rồi lấy kết quả ra; bạn có thể viết cả một hệ thống nghiệp vụ — vòng lặp, biến, ngoại lệ, module hoá — nằm gọn trong DB.

PL/SQL (Procedural Language extensions to SQL) ra đời cuối những năm 1980 và đến nay vẫn là "tiếng mẹ đẻ" của các hệ thống Oracle lớn. Bài này đi qua toàn bộ những mảnh ghép quan trọng: cấu trúc block, package, cursor, xử lý ngoại lệ, hai vũ khí hiệu năng BULK COLLECT / FORALL, và trigger.

Lưu ý sandbox: Sandbox của trang này chạy PostgreSQL, nên mọi ví dụ PL/SQL Oracle dưới đây KHÔNG chạy được ở đây — chúng chỉ để minh hoạ, hãy đọc như bản thiết kế và thử trên một instance Oracle thật (19c/21c/23ai). Postgres có ngôn ngữ tương đương là PL/pgSQL — xem bài riêng để so sánh; điểm khác biệt cốt lõi sẽ được nhắc rải rác.

Block PL/SQL: đơn vị nền tảng

Mọi thứ trong PL/SQL đều là block. Một block có tối đa ba phần: khai báo, thực thi, xử lý ngoại lệ.

-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
DECLARE
    v_luong   NUMBER := 0;              -- phần khai báo (tuỳ chọn)
    v_ten     VARCHAR2(100);
BEGIN
    SELECT salary, last_name            -- phần thực thi (bắt buộc)
      INTO v_luong, v_ten
      FROM employees
     WHERE employee_id = 100;
    DBMS_OUTPUT.PUT_LINE(v_ten || ': ' || v_luong);
EXCEPTION
    WHEN NO_DATA_FOUND THEN            -- phần xử lý ngoại lệ (tuỳ chọn)
        DBMS_OUTPUT.PUT_LINE('Không tìm thấy nhân viên');
END;
/

Block trên là anonymous block — không có tên, không lưu trong DB, biên dịch và chạy một lần rồi thôi. Ngược lại là stored program (procedure, function, package): có tên, được biên dịch và cất trong data dictionary, gọi đi gọi lại được, cấp quyền được, và được database quản lý phụ thuộc (nếu một bảng đổi cấu trúc, các stored program phụ thuộc bị đánh dấu INVALID để biên dịch lại).

Điểm cần nhớ ngay: SELECT ... INTO là cách nạp đúng một dòng vào biến. Nếu truy vấn trả về 0 dòng → ngoại lệ NO_DATA_FOUND; nếu trả về nhiều hơn 1 dòng → TOO_MANY_ROWS. Đây là nguồn lỗi kinh điển của người mới.

Stored program: procedure, function, và PACKAGE

PROCEDURE thực hiện một hành động (không bắt buộc trả về giá trị), dùng tham số IN / OUT / IN OUT. FUNCTION trả về một giá trị qua RETURN và có thể gọi ngay trong câu SQL (nếu không sửa đổi dữ liệu).

-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
CREATE OR REPLACE FUNCTION tinh_thue(p_luong IN NUMBER)
    RETURN NUMBER
IS
BEGIN
    RETURN p_luong * 0.1;
END tinh_thue;
/

Nhưng thứ đáng học nhất của Oracle là PACKAGE — cơ chế đóng gói mà PostgreSQL không có sẵn tương đương. Một package gồm hai phần tách rời:

  • Package specification (spec) — phần "giao diện công khai": khai báo tên các procedure/function, kiểu, hằng, cursor mà bên ngoài được phép gọi. Đây là hợp đồng.
  • Package body — phần "cài đặt": mã thực sự của các chương trình, cộng thêm các thủ tục/biến private chỉ dùng nội bộ (không khai báo trong spec).
-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
-- SPEC: giao diện công khai
CREATE OR REPLACE PACKAGE hr_luong AS
    g_he_so_max CONSTANT NUMBER := 3.0;         -- hằng công khai

    PROCEDURE tang_luong(p_emp_id IN NUMBER, p_phan_tram IN NUMBER);
    FUNCTION  tong_quy_luong(p_dept_id IN NUMBER) RETURN NUMBER;
END hr_luong;
/

-- BODY: cài đặt
CREATE OR REPLACE PACKAGE BODY hr_luong AS

    -- biến trạng thái phiên (package state): sống suốt session
    g_so_lan_goi PLS_INTEGER := 0;

    -- thủ tục PRIVATE, không có trong spec → ngoài không gọi được
    FUNCTION hop_le(p_phan_tram NUMBER) RETURN BOOLEAN IS
    BEGIN
        RETURN p_phan_tram > 0 AND p_phan_tram <= 50;
    END hop_le;

    PROCEDURE tang_luong(p_emp_id IN NUMBER, p_phan_tram IN NUMBER) IS
    BEGIN
        IF NOT hop_le(p_phan_tram) THEN
            RAISE_APPLICATION_ERROR(-20001, 'Phần trăm tăng không hợp lệ');
        END IF;
        UPDATE employees
           SET salary = salary * (1 + p_phan_tram/100)
         WHERE employee_id = p_emp_id;
        g_so_lan_goi := g_so_lan_goi + 1;
    END tang_luong;

    FUNCTION tong_quy_luong(p_dept_id IN NUMBER) RETURN NUMBER IS
        v_tong NUMBER;
    BEGIN
        SELECT SUM(salary) INTO v_tong
          FROM employees WHERE department_id = p_dept_id;
        RETURN NVL(v_tong, 0);
    END tong_quy_luong;

END hr_luong;
/

-- Gọi từ ngoài:
BEGIN
    hr_luong.tang_luong(p_emp_id => 100, p_phan_tram => 10);
END;
/

Vì sao package hơn hẳn các procedure rời?

  • Đóng gói & ẩn cài đặt. Người dùng chỉ thấy spec. Bạn sửa body thoải mái mà không làm invalid các đối tượng gọi package — chừng nào spec không đổi. Với procedure rời, sửa một cái là những cái phụ thuộc bị đánh INVALID dây chuyền.
  • Trạng thái phiên (package state). Biến khai báo ở mức package (như g_so_lan_goi) giữ giá trị suốt session — dùng làm cache trong bộ nhớ, bộ đếm, cấu hình đọc một lần.
  • Nhóm logic liên quan vào một namespace, dễ cấp quyền một lần, dễ quản lý phụ thuộc.
  • Nạp một lần. Gọi bất kỳ phần tử nào của package thì cả package được nạp vào bộ nhớ shared pool, các lời gọi sau nhanh hơn.
  • Cho phép overloading (nhiều chương trình cùng tên, khác chữ ký) và các cấu trúc như package initialization block.

Sơ đồ dưới minh hoạ cấu trúc và luồng gọi:

Kiểu, biến và collection

PL/SQL rất mạnh về gắn kiểu bám theo schema, giúp code không hỏng khi bảng đổi:

  • %TYPE — mượn kiểu của một cột hoặc một biến khác: v_luong employees.salary%TYPE;
  • %ROWTYPE — mượn cấu trúc cả một dòng: v_emp employees%ROWTYPE; rồi truy cập v_emp.salary.
  • RECORD — kiểu bản ghi tự định nghĩa, gom nhiều trường khác kiểu.
  • Collection — mảng trong bộ nhớ, ba loại:
    • Associative array (TABLE OF ... INDEX BY) — mảng kết hợp, chỉ số là số hoặc chuỗi, thưa (sparse), sống trong bộ nhớ PL/SQL. Đây là loại dùng nhiều nhất với BULK COLLECT.
    • Nested table (TABLE OF) — có thể lưu thành cột trong bảng, thứ tự linh hoạt.
    • VARRAY (VARRAY(n) OF) — mảng kích thước tối đa cố định, giữ thứ tự.
-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
DECLARE
    TYPE t_ten IS TABLE OF employees.last_name%TYPE INDEX BY PLS_INTEGER;
    v_ds t_ten;                          -- associative array
BEGIN
    v_ds(1) := 'An';  v_ds(2) := 'Bình';
    DBMS_OUTPUT.PUT_LINE(v_ds.COUNT);    -- 2
END;
/

Cursor: đi qua kết quả từng dòng

Khi cần xử lý nhiều dòng, ta dùng cursor — một con trỏ trỏ vào vùng làm việc của câu SQL.

Implicit cursor — Oracle tự tạo cho mọi câu SELECT INTO, INSERT, UPDATE, DELETE. Bạn không mở/đóng thủ công, nhưng đọc được thuộc tính của nó qua SQL%...:

-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
UPDATE employees SET salary = salary * 1.05 WHERE department_id = 10;
DBMS_OUTPUT.PUT_LINE('Đã cập nhật ' || SQL%ROWCOUNT || ' dòng');
-- SQL%FOUND, SQL%NOTFOUND, SQL%ROWCOUNT, SQL%ISOPEN

Explicit cursor — bạn khai báo, OPEN / FETCH / CLOSE thủ công, kiểm soát tối đa:

-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
DECLARE
    CURSOR c_emp IS SELECT employee_id, salary FROM employees WHERE department_id = 20;
    v_row c_emp%ROWTYPE;
BEGIN
    OPEN c_emp;
    LOOP
        FETCH c_emp INTO v_row;
        EXIT WHEN c_emp%NOTFOUND;
        DBMS_OUTPUT.PUT_LINE(v_row.employee_id || ' -> ' || v_row.salary);
    END LOOP;
    CLOSE c_emp;
END;
/

Cursor FOR loop — cách gọn nhất và được khuyến nghị cho vòng lặp đơn giản: Oracle tự OPEN/FETCH/CLOSE và tự khai báo biến dòng:

-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
BEGIN
    FOR r IN (SELECT employee_id, salary FROM employees WHERE department_id = 20) LOOP
        DBMS_OUTPUT.PUT_LINE(r.employee_id || ' -> ' || r.salary);
    END LOOP;
END;
/

Ref cursor (SYS_REFCURSOR) — con trỏ trỏ động tới một tập kết quả, có thể trả về cho tầng gọi (Java/JDBC, .NET, Python...) như một result set. Đây là cách chuẩn để một stored procedure "trả về một bảng" cho ứng dụng:

-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
CREATE OR REPLACE PROCEDURE ds_nhan_vien(
    p_dept_id IN NUMBER,
    p_cur     OUT SYS_REFCURSOR
) IS
BEGIN
    OPEN p_cur FOR
        SELECT employee_id, last_name, salary
          FROM employees WHERE department_id = p_dept_id;
END;
/

Xử lý ngoại lệ

Khi một lỗi xảy ra trong phần thực thi, luồng nhảy tới phần EXCEPTION. Có ba nhóm ngoại lệ:

  • Predefined — Oracle đặt tên sẵn cho các lỗi hay gặp: NO_DATA_FOUND (SELECT INTO không có dòng), TOO_MANY_ROWS (SELECT INTO nhiều dòng), DUP_VAL_ON_INDEX (vi phạm unique/PK), ZERO_DIVIDE, VALUE_ERROR, CURSOR_ALREADY_OPEN...
  • User-defined — bạn tự DECLARE ex EXCEPTION; rồi RAISE ex;.
  • RAISE_APPLICATION_ERROR(mã, thông_điệp) — ném lỗi có mã từ -20000 đến -20999 kèm thông điệp về tận ứng dụng gọi. Đây là cách chuẩn báo lỗi nghiệp vụ.

Trong handler, hai hàm luôn có ích: SQLCODE (mã lỗi số) và SQLERRM (thông điệp lỗi).

-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
DECLARE
    e_qua_han EXCEPTION;
    v_luong employees.salary%TYPE;
BEGIN
    SELECT salary INTO v_luong FROM employees WHERE employee_id = 9999;
    IF v_luong > 100000 THEN RAISE e_qua_han; END IF;
EXCEPTION
    WHEN NO_DATA_FOUND THEN
        RAISE_APPLICATION_ERROR(-20010, 'Nhân viên không tồn tại');
    WHEN e_qua_han THEN
        DBMS_OUTPUT.PUT_LINE('Lương vượt ngưỡng');
    WHEN OTHERS THEN                        -- bắt tất cả còn lại
        DBMS_OUTPUT.PUT_LINE('Lỗi ' || SQLCODE || ': ' || SQLERRM);
        RAISE;                              -- ném lại — đừng "nuốt" lỗi
END;
/

Nguyên tắc vàng: đừng dùng WHEN OTHERS để nuốt lỗi rồi im lặng. Nếu bắt OTHERS, hãy log lại và RAISE (hoặc RAISE_APPLICATION_ERROR), tránh che giấu bug.

Hiệu năng: context switch và BULK COLLECT / FORALL

Đây là phần quan trọng nhất về hiệu năng của PL/SQL. Oracle có hai engine tách biệt: một cho PL/SQL (chạy vòng lặp, gán biến) và một cho SQL (chạy câu truy vấn/DML). Mỗi lần PL/SQL gặp một câu SQL, quyền điều khiển chuyển sang SQL engine rồi quay lại — gọi là context switch. Một hai lần thì không sao, nhưng trong một vòng lặp chạy hàng chục nghìn lần, số lần chuyển đổi này trở thành nút cổ chai lớn.

Xét đoạn code "ngây thơ" cập nhật từng dòng trong vòng lặp: mỗi vòng là một context switch SQL↔PL/SQL. 100.000 dòng = 100.000 lần chuyển.

Giải pháp:

  • BULK COLLECT — nạp nhiều dòng cùng lúc từ SQL vào collection chỉ với một context switch, thay vì FETCH từng dòng.
  • FORALL — gửi một mảng dữ liệu cho SQL engine để chạy DML hàng loạt, cũng chỉ một context switch cho toàn bộ mảng (dù bên trong nó vẫn thực thi từng câu, việc truyền dữ liệu gộp làm một lượt).

Ví dụ đầy đủ, kết hợp cả hai, có LIMIT để không nạp hết bảng vào RAM:

-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
DECLARE
    CURSOR c_src IS SELECT employee_id, salary FROM employees;

    TYPE t_id  IS TABLE OF employees.employee_id%TYPE;
    TYPE t_sal IS TABLE OF employees.salary%TYPE;

    v_ids  t_id;
    v_sals t_sal;
    c_limit CONSTANT PLS_INTEGER := 1000;   -- xử lý theo lô 1000 dòng
BEGIN
    OPEN c_src;
    LOOP
        -- Nạp tối đa 1000 dòng trong MỘT context switch
        FETCH c_src BULK COLLECT INTO v_ids, v_sals LIMIT c_limit;
        EXIT WHEN v_ids.COUNT = 0;

        -- (tuỳ chọn) biến đổi dữ liệu trong bộ nhớ ở đây...
        FOR i IN 1 .. v_sals.COUNT LOOP
            v_sals(i) := v_sals(i) * 1.05;
        END LOOP;

        -- Ghi cả mảng về DB trong MỘT context switch
        FORALL i IN 1 .. v_ids.COUNT
            UPDATE employees SET salary = v_sals(i)
             WHERE employee_id = v_ids(i);

        COMMIT;                              -- commit theo lô, giảm undo
    END LOOP;
    CLOSE c_src;
END;
/

Vì sao có LIMIT? Không có nó, BULK COLLECT nạp toàn bộ kết quả vào collection — với bảng lớn sẽ ngốn hết PGA/RAM và có thể lỗi. LIMIT 100..1000 cân bằng giữa số context switch (ít) và bộ nhớ (giới hạn). Với FORALL, có thể thêm SAVE EXCEPTIONS để không dừng cả lô khi một dòng lỗi, rồi kiểm tra SQL%BULK_EXCEPTIONS sau đó.

Kinh nghiệm thực tế: chuyển từ vòng lặp row-by-row sang BULK COLLECT/FORALL thường cho tốc độ nhanh gấp nhiều lần trên khối lượng lớn — đây là một trong những kỹ thuật tối ưu PL/SQL cơ bản và hiệu quả nhất.

Trigger

Trigger là block PL/SQL tự động chạy khi một sự kiện xảy ra trên bảng/view/database. Các trục phân loại:

  • Thời điểm: BEFORE (trước khi thao tác áp dụng — thường để kiểm tra/sửa giá trị), AFTER (sau — thường để audit, phản ứng dây chuyền).
  • Mức: FOR EACH ROW (row-level, chạy mỗi dòng bị ảnh hưởng) vs statement-level (mặc định, chạy một lần cho cả câu lệnh dù ảnh hưởng bao nhiêu dòng).
  • Biến giả :NEW / :OLD — trong row-level trigger, :NEW là giá trị mới, :OLD là giá trị cũ của dòng. INSERT chỉ có :NEW, DELETE chỉ có :OLD, UPDATE có cả hai.
-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
CREATE OR REPLACE TRIGGER trg_luong_audit
    BEFORE UPDATE OF salary ON employees
    FOR EACH ROW
    WHEN (NEW.salary <> OLD.salary)
BEGIN
    IF :NEW.salary < :OLD.salary THEN
        RAISE_APPLICATION_ERROR(-20020, 'Không được giảm lương qua đường này');
    END IF;
    :NEW.last_updated := SYSDATE;                  -- sửa giá trị trước khi ghi
    INSERT INTO salary_log(emp_id, cu, moi, thoi_diem)
        VALUES (:OLD.employee_id, :OLD.salary, :NEW.salary, SYSTIMESTAMP);
END;
/

Compound trigger — một trigger duy nhất gộp nhiều thời điểm (BEFORE STATEMENT, BEFORE EACH ROW, AFTER EACH ROW, AFTER STATEMENT) và chia sẻ trạng thái giữa chúng. Đây là công cụ chuẩn để né lỗi mutating table (xem dưới) và để gom dữ liệu từng dòng rồi ghi hàng loạt trong phần AFTER STATEMENT.

INSTEAD OF trigger — dùng cho view: view phức tạp (nhiều bảng join) không thể INSERT/UPDATE/DELETE trực tiếp; INSTEAD OF trigger định nghĩa "thay vì" làm gì lên các bảng gốc.

Cảnh báo — đừng lạm dụng trigger. Trigger chạy âm thầm: người đọc code ứng dụng không thấy vì sao dữ liệu "tự" thay đổi, gây khó debug và khó bảo trì. Đặc biệt tránh:

  • Mutating table error (ORA-04091): một row-level trigger trên bảng T mà lại truy vấn/sửa chính bảng T → Oracle chặn để tránh kết quả không xác định. Cách xử lý hiện đại: dùng compound trigger (gom vào AFTER STATEMENT) thay cho các mẹo cũ (package chứa collection).
  • Trigger gọi trigger dây chuyền, hoặc chứa logic nghiệp vụ nặng — nên để ở procedure/package được gọi tường minh.

Nguyên tắc: dùng trigger cho audit, ràng buộc toàn vẹn không diễn đạt được bằng constraint, và cột dẫn xuất đơn giản; nghiệp vụ phức tạp thì gọi thủ tục tường minh.

Dynamic SQL

Khi câu lệnh chỉ biết lúc chạy (tên bảng động, điều kiện lắp ghép, DDL), ta cần dynamic SQL.

  • EXECUTE IMMEDIATE — cách gọn cho hầu hết trường hợp: chạy một chuỗi SQL, có thể trả kết quả INTO và nhận tham số qua USING.
  • DBMS_SQL — API cấp thấp, linh hoạt hơn khi số cột/tham số không biết trước lúc biên dịch (query hoàn toàn động).
-- (minh hoạ — Oracle PL/SQL, không chạy trong sandbox Postgres)
DECLARE
    v_ten employees.last_name%TYPE;
BEGIN
    -- ĐÚNG: dùng bind variable :id qua USING
    EXECUTE IMMEDIATE
        'SELECT last_name FROM employees WHERE employee_id = :id'
        INTO v_ten USING 100;
END;
/

Bind variable — bắt buộc, không thương lượng. Đừng bao giờ nối chuỗi giá trị người dùng vào câu SQL (... WHERE id = ' || p_input). Hai lý do:

  1. Chống SQL injection — nối chuỗi mở cửa cho kẻ tấn công chèn lệnh.
  2. Hiệu năng — bind variable cho phép Oracle tái dùng execution plan đã parse trong shared pool. Nối chuỗi tạo ra vô số câu SQL "khác nhau", làm ngập shared pool và tốn hard parse (xem bài tối ưu).

Use case thực tế: job xử lý lô cuối ngày

Một ngân hàng cần chạy tính lãi cuối ngày cho hàng triệu tài khoản: đọc số dư, tính lãi theo bậc, ghi vào bảng giao dịch, cập nhật số dư. Đây đúng là bài toán PL/SQL sinh ra để giải:

  1. Một package eod_interest đóng gói toàn bộ quy trình, spec công khai một procedure run(p_ngay).
  2. Trong body, dùng BULK COLLECT LIMIT 1000 đọc tài khoản theo lô — thay vì kéo hàng triệu dòng qua mạng về app rồi đẩy ngược lại (round-trip khủng khiếp), dữ liệu không rời server.
  3. Tính lãi trong bộ nhớ (collection), rồi FORALL ghi giao dịch và cập nhật số dư hàng loạt — mỗi lô chỉ vài context switch thay vì hàng nghìn.
  4. COMMIT theo lô để giới hạn undo/redo; FORALL ... SAVE EXCEPTIONS để một tài khoản lỗi không làm hỏng cả lô, ghi log những dòng lỗi.
  5. Ngoại lệ nghiệp vụ ném qua RAISE_APPLICATION_ERROR với mã -20xxx cho hệ thống điều phối bắt được.

So với việc kéo về app: PL/SQL thắng rõ khi tác vụ là set-based, gần dữ liệu, round-trip là nút cổ chai. Ngược lại, nếu logic hay đổi, cần tích hợp dịch vụ ngoài, cần test/version chặt chẽ thì nên để ở tầng app — nơi có Git, CI, và hệ sinh thái test tốt hơn. "Tất cả trong DB" và "không gì trong DB" đều là cực đoan.

PL/SQL vs PL/pgSQL — vài khác biệt cần nhớ

Nếu bạn quen PostgreSQL: PL/pgSQL không có package (chỉ có schema + function/procedure rời), không có package state; thay vì BULK COLLECT/FORALL, Postgres vốn thao tác theo tập tốt và ít bị context switch kiểu Oracle; ref cursor có nhưng cú pháp khác; %TYPE/%ROWTYPE thì cả hai đều có. Chi tiết ở bài PL/pgSQL.

Ghi nhớ

  • Block là đơn vị nền tảng: DECLARE/BEGIN/EXCEPTION/END. Anonymous chạy một lần; stored program được lưu, quản lý phụ thuộc, cấp quyền.
  • PACKAGE là điểm mạnh riêng của Oracle: tách spec (giao diện) và body (cài đặt) → ẩn cài đặt, giảm invalid dây chuyền, giữ trạng thái phiên, nạp một lần.
  • %TYPE/%ROWTYPE/RECORD/collection giúp code bám theo schema, không hỏng khi bảng đổi.
  • Cursor: implicit (đọc qua SQL%ROWCOUNT...), explicit (OPEN/FETCH/CLOSE), cursor FOR loop (gọn nhất), ref cursor để trả result set cho ứng dụng.
  • Ngoại lệ: biết NO_DATA_FOUND/TOO_MANY_ROWS/DUP_VAL_ON_INDEX; báo lỗi nghiệp vụ bằng RAISE_APPLICATION_ERROR (-20000..-20999); đừng nuốt lỗiWHEN OTHERS.
  • Hiệu năng: giảm context switch SQL↔PL/SQL bằng BULK COLLECT (+ LIMIT) khi đọc và FORALL khi ghi — kỹ thuật tối ưu quan trọng nhất.
  • Trigger: hiểu BEFORE/AFTER, ROW vs STATEMENT, :NEW/:OLD; dùng compound trigger để né mutating table; INSTEAD OF cho view; đừng lạm dụng — trigger chạy âm thầm.
  • Dynamic SQL: EXECUTE IMMEDIATE/DBMS_SQL; luôn dùng bind variable để chống injection và tái dùng plan.
  • Chọn PL/SQL cho tác vụ gần dữ liệu, theo lô, set-based; để nghiệp vụ hay đổi và tích hợp ngoài ở tầng app.

Đọc tiếp: Kiến trúc Oracle · SQL nâng cao Oracle · Optimizer & tuning · PL/pgSQL của PostgreSQL

Bài viết liên quan

Cách index hoạt động (B-Tree), đọc EXPLAIN, seq scan vs index scan và mẫu tối ưu truy vấn.

13 thg 7, 2026 3

Khoá, ràng buộc, quan hệ và chuẩn hoá 1NF/2NF/3NF — thiết kế lược đồ đúng từ đầu.

13 thg 7, 2026 3

Kết nhiều bảng đúng cách: các loại JOIN, bẫy thường gặp và phép hợp tập.

13 thg 7, 2026 2

Truy vấn lồng, CTE (WITH), CTE đệ quy và hàm cửa sổ — vũ khí cho phân tích nâng cao.

13 thg 7, 2026 2