[{"data":1,"prerenderedAt":595},["ShallowReactive",2],{"doc:\u002Fmysql-indexes":3},{"id":4,"title":5,"body":6,"description":16,"extension":588,"meta":589,"navigation":590,"path":591,"seo":592,"stem":593,"__hash__":594},"docs\u002FMYSQL-INDEXES.md","PMS DB 인덱스 추천 (브리핑 카드 쿼리 부하 절감)",{"type":7,"value":8,"toc":575},"minimark",[9,13,17,50,70,73,78,83,99,105,144,150,154,170,175,192,197,199,203,382,384,388,392,400,418,422,428,445,447,451,487,489,493,571],[10,11,5],"h1",{"id":12},"pms-db-인덱스-추천-브리핑-카드-쿼리-부하-절감",[14,15,16],"p",{},"브리핑 카드 생성 시 MySQL 부하 원인은 두 가지:",[18,19,20,40],"ol",{},[21,22,23,30,31,39],"li",{},[24,25,26],"del",{},[27,28,29],"code",{},"email LIKE '%@malgnsoft.com'"," → ",[32,33,34,35,38],"strong",{},"이미 코드에서 ",[27,36,37],{},"staff user_id IN (...)"," 으로 교체"," (별도 작업 불요)",[21,41,42,43,46,47],{},"큰 프로젝트(post 수천 건)에서 ",[27,44,45],{},"tb_post"," 스캔 — ",[32,48,49],{},"복합 인덱스 추가 필요",[14,51,52,55,56,59,60,69],{},[27,53,54],{},"hp_*"," 헬퍼 테이블 인덱스는 이미 ",[27,57,58],{},"001_init_hp_tables.sql","에 정의돼 있으니 여기서는 ",[32,61,62,64,65,68],{},[27,63,45],{}," \u002F ",[27,66,67],{},"tb_post_comment"," 운영 테이블"," 만 다룹니다.",[71,72],"hr",{},[74,75,77],"h2",{"id":76},"추천-인덱스","추천 인덱스",[79,80,82],"h3",{"id":81},"_1-tb_post-최우선","1. tb_post — 최우선",[84,85,90],"pre",{"className":86,"code":87,"language":88,"meta":89,"style":89},"language-sql shiki shiki-themes github-light github-dark","ALTER TABLE tb_post ADD INDEX idx_project_status_regdate (project_id, status, reg_date);\n","sql","",[27,91,92],{"__ignoreMap":89},[93,94,97],"span",{"class":95,"line":96},"line",1,[93,98,87],{},[14,100,101,104],{},[32,102,103],{},"효과",": 다음 6개 쿼리가 모두 인덱스 한 번으로 결정됨",[106,107,108,114,120,126,132,138],"ul",{},[21,109,110,113],{},[27,111,112],{},"COUNT(*) WHERE project_id=? AND status=1"," (누적·180일 통계)",[21,115,116,119],{},[27,117,118],{},"MIN\u002FMAX(reg_date) WHERE project_id=? AND status=1"," (빠른 quick check)",[21,121,122,125],{},[27,123,124],{},"SELECT subject ... ORDER BY reg_date DESC LIMIT 100"," (제목 군집화)",[21,127,128,131],{},[27,129,130],{},"WHERE project_id=? AND status=1 AND reg_date >= ?"," (180일 cutoff)",[21,133,134,137],{},[27,135,136],{},"WHERE project_id=? AND status=1 AND p.user_id NOT IN (...)"," (미응답·고객 메시지)",[21,139,140,143],{},[27,141,142],{},"WHERE project_id=? AND label IS NOT NULL GROUP BY label"," (라벨 분포)",[14,145,146,149],{},[32,147,148],{},"비용",": 추가 디스크 \u003C 1% (PMS tb_post 행 사이즈 작음). 쓰기 부하 무시 가능.",[79,151,153],{"id":152},"_2-tb_post_comment","2. tb_post_comment",[84,155,157],{"className":86,"code":156,"language":88,"meta":89,"style":89},"ALTER TABLE tb_post_comment ADD INDEX idx_postid_status_regdate (post_id, status, reg_date);\nALTER TABLE tb_post_comment ADD INDEX idx_postid_userid_status (post_id, user_id, status);\n",[27,158,159,164],{"__ignoreMap":89},[93,160,161],{"class":95,"line":96},[93,162,163],{},"ALTER TABLE tb_post_comment ADD INDEX idx_postid_status_regdate (post_id, status, reg_date);\n",[93,165,167],{"class":95,"line":166},2,[93,168,169],{},"ALTER TABLE tb_post_comment ADD INDEX idx_postid_userid_status (post_id, user_id, status);\n",[14,171,172,174],{},[32,173,103],{},":",[106,176,177,183,189],{},[21,178,179,182],{},[27,180,181],{},"NOT EXISTS (SELECT 1 FROM tb_post_comment WHERE post_id = p.id AND status = 1 AND user_id IN (...))"," (미응답 판정)",[21,184,185,188],{},[27,186,187],{},"MIN(reg_date) WHERE post_id = ? AND user_id IN (...)"," (FRT 첫 응답 시각)",[21,190,191],{},"가장 비싼 NOT EXISTS 상관 서브쿼리가 인덱스 한 번으로 끝남",[14,193,194,196],{},[32,195,148],{},": 댓글 테이블이 크면 디스크 사용 약간 증가 (1~2%). 그래도 NOT EXISTS 풀스캔 대비 압도적 이득.",[71,198],{},[74,200,202],{"id":201},"적용-절차-운영팀-검토","적용 절차 (운영팀 검토)",[18,204,205,211,372],{},[21,206,207,210],{},[32,208,209],{},"백업 또는 dry run"," — 운영 PMS에 적용 전 테스트 서버에서 EXPLAIN 비교",[21,212,213,216,217,220,223,244,246,253,271,276,278,284,302,310,312,315,363],{},[32,214,215],{},"온라인 ALTER 시도"," — MySQL 5.6 InnoDB는 환경에 따라 옵션 제약. 단계적으로:",[218,219],"br",{},[32,221,222],{},"1단계 — LOCK 옵션 생략 (대부분 OK)",[84,224,226],{"className":86,"code":225,"language":88,"meta":89,"style":89},"ALTER TABLE tb_post\n  ADD INDEX idx_project_status_regdate (project_id, status, reg_date),\n  ALGORITHM=INPLACE;\n",[27,227,228,233,238],{"__ignoreMap":89},[93,229,230],{"class":95,"line":96},[93,231,232],{},"ALTER TABLE tb_post\n",[93,234,235],{"class":95,"line":166},[93,236,237],{},"  ADD INDEX idx_project_status_regdate (project_id, status, reg_date),\n",[93,239,241],{"class":95,"line":240},3,[93,242,243],{},"  ALGORITHM=INPLACE;\n",[218,245],{},[32,247,248,249,252],{},"2단계 — ",[27,250,251],{},"LOCK=NONE"," 미지원 에러(2A000\u002F1845) 시",[84,254,256],{"className":86,"code":255,"language":88,"meta":89,"style":89},"ALTER TABLE tb_post\n  ADD INDEX idx_project_status_regdate (project_id, status, reg_date),\n  ALGORITHM=INPLACE, LOCK=SHARED;\n",[27,257,258,262,266],{"__ignoreMap":89},[93,259,260],{"class":95,"line":96},[93,261,232],{},[93,263,264],{"class":95,"line":166},[93,265,237],{},[93,267,268],{"class":95,"line":240},[93,269,270],{},"  ALGORITHM=INPLACE, LOCK=SHARED;\n",[106,272,273],{},[21,274,275],{},"읽기 허용, 쓰기만 짧게 차단",[218,277],{},[32,279,280,281],{},"3단계 — 그래도 안 되면 점검 시간에 ",[27,282,283],{},"ALGORITHM=COPY",[84,285,287],{"className":86,"code":286,"language":88,"meta":89,"style":89},"ALTER TABLE tb_post\n  ADD INDEX idx_project_status_regdate (project_id, status, reg_date),\n  ALGORITHM=COPY;\n",[27,288,289,293,297],{"__ignoreMap":89},[93,290,291],{"class":95,"line":96},[93,292,232],{},[93,294,295],{"class":95,"line":166},[93,296,237],{},[93,298,299],{"class":95,"line":240},[93,300,301],{},"  ALGORITHM=COPY;\n",[106,303,304,307],{},[21,305,306],{},"테이블 복사 (쓰기 차단). tb_post 크기에 따라 수 분",[21,308,309],{},"점검 윈도 확보 후",[218,311],{},[32,313,314],{},"4단계 — 무중단 필수면 외부 도구",[84,316,320],{"className":317,"code":318,"language":319,"meta":89,"style":89},"language-bash shiki shiki-themes github-light github-dark","# Percona toolkit\npt-online-schema-change \\\n  --alter \"ADD INDEX idx_project_status_regdate (project_id, status, reg_date)\" \\\n  D=pms,t=tb_post \\\n  --execute\n","bash",[27,321,322,328,338,349,357],{"__ignoreMap":89},[93,323,324],{"class":95,"line":96},[93,325,327],{"class":326},"sJ8bj","# Percona toolkit\n",[93,329,330,334],{"class":95,"line":166},[93,331,333],{"class":332},"sScJk","pt-online-schema-change",[93,335,337],{"class":336},"sj4cs"," \\\n",[93,339,340,343,347],{"class":95,"line":240},[93,341,342],{"class":336},"  --alter",[93,344,346],{"class":345},"sZZnC"," \"ADD INDEX idx_project_status_regdate (project_id, status, reg_date)\"",[93,348,337],{"class":336},[93,350,352,355],{"class":95,"line":351},4,[93,353,354],{"class":345},"  D=pms,t=tb_post",[93,356,337],{"class":336},[93,358,360],{"class":95,"line":359},5,[93,361,362],{"class":336},"  --execute\n",[106,364,365],{},[21,366,367,368,371],{},"또는 ",[27,369,370],{},"gh-ost"," (GitHub)",[21,373,374,377,378,381],{},[32,375,376],{},"검증",": 적용 후 첫 브리핑 카드 호출(",[27,379,380],{},"force=1",")이 5~10배 빨라지면 OK",[71,383],{},[74,385,387],{"id":386},"explain-비교-참고","EXPLAIN 비교 (참고)",[79,389,391],{"id":390},"before","Before",[84,393,398],{"className":394,"code":396,"language":397},[395],"language-text","SELECT COUNT(*) FROM tb_post p\nJOIN tb_user pu ON pu.id = p.user_id\nWHERE p.project_id = 1528 AND p.status = 1 AND p.reg_date >= '20251130000000'\n  AND pu.email NOT LIKE '%@malgnsoft.com'\n  AND NOT EXISTS (SELECT 1 FROM tb_post_comment c JOIN tb_user cu ...)\n","text",[27,399,396],{"__ignoreMap":89},[106,401,402,407,412],{},[21,403,404,406],{},[27,405,45],{}," Using where, rows = N (project_id 인덱스만 활용)",[21,408,409,411],{},[27,410,67],{}," ALL (풀스캔, 매 row마다)",[21,413,414,417],{},[27,415,416],{},"tb_user"," Using where (email LIKE 풀스캔)",[79,419,421],{"id":420},"after-코드-변경-인덱스-추가","After (코드 변경 + 인덱스 추가)",[84,423,426],{"className":424,"code":425,"language":397},[395],"SELECT COUNT(*) FROM tb_post p\nWHERE p.project_id = 1528 AND p.status = 1 AND p.reg_date >= '20251130000000'\n  AND p.user_id NOT IN (1, 2, 3, ...)\n  AND NOT EXISTS (SELECT 1 FROM tb_post_comment c\n                   WHERE c.post_id = p.id AND c.status = 1 AND c.user_id IN (1, 2, ...))\n",[27,427,425],{"__ignoreMap":89},[106,429,430,435,440],{},[21,431,432,434],{},[27,433,45],{}," Using index for group-by, range 한정",[21,436,437,439],{},[27,438,67],{}," Using index, ref (post_id) — 인덱스 lookup만",[21,441,442,444],{},[27,443,416],{}," 안 봄 (LIKE 제거됨)",[71,446],{},[74,448,450],{"id":449},"코드-측에서-한-일","코드 측에서 한 일",[18,452,453,467,481],{},[21,454,455,458,459,462,463,466],{},[32,456,457],{},"staff user_id를 한 번에 가져와 캐시"," — 모든 후속 쿼리에서 ",[27,460,461],{},"IN","\u002F",[27,464,465],{},"NOT IN","으로 사용",[21,468,469,472,473,476,477,480],{},[32,470,471],{},"캐시 키를 quick check 2개로 변경"," — ",[27,474,475],{},"MAX(tb_post.reg_date)"," + ",[27,478,479],{},"MAX(tb_post_comment.reg_date)","로 데이터 변동 감지. 같으면 13개 SQL 안 침",[21,482,483,486],{},[32,484,485],{},"24시간 hp_briefing 캐시 적중률 상승"," — 데이터 안 바뀌면 매번 캐시 그대로 반환",[71,488],{},[74,490,492],{"id":491},"적용-후-기대-효과","적용 후 기대 효과",[494,495,496,512],"table",{},[497,498,499],"thead",{},[500,501,502,506,509],"tr",{},[503,504,505],"th",{},"지표",[503,507,508],{},"적용 전",[503,510,511],{},"적용 후",[513,514,515,530,540,551],"tbody",{},[500,516,517,521,524],{},[518,519,520],"td",{},"캐시 hit 시 SQL 쿼리 수",[518,522,523],{},"13~14",[518,525,526,529],{},[32,527,528],{},"2"," (quick check만)",[500,531,532,535,537],{},[518,533,534],{},"캐시 miss 시 SQL 쿼리 수",[518,536,523],{},[518,538,539],{},"13~14 (동일하나 각 쿼리가 빨라짐)",[500,541,542,545,548],{},[518,543,544],{},"가장 비싼 unanswered 쿼리 지연",[518,546,547],{},"수백 ms ~ 수 초",[518,549,550],{},"수십 ms",[500,552,553,559,562],{},[518,554,555,558],{},[27,556,557],{},"email LIKE"," 풀스캔",[518,560,561],{},"매 호출마다 N회",[518,563,564,567,568,570],{},[32,565,566],{},"0회"," (",[27,569,461],{}," 사용)",[572,573,574],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}",{"title":89,"searchDepth":240,"depth":240,"links":576},[577,581,582,586,587],{"id":76,"depth":166,"text":77,"children":578},[579,580],{"id":81,"depth":240,"text":82},{"id":152,"depth":240,"text":153},{"id":201,"depth":166,"text":202},{"id":386,"depth":166,"text":387,"children":583},[584,585],{"id":390,"depth":240,"text":391},{"id":420,"depth":240,"text":421},{"id":449,"depth":166,"text":450},{"id":491,"depth":166,"text":492},"md",{},true,"\u002Fmysql-indexes",{"title":5,"description":16},"MYSQL-INDEXES","VXq31GglrcgYeVnwarbrD88e5Cqb3xVRcDlzFskxSBI",1780990720861]