통계 화면이 비어 있었다. 어제까지는 나왔고 쿼리를 그대로 돌려도 0건이다.
Table of contents
Open Table of contents
증상 — 조건을 하나씩 뺐다
돌린 쿼리는 이랬다.
SELECT COUNT(*) FROM order_item oi
JOIN orders o ON o.order_no = oi.order_no
JOIN product p ON p.product_no = oi.product_no
WHERE o.status = 'done'
AND o.pay_date >= '2014-09-01'
AND p.category_no IN (12, 15)
AND oi.cancel_yn = 'N';
-- 0
조건이 넷이라 하나씩 빼 봤다.
-- cancel_yn 빼기 → 0
-- category_no 빼기 → 0
-- pay_date 빼기 → 0
-- status 빼기 → 4,102
status = 'done' 이 전부를 걸렀다.
0건이라는 결과만으로는 어느 조건 때문인지 알 수 없다. 하나씩 빼는 것은 단순하지만 확실한 방법이고 조건이 넷이면 많아야 네 번만 돌려 보면 나온다.
값이 바뀌어 있었다
상태 값을 확인했다.
SELECT status, COUNT(*) FROM orders
WHERE pay_date >= '2014-09-01' GROUP BY status;
status count
completed 4102
done 이 아니라 completed 였다. 전날 다른 사람이 상태 값 체계를 바꾸면서 이 쿼리를 안 고친 것이었다.
바꾼 쪽은 자기가 쓰는 화면만 확인하고 넘어간 상태였다. 그 값을 우리 쪽에서도 읽고 있다는 것을 몰랐던 것이다.
조인도 확인했다
이번에는 조건이 원인이었지만 조건을 다 빼도 0이 나오는 경우가 있다. 그때는 조인을 본다.
SELECT COUNT(*) FROM order_item; -- 12,041
SELECT COUNT(*) FROM order_item oi JOIN orders o ON o.order_no = oi.order_no; -- 12,041
SELECT COUNT(*) FROM order_item oi JOIN product p ON p.product_no = oi.product_no; -- 11,890
세 번째에서 줄었다. 상품이 지워진 주문 항목이 151건이었다. 통계 목적에 따라 JOIN 이 아니라 LEFT JOIN 이 맞을 수도 있다.
여기에 한 번 걸린 적이 있다. LEFT JOIN 으로 바꿨는데도 여전히 0건이 나왔다. 원인은 그 표의 컬럼 조건이 WHERE 에 남아 있었기 때문이다.
MySQL 은 WHERE 의 조건이 null-rejected 이면 LEFT JOIN 을 INNER JOIN 으로 바꿔 실행한다. null-rejected 란 NULL 로 채워진 행에 대해 FALSE 나 UNKNOWN 이 되는 조건을 말한다. 안 붙은 행은 그 컬럼이 NULL 이라 조건이 거짓이 되고 그러면 LEFT JOIN 을 쓴 의미가 없어진다. 그 조건은 WHERE 가 아니라 ON 절로 옮겨야 했다.
0건이 나올 때 볼 순서를 정했다.
1. 조건을 하나씩 빼서 어느 조건인지 찾는다
2. 그 조건의 실제 값 분포를 본다 (GROUP BY)
3. 조건을 다 빼도 0이면 조인을 하나씩 확인한다
4. 테이블 자체가 비었는지 본다 (COUNT(*))
4번을 마지막에 둔 것은 대개 그건 아니어서다. 다만 확인은 한다.
값 체계를 바꿀 때의 절차
상태 값을 바꾼 쪽에서 쓰는 곳을 다 찾았어야 했다.
$ grep -rn "'done'" --include=*.php application/ | wc -l
14
14군데였고 그중 6곳이 안 고쳐져 있었다. 그래서 값을 상수로 뺐다.
class OrderStatus {
const COMPLETED = 'completed';
}
문자열을 직접 쓰면 검색으로 찾아야 하고 빠뜨린다. 상수면 최소한 한 곳에 모인다.
값 체계를 바꿀 때의 절차도 적었다.
1. 쓰는 곳을 전부 찾는다 (코드·쿼리·배치·보고서)
2. 두 값을 다 받아들이는 기간을 둔다
3. 자료를 바꾼다
4. 코드를 새 값만 쓰게 바꾼다
5. 옛 값이 남았는지 확인한다
2번을 안 하면 바꾸는 순간 뭔가 깨진다.
// 이행 기간
if (in_array($order->status, ['done', 'completed'], true)) { ... }
통계 화면이 조용히 비어 있었던 것도 손봤다. 그게 정상인지 아닌지 화면만 봐서는 모른다.
if (empty($rows)) {
log_message('warning', "통계 조회 0건: {$sql}");
}
0건이 나오는 조회는 그 자체를 기록한다. 정상일 수도 있지만 이상일 때 단서가 된다.
정리
- 0건이 나오면 조건을 하나씩 빼서 어느 조건이 걸렀는지 찾는다
- 그 조건의 실제 값 분포를
GROUP BY로 본다. 값 체계가 바뀌었을 수 있다 - 조건을 다 빼도 0이면 조인을 하나씩 확인한다
LEFT JOIN이어도 그 표 조건이WHERE에 있으면INNER JOIN이 된다- 그 조건은
ON절로 옮긴다 - 값 체계를 바꿀 때 쓰는 곳을 전부 찾고 두 값을 다 받는 기간을 둔다
- 문자열을 직접 쓰지 말고 상수로 모은다
- 0건 조회를 로그에 남기면 이상을 빨리 안다