MariaDB 강좌 / UPDATE·DELETE를 실행하기 전 대상 행 확인하기
UPDATE는 기존 행의 값을 바꾸고 DELETE는 조건에 맞는 행을 삭제합니다. 실행 전 같은 WHERE 조건으로 대상과 현재 값을 확인하고, InnoDB 트랜잭션 안에서 변경 결과를 검증한 뒤 COMMIT 또는 ROLLBACK을 선택해야 실수의 범위를 줄일 수 있습니다.
변경 전에 대상·복구 범위·확정 시점 정하기
이 실습은 수정과 삭제를 각각 시험한 뒤 모두 ROLLBACK으로 취소합니다. 운영 데이터 복구를 보장하는 절차가 아니라, 독립된 연습 테이블에서 대상 확인과 확정 전 검증을 배우는 예제입니다. 중요한 실제 변경에는 별도의 백업·복구 검증과 권한·동시 작업 정책이 필요합니다. 확정·취소의 범위는 트랜잭션 시작하기: InnoDB·COMMIT·ROLLBACK, 동시 연결에서 대기·충돌을 관찰하는 방법은 두 연결로 격리 수준·잠금·교착 상태 확인하기에서 별도 실습합니다.
| 확인 항목 | 확인 방법 | 이유 |
|---|---|---|
| 대상 서버·DB | 접속 정보, SELECT DATABASE() | 잘못된 서버 변경 방지 |
| 저장 엔진 | SHOW CREATE TABLE | 이 예제는 InnoDB 사용 |
| 대상 행 | 같은 WHERE의 SELECT | 이름이 아니라 고유 번호와 현재값 확인 |
| 변경 결과 | 즉시 ROW_COUNT(), 재조회 | 건수·수정값 확인 |
| 확정 여부 | COMMIT 또는 ROLLBACK | 이 연결의 트랜잭션 종료 |
WHERE가 없는 UPDATE·DELETE는 모든 행을 대상으로 합니다. 실습을 위해 운영 테이블에서 무조건 실행하거나, LIMIT만 붙였으니 안전하다고 생각하지 않습니다. 프로그램의 확인 화면을 거쳐도 실제 변경 직전 조건을 다시 평가해야 합니다.
InnoDB 연습 테이블과 연결 상태 확인하기
명령줄 클라이언트에서 course_user로 학습용 서버에 접속하고 shopcourse 데이터베이스를 준비합니다. 접속과 UTF-8 SQL 파일 실행 방법은 명령줄 클라이언트와 SQL 파일 실행 안내를 참고하세요. 아래 코드는 운영 서버가 아닌 연습 환경에서 실행합니다.
이 글은 products_18_lab이라는 독립된 실습 테이블을 사용합니다. 이전 강좌의 products 등 기존 테이블에는 손대지 않습니다. SELECT VERSION(), DATABASE();와 SHOW TABLES LIKE 'products_18_lab';로 서버·선택한 데이터베이스·이름 중복을 먼저 확인합니다. 같은 이름이 이미 있으면 정의와 데이터를 확인하고, 이 글에서 만든 연습 데이터인지 판단한 뒤 마지막 절의 반복 방법을 따르세요.
USE shopcourse; CREATE TABLE products_18_lab ( product_id INT NOT NULL AUTO_INCREMENT, product_name VARCHAR(100) NOT NULL, category VARCHAR(30) NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, note VARCHAR(100) NULL DEFAULT NULL, PRIMARY KEY (product_id), CONSTRAINT ck_p18_price CHECK (price >= 0), CONSTRAINT ck_p18_stock CHECK (stock >= 0) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; INSERT INTO products_18_lab (product_id, product_name, category, price, stock, note) VALUES (1, '노트', '문구', 3500.00, 20, NULL), (2, '볼펜', '문구', 1500.00, 0, ''), (3, '머그컵', '생활', 8000.00, 12, '선물 포장'), (4, '스탠드', '생활', 25000.00, 5, NULL), (5, '파일', '문구', 1500.00, 50, ''), (6, '케이블', '전자', 8000.00, 0, '호환 확인');
SELECT DATABASE() AS selected_database, @@autocommit AS autocommit_mode; SHOW CREATE TABLE products_18_lab;
SHOW CREATE TABLE 결과에서 ENGINE=InnoDB를 확인합니다. autocommit=1이어도 명시적인 START TRANSACTION 이후의 해당 트랜잭션은 COMMIT·ROLLBACK으로 끝낼 수 있습니다. 모든 문장은 같은 연결에서 실행해야 하며, 도구가 문장마다 새 연결을 열거나 자동 확정하는 설정이면 이 절차를 그대로 사용할 수 없습니다. 테이블 생성은 트랜잭션 실습을 시작하기 전에 끝냅니다.
재고 수정 전에 행을 잠그고 바뀐 값 확인하기
노트의 재고가 20일 때 3개 줄이는 상황입니다. 먼저 번호와 예상 재고로 조회하고, 트랜잭션을 시작한 뒤 같은 조건을 FOR UPDATE로 다시 읽습니다. 실행 사이 다른 연결이 재고를 바꿨다면 기대한 행이 없을 수 있으므로, 자동으로 조건을 완화하지 말고 멈춰 원인을 확인합니다.
SELECT product_id, product_name, stock FROM products_18_lab WHERE product_id = 1 AND stock = 20; START TRANSACTION; SELECT product_id, product_name, stock FROM products_18_lab WHERE product_id = 1 AND stock = 20 FOR UPDATE; UPDATE products_18_lab SET stock = stock - 3 WHERE product_id = 1 AND stock = 20; SELECT ROW_COUNT() AS changed_rows; SELECT product_id, stock FROM products_18_lab WHERE product_id = 1; ROLLBACK; SELECT product_id, stock FROM products_18_lab WHERE product_id = 1;
초기 데이터에서는 changed_rows=1이고, 트랜잭션 안의 재고는 17입니다. ROLLBACK 뒤에는 다시 20이 됩니다. SET stock = stock - 3은 현재 값을 기준으로 계산하고 WHERE product_id = 1 AND stock = 20은 조회 때 기대한 값이 유지되는지 다시 제한합니다. 잠금은 트랜잭션을 끝낼 때까지 유지되므로 작업 중 사용자 입력을 오래 기다리게 만들지 않습니다.
FOR UPDATE의 잠금 범위는 인덱스·격리 수준·조건 등에 영향을 받습니다. 이 예제는 기본키 한 행을 다루며, 모든 변경 충돌이나 교착 상태가 사라진다는 뜻은 아닙니다. 오류나 시간 초과가 나면 결과를 재확인하고 해당 트랜잭션을 명시적으로 끝냅니다. FOR UPDATE의 트랜잭션 적용 조건도 함께 확인하세요. UPDATE 공식 문서에서 SET과 WHERE의 역할을 확인하세요.
삭제 대상과 삭제 후 행 수를 확인하고 되돌리기
재고가 없는 모든 상품을 지우는 대신 번호 2인 볼펜 한 행만 대상으로 정합니다. 6번 케이블도 재고가 0이지만 이 예제의 삭제 대상은 아닙니다. 이름이나 수량만으로 대상을 넓히는 실수를 피하기 위해 고유 번호와 확인한 값을 함께 사용합니다.
SELECT product_id, product_name, stock FROM products_18_lab WHERE product_id = 2 AND stock = 0; START TRANSACTION; SELECT product_id, product_name, stock FROM products_18_lab WHERE product_id = 2 AND stock = 0 FOR UPDATE; DELETE FROM products_18_lab WHERE product_id = 2 AND stock = 0; SELECT ROW_COUNT() AS deleted_rows; SELECT COUNT(*) AS rows_inside_transaction FROM products_18_lab; ROLLBACK; SELECT COUNT(*) AS rows_after_rollback FROM products_18_lab;
deleted_rows=1, 삭제 직후 전체 행 수는 5, ROLLBACK 뒤 행 수는 6입니다. 마지막에는 SELECT * FROM products_18_lab WHERE product_id = 2;로 볼펜이 복구되었는지도 확인합니다. DELETE는 선택한 행을 지우는 작업이며 테이블의 열 구조를 없애지 않습니다. DELETE 공식 문서에서 WHERE가 없을 때의 범위를 확인할 수 있습니다.
영향받은 행 수가 0일 때 판단하기
START TRANSACTION; UPDATE products_18_lab SET stock = stock WHERE product_id = 1; SELECT ROW_COUNT() AS changed_rows; ROLLBACK;
기본 명령줄 연결에서 재고를 같은 값으로 대입하면 실제 바뀐 행이 없어 changed_rows는 0입니다. 따라서 0이 항상 상품이 없다는 뜻은 아닙니다. 대상 부재·현재값 조건 불일치·같은 값 대입을 재조회로 구분합니다. 클라이언트가 CLIENT_FOUND_ROWS 옵션을 사용하면 일치한 행 수를 보고할 수 있으므로 드라이버 설정도 확인해야 합니다. ROW_COUNT 공식 문서에서 두 계산 방식의 차이를 확인할 수 있습니다.
ROW_COUNT()는 변경 직후 다른 문장보다 먼저 읽습니다. 예상보다 많거나 적으면 COMMIT하지 말고 ROLLBACK한 뒤 같은 조건의 SELECT부터 다시 점검합니다. 실제로 확정하는 별도 작업에서는 취소 대신 COMMIT을 선택하고 확정 후 다시 조회해야 합니다. 이 글의 정상 파일은 연습 결과를 남기지 않기 위해 COMMIT을 포함하지 않습니다.
ROLLBACK으로 되돌릴 수 없는 경계 확인하기
ROLLBACK은 아직 확정하지 않은 해당 연결의 트랜잭션 작업을 되돌립니다. 이미 COMMIT한 변경, autocommit으로 끝난 변경, 비트랜잭션 엔진의 변경까지 이전 상태로 돌려 주지는 않습니다. CREATE·DROP·TRUNCATE 같은 구조 변경은 암묵적 확정과 별도 동작이 있으므로 취소 실습 사이에 섞지 않습니다.
safe-updates 설정에 의해 실행이 막히면 안전 장치를 무조건 끄지 말고 기본키 조건과 쿼리 범위를 먼저 확인합니다. 운영에서 실수로 확정한 삭제를 발견했다면 추가 쓰기를 멈추고 백업·로그를 이용한 복구 가능성을 담당자와 판단해야 합니다. 반대 값을 한 번 UPDATE하면 언제나 정확히 복원된다고 가정하지 않습니다.
다시 실습하려면 SELECT DATABASE();, SHOW CREATE TABLE products_18_lab;와 저장된 행을 확인합니다. 보관할 데이터가 없는 이 글의 연습 테이블이 확실할 때만 아래 문장으로 지운 뒤 준비 코드를 다시 실행합니다. DROP TABLE은 구조와 데이터를 함께 없애며 일반적인 ROLLBACK으로 취소되지 않습니다.
DROP TABLE shopcourse.products_18_lab;










