Công nghệ - 04/04/2025 06:48:00
Trong quá trình làm việc, tôi đã chứng kiến nhiều trường hợp dữ liệu bị xóa nhầm hoặc cập nhật sai do các câu lệnh SQL thiếu kiểm soát. VD update nhầm dữ liệu vì quên mất bôi đen cái WHERE - kiểu UPDATE toàn bộ giới tính của user thành nữ chẳng hạn. Lúc đó, chẳng có cách nào là rollback lại log trước đó, mất một phần dữ liệu, hay phải liên hệ DBA - nói chung, chắc chắn bị sếp chửi không oan :D
Việc này xảy ra từ cả phía developer mới, lẫn những người lâu năm, nếu sơ ý.
Và anh em đều biết trong thế giới dữ liệu, chỉ một câu lệnh SQL sai có thể gây ra hậu quả nghiêm trọng: mất dữ liệu quan trọng, gián đoạn dịch vụ, thậm chí ảnh hưởng đến toàn bộ hệ thống. Với kinh nghiệm hơn một thập kỷ làm việc với database lớn, tôi đã chứng kiến nhiều sự cố đáng tiếc do các thao tác UPDATE hoặc DELETE thiếu kiểm soát.
Trong bài viết này, chúng ta cùng trao đổi sẻ các mẹo thực tiễn để thực hiện query, update và delete an toàn trong Database, giúp giảm thiểu rủi rỏ "accident", đặc biệt khi làm việc với dữ liệu lớn.
Khi thực hiện các thao tác UPDATE hoặc DELETE, hãy bao bọc chúng trong một transaction. Điều này cho phép bạn xem trước kết quả và rollback nếu có sai sót.
✅ Cách làm đúng:
BEGIN TRANSACTION;
UPDATE orders
SET status = 'cancelled'
WHERE order_date < DATEADD(MONTH, -6, GETDATE());
-- Kiểm tra kết quả
SELECT * FROM orders WHERE order_date < DATEADD(MONTH, -6,
GETDATE());
-- Nếu ổn, commit; nếu không, rollback
-- COMMIT TRANSACTION;
-- ROLLBACK TRANSACTION;
🔥 Lưu ý: Nếu sử dụng hệ thống có khối lượng giao dịch lớn, hãy kiểm tra Deadlock trước khi COMMIT.
Note: với Postgres Admin, mặc định có sẵn transaction và bạn "phải" ấn commit để submit dữ liệu thay đổi.
Trước khi thực hiện bất kỳ thao tác thay đổi dữ liệu nào, hãy viết một câu lệnh SELECT với điều kiện tương tự để kiểm tra xem bạn sẽ tác động đến những bản ghi nào. Điều này giúp bạn xác nhận phạm vi ảnh hưởng trước khi thực sự thay đổi dữ liệu.
Tình huống: bạn cần xóa các bản ghi người dùng không hoạt động trong hơn 1 năm:
✅ Cách làm đúng:
BEGIN TRANSACTION;
-- Kiểm tra trước
SELECT * FROM users WHERE last_login < DATEADD(YEAR, -1,
GETDATE()) AND active = 0;
-- Nếu đúng, mới xóa
DELETE FROM users WHERE last_login < DATEADD(YEAR, -1,
GETDATE()) AND active = 0;
-- Kiểm tra lại kết quả
SELECT * FROM users WHERE last_login < DATEADD(YEAR, -1,
GETDATE()) AND active = 0;
-- Nếu ổn, commit; nếu không, rollback
-- COMMIT TRANSACTION;
-- ROLLBACK TRANSACTION;
🚨 Sai lầm phổ biến:
DELETE FROM users WHERE last_login < '2024-04-04'; -- "lỡ" thiếu điều kiện active, có thể xóa nhầm user đang hoạt động!
Để đỡ dài dòng, ở các tips dưới tôi tạm thời bỏ TRANSACTION để các bạn đễ tập trung vào điều kiện nhé. Tuy nhiên, các bạn luôn nhớ áp dụng 2 típ đầu tiên này: TRANSACTION, + WHERE
Điều kiện WHERE không đủ chi tiết có thể gây ra mất mát dữ liệu nghiêm trọng.
🚨 Sai lầm phổ biến:
UPDATE products SET price = 0; -- Đặt giá TẤT CẢ sản phẩm về 0!
✅ Cách làm đúng:
UPDATE products SET price = 0 WHERE category = 'discontinued' AND stock = 0;
Thay vì xóa một lúc hàng triệu bản ghi, hãy xử lý theo lô nhỏ.
PostgreSQL:
DELETE FROM logs WHERE created_at < NOW() - INTERVAL '1 year' LIMIT 1000;
SQL Server:
DELETE TOP (1000) FROM logs WHERE created_at < DATEADD(YEAR, -1, GETDATE());
Dữ liệu quan trọng luôn cần backup trước khi chạy UPDATE hoặc DELETE hàng loạt.
PostgreSQL:
CREATE TABLE users_backup AS SELECT * FROM users;
SQL Server:
SELECT * INTO users_backup FROM users;
🔥 Viết ra một quy trình an toàn:
Viết câu lệnh trên môi trường Staging.
Chạy thử và kiểm tra dữ liệu.
Chỉ áp dụng lên Production khi đã thực sự chắc chắn.
Nếu chạy cron job hoặc script tự động, luôn luôn thêm điều kiện kiểm tra.
PostgreSQL:
DELETE FROM temp_data WHERE created_at < CURRENT_DATE - INTERVAL '7 days' AND status = 'processed';
SQL Server:
DELETE FROM temp_data WHERE created_at < DATEADD(DAY, -7, GETDATE()) AND status = 'processed';
Ghi lại các thao tác DELETE hoặc UPDATE để có thể kiểm tra lại sau này.
PostgreSQL:
CREATE TABLE audit_log (
id SERIAL PRIMARY KEY,
table_name TEXT,
action TEXT,
record_id INT,
changed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE OR REPLACE FUNCTION log_user_deletion()
RETURNS TRIGGER AS $$
BEGIN
INSERT INTO audit_log (table_name, action, record_id)
VALUES ('users', 'DELETE', OLD.id);
RETURN OLD;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER log_user_deletion
AFTER DELETE ON users
FOR EACH ROW
EXECUTE FUNCTION log_user_deletion();
SQL Server:
CREATE TABLE audit_log (
id INT IDENTITY(1,1) PRIMARY KEY,
table_name NVARCHAR(50),
action NVARCHAR(50),
record_id INT,
changed_at DATETIME DEFAULT GETDATE()
);
CREATE TRIGGER log_user_deletion
ON users
AFTER DELETE
AS
BEGIN
INSERT INTO audit_log (table_name, action, record_id)
SELECT 'users', 'DELETE', deleted.id
FROM deleted;
END;
Để tránh các sự cố nghiêm trọng trong database, hãy luôn:
✅ Dùng TRANSACTION để có thể ROLLBACK ngay khi cần.
✅ Kiểm tra bằng SELECT trước khi UPDATE hoặc DELETE.
✅ Thêm điều kiện WHERE cụ thể và giới hạn số bản ghi bị ảnh hưởng.
✅ Sao lưu dữ liệu và kiểm tra trên môi trường Staging trước khi chạy trên Production.
Chúc các bạn coding vui vẻ.
/Son Do - 1 ông dev thích phá database
Bạn đã từng gặp sự cố nào do câu lệnh SQL mình viết chưa? Hãy chia sẻ kinh nghiệm đau thượng của bạn trong phần bình luận nhé 🚀
#SQL #Programming #SQLBestPractices #DataSafety #CodingTips #TechTutorial #TechBlog DBA #SoftwareDevelopment
#wecommit100xshare #1percenterbetter