Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Triggers, stored procedures: BA chỉ cần biết khái niệm

SQL and Data Analysis cho BA Bài 55/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là BA của một sàn thương mại điện tử. Một buổi sáng, bộ phận vận hành báo lên: "Có những đơn hàng tự nhiên đổi trạng thái từ pending sang cancelled mà không ai bấm nút nào cả." Bạn mở bảng orders ra, soi từng dòng, kiểm tra log ứng dụng — không thấy ai thao tác. Dữ liệu cứ tự thay đổi như có "ma".

Trong 95% trường hợp tôi gặp ngoài đời, "con ma" đó chính là trigger hoặc stored procedure — những đoạn code chạy ngầm bên trong database, không hiện ra ở tầng ứng dụng. Là BA, bạn gần như sẽ không bao giờ phải tự viết chúng (đó là việc của DBA và backend dev). Nhưng nếu bạn không biết chúng tồn tại, bạn sẽ mất hàng giờ đi sai hướng khi điều tra một sự cố dữ liệu, hoặc tệ hơn, đưa ra kết luận sai trong báo cáo gửi sếp.

Mục tiêu của bài này rất rõ ràng và có giới hạn: bạn chỉ cần biết khái niệm — hiểu trigger và stored procedure là gì, chúng làm được gì, tại sao chúng khiến dữ liệu "tự thay đổi", và quan trọng nhất là biết cách phát hiện sự tồn tại của chúng khi đi debug. Chúng ta sẽ không học cách viết một trigger production-grade, vì đó không phải vai trò của bạn. Hãy xem bài này như tấm bản đồ giúp bạn không bị lạc trong "phòng máy" của database.

Khái niệm cốt lõi

Trigger — đoạn code chạy tự động khi dữ liệu thay đổi

Trigger (tạm dịch: "cò súng") là một đoạn code được gắn vào một bảng và tự động kích hoạt mỗi khi có thao tác INSERT, UPDATE hoặc DELETE xảy ra trên bảng đó. Bạn không gọi nó, không ai bấm nút — nó tự bắn ra như một phản xạ.

Điểm mấu chốt để hiểu trigger là ba "trục" định nghĩa nó:

  • Thời điểm: BEFORE (chạy trước khi dữ liệu được ghi) hay AFTER (chạy sau khi đã ghi).
  • Sự kiện: INSERT, UPDATE, hay DELETE.
  • Phạm vi: thường là FOR EACH ROW — nghĩa là code chạy một lần cho mỗi dòng bị tác động.
Một ví dụ minh hoạ (bạn chỉ cần đọc hiểu, không cần viết):

CREATE TRIGGER trg_order_audit
AFTER UPDATE ON orders
FOR EACH ROW
INSERT INTO order_history (order_id, old_status, new_status, changed_at)
VALUES (OLD.order_id, OLD.status, NEW.status, NOW());

Đọc dòng này theo ngôn ngữ con người: "Sau khi (AFTER) bất kỳ dòng nào trong bảng orders bị cập nhật (UPDATE), hãy tự động ghi một dòng lịch sử vào bảng order_history." Hai từ khoá OLDNEW rất quan trọng: OLD là giá trị trước khi sửa, NEW là giá trị sau khi sửa. Đây là cách trigger "chụp ảnh" thay đổi.

Vì sao trigger khiến BA đau đầu? Vì nó vô hình ở tầng query thông thường. Khi bạn chạy UPDATE orders SET status = 'cancelled' WHERE order_id = 123, bạn nghĩ mình chỉ sửa một dòng. Nhưng phía sau, một trigger có thể đồng thời: trừ kho, ghi log, gửi giá trị sang bảng khác, hoặc thậm chí chặn không cho bạn sửa. Dữ liệu thay đổi nhiều hơn bạn tưởng.

Stored procedure — một "hàm" lưu sẵn trong database

Stored procedure (thủ tục lưu trữ) là một khối lệnh SQL được đặt tên và lưu sẵn trong database, để gọi lại nhiều lần. Khác với trigger (tự chạy), stored procedure phải được gọi chủ động bằng lệnh CALL:

CALL calculate_monthly_revenue(2026, 6);

Bạn có thể hình dung stored procedure giống một "macro" hay một hàm trong Excel mà ai đó đã viết sẵn: gói nhiều bước logic phức tạp lại, đặt cho nó một cái tên, rồi từ đó chỉ cần gọi tên là chạy cả khối. Bên trong một stored procedure có thể có biến, vòng lặp, lệnh điều kiện IF, và nhiều câu lệnh SELECT/INSERT/UPDATE nối tiếp nhau.

Stored procedure thường được dùng để: gói logic nghiệp vụ nặng (tính hoa hồng, tổng hợp doanh thu cuối tháng), chạy các job định kỳ (ETL ban đêm), hoặc cung cấp một "cổng" an toàn để ứng dụng gọi mà không cần viết SQL phức tạp.

Function — họ hàng gần của stored procedure

Bạn sẽ còn nghe tới function (hàm). Khác biệt thực dụng cần nhớ: function trả về một giá trị và thường được nhúng thẳng vào câu SELECT (ví dụ SELECT calc_vat(price) FROM products), còn stored procedure được gọi riêng bằng CALL và có thể không trả về gì hoặc trả về nhiều kết quả. Với vai trò BA, bạn chỉ cần phân biệt được "à đây là hàm tự định nghĩa, không phải hàm gốc của SQL" là đủ.

Tại sao BA cần biết ba thứ này

Tóm lại một câu: chúng là nguồn của những thay đổi dữ liệu mà bạn không nhìn thấy trong code ứng dụng. Khi một con số trong report sai lệch một cách bí ẩn, khi data đổi mà không ai nhận, khi một bảng "phình" lên không rõ lý do — trigger và stored procedure là nghi phạm hàng đầu mà bạn phải biết để hỏi đúng người, đúng câu.

Tình huống thực tế

Tình huống 1: Sàn TMĐT — đơn hàng "tự huỷ" lúc nửa đêm

Một sàn thương mại điện tử kiểu Tiki có khoảng 40.000 đơn/ngày. BA tên Trang nhận phản ánh: report "tỷ lệ huỷ đơn" tăng đột biến từ 4% lên 11% chỉ trong một tuần, nhưng đội chăm sóc khách hàng khẳng định không hề huỷ thêm đơn nào.

Trang chạy query thống kê và phát hiện: tất cả đơn bị huỷ "bất thường" đều có cancelled_at rơi vào khoảng 0h–1h sáng. Không có nhân viên nào làm việc giờ đó. Khi Trang đem dữ kiện này hỏi đội backend, họ kiểm tra và tìm ra một stored procedure tên auto_cancel_unpaid_orders được lập lịch chạy mỗi đêm. Nó tự huỷ mọi đơn pending quá 48 giờ chưa thanh toán. Tuần đó marketing chạy một campaign thu hút nhiều khách đặt đơn COD nhưng chần chừ, khiến số đơn quá hạn tăng vọt.

Bài học: Con số trong report không sai do lỗi query — nó sai do Trang không biết về một quy tắc nghiệp vụ được cài thẳng trong database. Vì biết khái niệm stored procedure và job định kỳ, Trang nhanh chóng đưa được dữ kiện "thời điểm huỷ tập trung lúc 0h–1h" để hướng đội kỹ thuật tới đúng thủ phạm, thay vì đổ lỗi cho đội CSKH.

Tình huống 2: Fintech ví điện tử — số dư lệch sau mỗi giao dịch

Một công ty ví điện tử kiểu MoMo có bảng wallet_transactions (ghi từng giao dịch) và bảng wallet_balances (lưu số dư hiện tại của mỗi ví). BA tên Hùng được giao đối soát: tổng giao dịch cộng dồn phải khớp với số dư.

Hùng viết query cộng tất cả giao dịch của một ví và so với số dư — và bối rối khi thấy chúng khớp một cách hoàn hảo, kể cả khi anh thử chèn thử một giao dịch test 50.000đ vào wallet_transactions thì wallet_balances cũng tự cập nhật ngay lập tức mà anh không hề đụng vào bảng đó. "Sao số dư tự nhảy?"

Hoá ра có một trigger AFTER INSERT ON wallet_transactions: mỗi khi một giao dịch được ghi vào, trigger tự động cộng/trừ số tiền tương ứng vào wallet_balances. Đây là một thiết kế tốt của hệ thống (đảm bảo số dư luôn nhất quán), nhưng nó là cái bẫy cho người đi đối soát: nếu Hùng chèn dữ liệu test mà quên, anh đã vô tình làm sai lệch số dư thật của một khách hàng.

Bài học: Trigger có thể là "bạn" (giữ dữ liệu nhất quán tự động) nhưng cũng là "bẫy" khi bạn thao tác thủ công trên môi trường có dữ liệu thật. Quy tắc vàng cho BA: không bao giờ INSERT/UPDATE/DELETE thử nghiệm trên database production, vì bạn không biết hết những trigger nào đang rình sẵn để phản ứng dây chuyền.

Tình huống 3: Ride-hailing — bảng log phình to bất thường

Một startup gọi xe kiểu Grab/Be phát hiện chi phí lưu trữ database tăng vọt. BA tên Mai được nhờ phân tích bảng nào "ngốn" dung lượng nhất. Cô thấy bảng driver_status_log có tới 800 triệu dòng — gấp 20 lần kỳ vọng.

Điều tra ra: có một trigger AFTER UPDATE ON drivers ghi log mỗi lần tài xế đổi trạng thái (online/offline/busy). Vấn đề là app gửi heartbeat cập nhật vị trí tài xế mỗi 3 giây, và mỗi lần update đều kích hoạt trigger ghi một dòng log — kể cả khi trạng thái không hề đổi. Trigger được viết thiếu điều kiện kiểm tra IF OLD.status <> NEW.status, nên nó ghi log vô tội vạ.

Bài học: Mai không sửa trigger (không phải việc của cô), nhưng nhờ hiểu cơ chế trigger chạy FOR EACH ROW cho mọi update, cô diễn giải được chính xác nguyên nhân chi phí và đề xuất giải pháp cho đội kỹ thuật. Một BA hiểu khái niệm sẽ tạo ra giá trị bằng cách đặt đúng câu hỏikhoanh đúng vùng nghi vấn, chứ không cần tự tay sửa code.

Hướng dẫn từng bước

Đây là quy trình thực dụng khi bạn nghi ngờ dữ liệu "tự thay đổi" và muốn kiểm tra xem có trigger/stored procedure không. Bạn cần quyền đọc metadata của database (read-only là đủ).

Bước 1 — Liệt kê các trigger đang tồn tại trên một bảng. Hệ thống bảng information_schema lưu metadata của toàn bộ database. Để xem trigger:

-- MySQL & PostgreSQL đều dùng được information_schema
SELECT trigger_name, event_manipulation, action_timing, event_object_table
FROM information_schema.triggers
WHERE event_object_table = 'orders';

event_manipulation cho biết trigger phản ứng với INSERT/UPDATE/DELETE; action_timing cho biết BEFORE hay AFTER. Đây là bước đầu tiên để xác nhận "có ma hay không".

Bước 2 — Trên MySQL, dùng lệnh tắt nhanh hơn:

SHOW TRIGGERS;            -- toàn bộ trigger trong database
SHOW TRIGGERS LIKE 'orders';

Bước 3 — Liệt kê stored procedure và function:

-- MySQL
SHOW PROCEDURE STATUS WHERE Db = 'shop_db';
SHOW FUNCTION STATUS WHERE Db = 'shop_db';

-- PostgreSQL SELECT routine_name, routine_type FROM information_schema.routines WHERE routine_schema = 'public';

Bước 4 — Đọc nội dung của một thủ tục để hiểu nó làm gì (chỉ đọc, đừng sửa):

-- MySQL
SHOW CREATE PROCEDURE auto_cancel_unpaid_orders;
SHOW CREATE TRIGGER trg_order_audit;

Bước 5 — Diễn giải, không phán xét. Khi đã đọc được logic, hãy dịch nó sang ngôn ngữ nghiệp vụ và mang đi xác nhận với DBA/dev: "Tôi thấy có thủ tục tự huỷ đơn quá 48h, phải chăng đây là nguyên nhân tỷ lệ huỷ tăng?" Bạn cung cấp dữ kiệngiả thuyết, để người có quyền sửa code ra quyết định.

Bước 6 — Ghi lại vào data dictionary. Nếu bạn phát hiện một trigger/procedure ảnh hưởng tới logic số liệu, hãy ghi chú lại trong tài liệu mô tả dữ liệu của team, để lần sau không ai phải đi tìm "con ma" này lần nữa.

Lỗi thường gặp & mẹo

Lỗi 1: Tin rằng "database chỉ làm đúng những gì query viết ra". Đây là ngộ nhận nguy hiểm nhất. Một UPDATE đơn giản có thể kéo theo cả chuỗi phản ứng từ trigger. Luôn giữ trong đầu khả năng có code chạy ngầm.

Lỗi 2: Thử nghiệm trên production. Như tình huống ví điện tử ở trên — chèn một dòng test có thể kích hoạt trigger làm sai lệch dữ liệu thật của khách. Luôn yêu cầu một môi trường staging/sandbox để thử.

Lỗi 3: Nhầm trigger với logic ứng dụng. Khi debug, dev hay tìm trong codebase ứng dụng. Nhưng trigger nằm trong database, không hiện ở code ứng dụng. Nếu bạn không nhắc tới khả năng này, cả team có thể bỏ sót và tìm sai chỗ hàng giờ.

Lỗi 4: Cố tự sửa trigger/procedure. Đây không phải vai trò của BA, và sửa sai một trigger trên production có thể gây sự cố dây chuyền. Việc của bạn là phát hiện và diễn giải, không phải sửa chữa.

Mẹo 1 — Dấu hiệu nhận biết "có trigger": dữ liệu thay đổi mà không có dấu vết ở tầng ứng dụng; một bảng được cập nhật ngay sau khi bảng khác thay đổi; các cột kiểu updated_at, audit_log, history tự được điền.

Mẹo 2 — Dấu hiệu "có job stored procedure định kỳ": các thay đổi tập trung vào một khung giờ cố định (nửa đêm, đầu giờ sáng), thường là dấu hiệu của một thủ tục được lập lịch (scheduled event/cron).

Mẹo 3 — Hỏi đúng câu với DBA: thay vì "data bị sai", hãy hỏi "Bảng X có trigger nào không? Có job định kỳ nào động vào bảng này lúc nửa đêm không?". Câu hỏi cụ thể tiết kiệm cho cả hai bên rất nhiều thời gian.

Mẹo 4 — Phân biệt nhanh: trigger = tự chạy khi DML xảy ra; stored procedure = được gọi bằng CALL; function = trả về giá trị, dùng trong SELECT.

Bài tập thực hành

  • Đọc hiểu trigger. Cho trigger sau, hãy diễn giải bằng tiếng Việt nó làm gì và khi nào chạy:
   CREATE TRIGGER trg_stock_check
   BEFORE INSERT ON order_items
   FOR EACH ROW
   ...giảm số lượng trong bảng products...
   
Câu hỏi: nếu bạn chèn một dòng test vào order_items trên production, điều gì sẽ xảy ra với bảng products?

  • Khoanh vùng nghi phạm. Bạn nhận report cho thấy bảng points_balance (điểm tích luỹ khách hàng) tự tăng mỗi khi có đơn hàng mới, dù không có code ứng dụng nào làm việc đó. Hãy viết câu query dùng information_schema.triggers để liệt kê các trigger trên bảng orderspoints_balance.
  • Phân loại tình huống. Với mỗi mô tả, xác định thủ phạm khả năng cao là trigger hay stored procedure định kỳ:
- (a) Mỗi khi một giao dịch được ghi, số dư ví cập nhật tức thì. - (b) Mỗi sáng 6h, bảng daily_summary có thêm dữ liệu của ngày hôm trước. - (c) Khi xoá một khách hàng, mọi đơn hàng của họ tự động chuyển sang archived.

  • Viết câu hỏi cho DBA. Bạn phát hiện doanh thu trong report lệch so với tổng giao dịch thô. Soạn 3 câu hỏi cụ thể, đúng trọng tâm để gửi DBA nhằm xác định xem có trigger/procedure nào tác động.
  • Suy ngẫm. Theo bạn, vì sao một hệ thống lại chọn dùng trigger để cập nhật số dư ví thay vì để ứng dụng tự cập nhật? Nêu một ưu điểm và một nhược điểm.
(Gợi ý đáp án bài 3: a — trigger; b — stored procedure định kỳ/scheduled job; c — trigger.)

Tóm tắt

Trigger là đoạn code tự động chạy khi có INSERT/UPDATE/DELETE trên một bảng — đó chính là lý do dữ liệu đôi khi thay đổi "huyền bí" mà không thấy dấu vết ở tầng ứng dụng. Stored procedure là khối logic SQL được lưu sẵn và gọi chủ động bằng CALL, thường dùng cho các tác vụ nghiệp vụ nặng hoặc job định kỳ; còn function là họ hàng trả về giá trị, dùng trong SELECT.

Là BA, bạn không cần viết chúng — nhưng nhất định phải biết chúng tồn tại, biết tra information_schema.triggers, SHOW TRIGGERS, SHOW PROCEDURE STATUS để phát hiện, và biết diễn giải logic của chúng sang ngôn ngữ nghiệp vụ. Ba bài học cốt lõi: (1) đừng tin database chỉ làm đúng những gì query viết ra; (2) đừng bao giờ thử nghiệm DML trên production vì trigger có thể phản ứng dây chuyền; (3) vai trò của bạn là phát hiện và đặt đúng câu hỏi, không phải sửa code. Nắm vững những điều này, bạn sẽ không bao giờ còn bị "con ma" trong database dẫn đi lạc hướng khi điều tra một sự cố dữ liệu.