<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>면목동인간의 일상</title>
    <link>https://oniic.tistory.com/</link>
    <description>안녕하세요!! 무한한 가능성과 항상 노력하고 배우는 새싹 개발자입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 24 Jun 2026 16:23:04 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>면목동인간</managingEditor>
    <image>
      <title>면목동인간의 일상</title>
      <url>https://tistory1.daumcdn.net/tistory/4454910/attach/437a2cb9cf5748d08091b283b5b3d388</url>
      <link>https://oniic.tistory.com</link>
    </image>
    <item>
      <title>&amp;lt;DB&amp;gt; 커버링 인덱스란?</title>
      <link>https://oniic.tistory.com/196</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;커버링 인덱스란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;커버링 인덱스란 쿼리에 필요한 모든 칼럼을 포함하고 있는 인덱스를 의미한다. 예를 들어 책의 맨 뒤의 원하는 키워드에서 찾은 후 (1단계) 다시 그 페이지로 넘어가서 내용을 읽는(2단계)가 아닌 1단계에서 처리를 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;커버링 인덱스 적용 전&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;아래는 price만 인덱스가 걸려 있고, 그 상태에서 item_id, price, item_name을 SELECT 하는 쿼리를 실행했다고 가정하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1781362879564&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT item_id, price, item_name FROM items WHERE price BETWEEN 50000 AND
100000;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;585&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq6iRe/dJMcaiKofPG/AiEAWy3ppNxV9k3aZ0eKT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq6iRe/dJMcaiKofPG/AiEAWy3ppNxV9k3aZ0eKT0/img.png&quot; data-alt=&quot;김영한의 실전 데이터베이스 기본편/인프런&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq6iRe/dJMcaiKofPG/AiEAWy3ppNxV9k3aZ0eKT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq6iRe%2FdJMcaiKofPG%2FAiEAWy3ppNxV9k3aZ0eKT0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;527&quot; height=&quot;367&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;585&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;김영한의 실전 데이터베이스 기본편/인프런&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위의 쿼리를 분석해보면 인덱스에서 price 조건에 맞는 행의 item_id 5개를 찾았지만, item_name도 추가로 조회하기 때문에 &lt;b&gt;원본 테이블에 5번 접근(5번 랜덤 I/O 발생)&lt;/b&gt;하여 item_name을 가져와야 한다. 위의 인덱스에는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;item_name&lt;span&gt; &lt;/span&gt;&lt;/span&gt;포함되지 않기 때문에 원본 테이블을 무조건 접근해야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;커버링 인덱스 적용 후&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앞서 위에 price만 적용된 인덱스 말고 item_name까지 포함하는 인덱스가 걸려있고 위와 똑같이 SELECT 하는 쿼리를 실행했다고 가정하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjynpT/dJMcaci6PNF/Y48aqdmeJyDSUqncwa9dtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjynpT/dJMcaci6PNF/Y48aqdmeJyDSUqncwa9dtk/img.png&quot; data-alt=&quot;김영한의 실전 데이터베이스 기본편/인프런&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjynpT/dJMcaci6PNF/Y48aqdmeJyDSUqncwa9dtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjynpT%2FdJMcaci6PNF%2FY48aqdmeJyDSUqncwa9dtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;530&quot; height=&quot;452&quot; data-origin-width=&quot;833&quot; data-origin-height=&quot;710&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;김영한의 실전 데이터베이스 기본편/인프런&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위의 쿼리를 분석해보면 인덱스만 읽고 끝나므로, 원본 테이블 접근을 위한 &lt;b&gt;랜덤 I/O가 완전히 사라져 훨씬 빠르고 효율적으로 동작&lt;/b&gt;한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;장점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;1. 디스크 I/O 감소&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; 일반 인덱스: &lt;span&gt;인덱스 읽기&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;테이블 읽기 이지만, 커버링 인덱스: 인덱스만 읽기 이므로 랜덤 I/O가 크게 감소한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;b&gt;2. 응답 속도 향상&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; 수백만 건 이상의 테이블에서는 체감 성능 차이가 상당할 수 있다.&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;단점&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;1. 인덱스 크기 증가 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;-&amp;gt; 커버링을 위해 칼럼을 많이 넣으면 인덱스 크기가 증가한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span&gt;&amp;nbsp;2. INSERT 성능 저하 &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;-&amp;gt; 새 데이터 입력 시 원본 &lt;/span&gt;테이블뿐 아니라 인덱스도 갱신해야 한다. 인덱스가 크고 많을수록 INSERT 느려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;3. UPDATE 비용 증가 &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; 인덱스 컬럼 수정 시 인덱스 재구성이 발생한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;언제 사용하는가?&lt;/b&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2905&quot; data-start=&quot;2887&quot; data-section-id=&quot;1q4oara&quot;&gt;조회가 압도적으로 많은 서비스&lt;/li&gt;
&lt;li data-end=&quot;2915&quot; data-start=&quot;2906&quot; data-section-id=&quot;1gxr7fo&quot;&gt;대용량 테이블&lt;/li&gt;
&lt;li data-end=&quot;2931&quot; data-start=&quot;2916&quot; data-section-id=&quot;wv6b9o&quot;&gt;자주 실행되는 핵심 쿼리&lt;/li&gt;
&lt;li data-end=&quot;2943&quot; data-start=&quot;2932&quot; data-section-id=&quot;16juguw&quot;&gt;API 목록 조회&lt;/li&gt;
&lt;li data-end=&quot;2952&quot; data-start=&quot;2944&quot; data-section-id=&quot;1yk1kd&quot;&gt;페이징 조회&lt;/li&gt;
&lt;li data-end=&quot;2960&quot; data-start=&quot;2953&quot; data-section-id=&quot;6vpykl&quot;&gt;통계 조회&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;커버링 인덱스는 &quot;쿼리에 필요한 모든 칼럼이 인덱스 안에 있어 테이블 접근 없이 인덱스만으로 결과를 반환하는 최적화 기법&quot;이다. 조회 성능은 크게 향상되지만, 인덱스 크기 증가와 쓰기 성능 저하라는 비용을 함께 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;본 포스팅은 &amp;ldquo;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;김영한의 실전 데이터베이스 기본편/&lt;/span&gt;인프런&amp;rdquo;를 학습한 내용을 정리한 것&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DB/DB</category>
      <category>DB 커버링 인덱스</category>
      <category>커버링 인덱스란</category>
      <author>면목동인간</author>
      <guid isPermaLink="true">https://oniic.tistory.com/196</guid>
      <comments>https://oniic.tistory.com/196#entry196comment</comments>
      <pubDate>Sun, 14 Jun 2026 00:26:16 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;DB&amp;gt; 인덱스란?</title>
      <link>https://oniic.tistory.com/195</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;인덱스란&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;데이터베이스에서의 인덱스는 &lt;b&gt;데이터를 빠르게 찾기 위한 색인&lt;/b&gt;이다. 일상 생활에서 책 내용을 찾을려면 처음부터 끝까지 모두 확인하는게 아닌 책 뒤의 특정 단어(색인)를 통해 바로 찾을 수 있다. 데이터베이스의 인덱스도 이와 동일한 개념이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인덱스 동작 원리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;대부분의 관계형 데이터베이스에서 인덱스는 &lt;b&gt;B+ Tree 구조&lt;/b&gt;를 사용하며, 원본 테이블과는 별개의 특수한 자료 구조다. 인덱스는 지정된 컬럼의 값과, 해당 값을 가진 실제 데이터 행의 위치(예: 주소값, 포인터,pk 등)를 한 쌍으로 지정하고, &lt;b&gt;인덱스 내부의 데이터는 항상 정렬된 상태를 유지&lt;/b&gt;한다. (아래는 items 테이블과 items 테이블의 item_name으로 인덱스를 나타낸 그림이다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;570&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLis2q/dJMcahrciWH/3jhO2ruiUQkvVBeWCy0ca1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLis2q/dJMcahrciWH/3jhO2ruiUQkvVBeWCy0ca1/img.png&quot; data-alt=&quot;김영한의 실전 데이터베이스 기본편/인프런&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLis2q/dJMcahrciWH/3jhO2ruiUQkvVBeWCy0ca1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLis2q%2FdJMcahrciWH%2F3jhO2ruiUQkvVBeWCy0ca1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;361&quot; data-origin-width=&quot;842&quot; data-origin-height=&quot;570&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;김영한의 실전 데이터베이스 기본편/인프런&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앞서 인덱스는 B+ Tree 구조를 가지고 있다고 했는데 여기서 B+ Tree 구조를 간단히 나타내면 아래와 같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;628&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EhdQG/dJMcafGUBci/ShkEd1UjZvvFmOoKJZE8iK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EhdQG/dJMcafGUBci/ShkEd1UjZvvFmOoKJZE8iK/img.png&quot; data-alt=&quot;김영한의 실전 데이터베이스 기본편/인프런&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EhdQG/dJMcafGUBci/ShkEd1UjZvvFmOoKJZE8iK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEhdQG%2FdJMcafGUBci%2FShkEd1UjZvvFmOoKJZE8iK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;534&quot; height=&quot;403&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;628&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;김영한의 실전 데이터베이스 기본편/인프런&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위의 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;자료구조를 통해 데이터를 조회 하는 방식은 아래과 같으며 &lt;/span&gt;&lt;span data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 500; margin: 0px; text-decoration: none; border-bottom: 0px rgb(0, 29, 53);&quot; data-sfc-cb=&quot;&quot; data-sfc-root=&quot;c&quot; data-sfc-cp=&quot;&quot;&gt;알고리즘의 효율성을 나타내는 수학적 표기법으로 나타내면 O(log n)과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2개의 데이터 2로 1번 나누기, log₂(2)=1&lt;/li&gt;
&lt;li&gt;4개의 데이터 2로 2번 나누기, log₂(4)=2&lt;/li&gt;
&lt;li&gt;8개의 데이터 2로 3번 나누기, log₂(8)=3&lt;/li&gt;
&lt;li&gt;16개의 데이터 2로 4번 나누기, log₂(16)=4&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;16,384개의 데이터 2로 14번 나누기, log₂ (16384)=14&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;li&gt;1,000,000개의 데이터 2로 약 20번 나누기, log₂ (1,000,000)&amp;asymp;19.93&lt;/li&gt;
&lt;li&gt;100,000,000개의 데이터 2로 약 27번 나누기, log₂ (100,000,000)&amp;asymp;26.57&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 인덱스를 사용하게 되면 데이터가 많아질수록 차이가 극적으로 커지게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인덱스 생성, 조회, 삭제&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1780894703261&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;-- 생성
CREATE INDEX 인덱스이름 ON 테이블이름 (컬럼1, 컬럼2, ...);

-- 조회
SHOW INDEX FROM 테이블이름;

-- 삭제
DROP INDEX 인덱스이름 ON 테이블이름;&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인덱스 장점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;1. 조회 속도가 압도적으로 빨라진다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;gt; 인덱스가 있으면 B+ Tree 구조를 이용해 조회 속도가 빠르지만 &lt;b&gt;인덱스가 없으면 Full Table Scan으로 전체 테이블을 순차적으로 읽어야&lt;/b&gt; 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;2. WHERE 조건 검색이 빨라진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; 검색 조건에 사용되는 컬럼에 인덱스가 있으면 빨라진다. (&lt;b&gt;반드시 인덱스에 WHERE 조건할 컬럼이 있어야 한다.&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;3. 정렬이 빨라질 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; 인덱스가 없으면 전체 데이터 조회 후 정렬을 해야 하지만 인덱스가 있으면 이미 정렬된 인덱스 순서를 사용하기 때문에 정렬 비용이 크게 감소한다. (&lt;b&gt;이것도 반드시 ORDER BY 할 컬럼이 인덱스에 있어야 한다.&lt;/b&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;4. JOIN 성능 향상&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1780895850788&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT *
FROM orders o
JOIN users u
ON o.user_id = u.id;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;user_id나 id에 인덱스가 있으면 &lt;b&gt;JOIN할 사용자 테이블 위치를 즉시 탐색&lt;/b&gt;하기 때문에 성능이 향상 된다. 참고로 &lt;b&gt;MYSQL에서는 PRIMARY KEY (기본 키)나 FOREIGN KEY (외래 키) 제약조건을 설정하면, 해당 컬럼에 대해 자동으로 인덱스를 생성&lt;/b&gt;한다. UNIQUE 제약조건도 마찬가지로 인덱스가 생성된다.)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;인덱스 단점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;1. 저장 공간을 추가로 사용한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; 만약 테이블이 하나가 있고 인덱스를 N개 생성하였으면 인덱스 별도로 N개의 저장 공간이 필요하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. INSERT, UPDATE, DELETE가 느려진다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; 테이블에 삭제, 수정, 삭제를 하게 되면 &lt;b&gt;생성된 인덱스 내부 데이터를 추가, 수정, 삭제를 해야하고 추가로 트리를 재정렬 해야하기 때문에 추가 비용이 발생&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;인덱스는 데이터베이스에서 특정 데이터를 빠르게 찾기 위해 만드는 &quot;색인&quot;이며, 보통 B+ Tree 자료구조를 사용하여 전체 데이터를 훑는 O(N) 탐색을 O(log N) 수준으로 줄여 성능을 크게 향상시킨다. 대신 저장 공간을 추가로 사용하고 INSERT/UPDATE/DELETE 성능은 일부 감소한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;본 포스팅은 &amp;ldquo;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;김영한의 실전 데이터베이스 기본편/&lt;/span&gt;인프런&amp;rdquo;를 학습한 내용을 정리한 것&lt;/b&gt;&lt;/p&gt;</description>
      <category>DB/DB</category>
      <category>DB 인덱스</category>
      <category>인덱스란</category>
      <author>면목동인간</author>
      <guid isPermaLink="true">https://oniic.tistory.com/195</guid>
      <comments>https://oniic.tistory.com/195#entry195comment</comments>
      <pubDate>Mon, 8 Jun 2026 14:38:57 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;JPA&amp;gt; OSIV이란?</title>
      <link>https://oniic.tistory.com/194</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;OSIV이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA의 &lt;b&gt;OSIV(Open Session In View)&lt;/b&gt; 는 웹 요청이 시작될 때 영속성 컨텍스트(EntityManager)를 생성하고, 응답이 끝날 때까지 유지하는 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OSIV 설정 방법&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;아래는 Spring Boot 환경에서 yaml 파일에 대한 설정 방법이고, Spring Boot에서는 기본적으로 OSIV가 활성화되어 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780495292295&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  jpa:
    open-in-view: true&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OSIV가 필요한 이유&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;JPA의 연관관계가 LAZY 로딩일 때 문제가 발생할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780495394034&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 엔티티
@Entity
public class Order {

    @ManyToOne(fetch = FetchType.LAZY)
    private Member member;
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1780495439268&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 서비스
@Transactional
public Order findOrder(Long id) {
    return orderRepository.findById(id).get();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1780495457848&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 컨트롤러
@GetMapping(&quot;/orders/{id}&quot;)
public String order(@PathVariable Long id) {

    Order order = orderService.findOrder(id);

    String memberName = order.getMember().getName();

    return memberName;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;OSIV가 false일 경우&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;서비스 메서드 종료&lt;/li&gt;
&lt;li data-end=&quot;838&quot; data-start=&quot;828&quot; data-section-id=&quot;fc1btt&quot;&gt;트랜잭션 종료&lt;/li&gt;
&lt;li data-end=&quot;853&quot; data-start=&quot;839&quot; data-section-id=&quot;1fji6d1&quot;&gt;영속성 컨텍스트 종료&lt;/li&gt;
&lt;li data-end=&quot;886&quot; data-start=&quot;854&quot; data-section-id=&quot;1rf5lig&quot;&gt;컨트롤러에서 order.getMember() 호출&lt;/li&gt;
&lt;li data-end=&quot;886&quot; data-start=&quot;854&quot; data-section-id=&quot;1rf5lig&quot;&gt;Lazy Loading 불가&lt;/li&gt;
&lt;li data-end=&quot;886&quot; data-start=&quot;854&quot; data-section-id=&quot;1rf5lig&quot;&gt;LazyInitializationException 에러 발생&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;OSIV가 true일 경우&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;995&quot; data-start=&quot;968&quot; data-section-id=&quot;1j3asnq&quot;&gt;요청 시작 시 EntityManager 생성&lt;/li&gt;
&lt;li data-end=&quot;1017&quot; data-start=&quot;996&quot; data-section-id=&quot;1myniel&quot;&gt;서비스 트랜잭션 종료 후에도 유지&lt;/li&gt;
&lt;li data-end=&quot;1043&quot; data-start=&quot;1018&quot; data-section-id=&quot;1wfxz29&quot;&gt;컨트롤러에서 Lazy Loading 가능&lt;/li&gt;
&lt;li data-end=&quot;1043&quot; data-start=&quot;1018&quot; data-section-id=&quot;1wfxz29&quot;&gt;응답 완료 시 EntityManager 종료&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OSIV 동작 구조&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OSIV가 true인 경우&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdWKvX/dJMcabxDrBz/XGXfPWrlxovWkrzfqc3B8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdWKvX/dJMcabxDrBz/XGXfPWrlxovWkrzfqc3B8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdWKvX/dJMcabxDrBz/XGXfPWrlxovWkrzfqc3B8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdWKvX%2FdJMcabxDrBz%2FXGXfPWrlxovWkrzfqc3B8k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;901&quot; height=&quot;422&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;901&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;OSIV가 true인 경우 데이터베이스 커넥션 시작 시점부터 API 응답이 끝날 때 까지 영속성 컨텍스트 와 데이터베이스 커넥션을 유지한다. 그래서 View Template이나 API 컨트롤러에서 지연 로딩이 가능했던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OSIV가 false인 경우&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWwJzE/dJMcab5uOqI/AlYRlTYnk2hRsZGV6zU3Z0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWwJzE/dJMcab5uOqI/AlYRlTYnk2hRsZGV6zU3Z0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWwJzE/dJMcab5uOqI/AlYRlTYnk2hRsZGV6zU3Z0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWwJzE%2FdJMcab5uOqI%2FAlYRlTYnk2hRsZGV6zU3Z0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;907&quot; height=&quot;430&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;OSIV가 false인 경우 트랜잭션을 종료할 때 영속성 컨텍스트를 닫고, 데이터베이스 커넥션도 반환한다. 그리고 모든 지연로딩을 트랜잭션 안에서 처리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OSIV 장점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1. Lazy Loading이 편리&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; 별도 조회 없이 접근 가능&lt;/p&gt;
&lt;pre id=&quot;code_1780496285562&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;order.getMember().getName();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2. 개발 생산성 향상&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; 서비스 계층에서 필요한 데이터를 전부 조회해둘 필요가 없고, 컨트롤러에서 연관 객체 접근 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;OSIV 단점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1. 데이터베이스 커넥션을 오래 점유&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; OSIV true 상태에서는 영속성 컨텍스트가 요청 끝까지 유지된다. 상황에 따라 DB 커넥션이 예상보다 오래 사용될 수 있어 트래픽이 많아지면 문제가 될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2. N+1 문제를 숨긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; 컨트롤러에서 예상치 못한 추가 쿼리가 발생할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1780496464906&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 컨트롤러
orders.forEach(order -&amp;gt; {
    System.out.println(order.getMember().getName());
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1780496495078&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select * from orders;

select * from member where id=?
select * from member where id=?
select * from member where id=?
...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3. 계층 분리가 흐려짐&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;-&amp;gt; 원칙적으로는 Controller-&amp;gt;Service-&amp;gt;Repository 인데 Controller에서 Lazy Loading이 발생하게 되면 조회 로직이 프레젠테이션 계층까지 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실무에서는?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;실무에서는 &lt;b&gt;OSIV를 끄는 경우가 많으며,&amp;nbsp;&lt;/b&gt;서비스 계층에서 Fetch Join과 DTO을 사용하여 조회한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1780496729509&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  jpa:
    open-in-view: false&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;OSIV는 요청 시작부터 응답 완료 시점까지 영속성 컨텍스트를 유지하여 &lt;b&gt;트랜잭션 종료 이후에도 Lazy Loading을 가능&lt;/b&gt;하게 하는 기능이다. 다만 개발 편의성을 높여주지만 성능 문제를 숨길 수 있기 때문에, &lt;b&gt;실무에서는 OFF 후 Fetch Join&amp;middot;DTO 조회를 적극 활용&lt;/b&gt;하는 방식이 많이 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;본 포스팅은 &amp;ldquo; &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;/인프런&amp;rdquo;를&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;학습한 내용을 정리한 것&lt;/b&gt;&lt;/p&gt;</description>
      <category>Java/JPA</category>
      <category>jpa</category>
      <category>JPA OSIV</category>
      <category>OSIV이란</category>
      <author>면목동인간</author>
      <guid isPermaLink="true">https://oniic.tistory.com/194</guid>
      <comments>https://oniic.tistory.com/194#entry194comment</comments>
      <pubDate>Wed, 3 Jun 2026 23:37:06 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Spring Data JPA&amp;gt; 쿼리 메서드란?</title>
      <link>https://oniic.tistory.com/193</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;쿼리 메서드란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Spring Data JPA에서 제공하는 쿼리 메서드 기능은 &lt;b&gt;메서드 이름만으로 SQL/JPQL 쿼리를 자동 생성해주는 기능&lt;/b&gt;이다. 즉, 개발자가 쿼리를 작성하지 않아도 Repository 인터페이스에 메서드 작성만으로 DB 조회가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;메서드 이름 규칙&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;쿼리 메서드는 아래와 같이 메서드 이름 규칙으로 만들어야 실제 JPQL로 변경하여 사용할 수 있으며, &lt;b&gt;키워드 + Entity 필드명 + CamelCase을 정확히 맞춰서 작성&lt;/b&gt;해야한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 조회 키워드&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;findBy&lt;/li&gt;
&lt;li&gt;readBy&lt;/li&gt;
&lt;li&gt;getBy&lt;/li&gt;
&lt;li&gt;queryBy&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1778294713904&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;findByUsername(String username)
// SELECT u FROM User u WHERE u.username = :username&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 조건 키워드&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Equals (생략 가능)&lt;/li&gt;
&lt;li&gt;Is&lt;/li&gt;
&lt;li&gt;Not&lt;/li&gt;
&lt;li&gt;GreaterThan&lt;/li&gt;
&lt;li&gt;LessThan&lt;/li&gt;
&lt;li&gt;Between&lt;/li&gt;
&lt;li&gt;Like&lt;/li&gt;
&lt;li&gt;Containing&lt;/li&gt;
&lt;li&gt;StartingWith&lt;/li&gt;
&lt;li&gt;EndingWith&lt;/li&gt;
&lt;li&gt;IsNull&lt;/li&gt;
&lt;li&gt;IsNotNull&lt;/li&gt;
&lt;li&gt;True&lt;/li&gt;
&lt;li&gt;False&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1778294951875&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;findByUsername(String username)
// SELECT u FROM User u WHERE u.username = :username

findByUsernameIs(String username)
// SELECT u FROM User u WHERE u.username = :username

findByUsernameNot(String username)
// SELECT u FROM User u WHERE u.username &amp;lt;&amp;gt; :username

findByAgeGreaterThan(int age)
// SELECT u FROM User u WHERE u.age &amp;gt; :age

findByCreatedDateBetween(LocalDate start, LocalDate end)
// SELECT u FROM User u WHERE u.age BETWEEN :start AND :end

findByUsernameContaining(String keyword)
// SELECT u FROM User u WHERE u.username LIKE %:keyword%

findByEmailEndingWith(String domain)
// SELECT u FROM User u WHERE u.username LIKE %:suffix

findByDeletedAtIsNull()
// SELECT u FROM User u WHERE u.deletedAt IS NULL

findByActiveTrue()
// SELECT u FROM User u WHERE u.active = true&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. AND/OR 조건&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;And&lt;/li&gt;
&lt;li&gt;Or&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1778295461616&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;findByUsernameAndEmail(String username, String email)
// SELECT u FROM User u WHERE u.username = :username AND u.email = :email

findByUsernameOrEmail(String username, String email)
// SELECT u FROM User u WHERE u.username = :username OR u.email = :email&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 정렬&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OrderBy...Desc/Asc&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1778295504833&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;findByAgeOrderByUsernameDesc(int age);
// SELECT u FROM User u WHERE u.age = :age ORDER BY u.username DESC&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 개수/존재 여부&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;countBy&lt;/li&gt;
&lt;li&gt;existBy&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1778295525922&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;long countByUsername(String username);
// SELECT COUNT(u) FROM User u WHERE u.username = :username

boolean existsByEmail(String email);
// SELECT CASE WHEN COUNT(u) &amp;gt; 0 THEN true ELSE false END FROM User u WHERE u.email = :email&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;쿼리 메서드 주의할 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;쿼리 메서드는 너무 길어지면 가독성이 떨어지기 때문에 길어지면 @Query 사용을 권장한다. 그리고 &lt;b&gt;Entity 필드명으로 JPQL이 생성 되기 때문에 Entity 필드명이 바뀔 경우 메서드도 추가로 수정&lt;/b&gt;해야 한다. &lt;b&gt;그렇지 않으면 애플리케이션 시작하는 시점에 오류가 발생&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;쿼리 메서드를 언제 사용 하는게 좋을까?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;쿼리 메서드를 추천 하는 경우 1. 단순 조회 2. 조건 1~3개 정도 3. 빠른 개발 이며 비추천 하는 경우 1. 복잡한 통계 쿼리 2. 다중 JOIN 3. 성능 튜닝이 필요한 경우 이렇게 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Spring Data JPA의 쿼리 메서드는 메서드 이름을 규칙(CamelCase + 키워드)에 따라 해석해 자동으로 JPQL을 생성하는 기능이다. 주로 SELECT 조회에 사용되며, DELETE는 지원하지만 UPDATE는 지원하지 않아 @Query나 변경 감지를 사용해야 한다. 간단한 조건 조회에는 매우 편리하지만, 복잡한 쿼리는 가독성과 한계 때문에 별도의 방법을 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;본 포스팅은 &amp;ldquo;실전! 스프링 데이터 JPA&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;/인프런&amp;rdquo;를&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;학습한 내용을 정리한 것&lt;/b&gt;&lt;/p&gt;</description>
      <category>Java/Spring Data JPA</category>
      <category>Spring Data JPA</category>
      <category>Spring Data JPA 쿼리 메서드</category>
      <category>쿼리 메서드란</category>
      <author>면목동인간</author>
      <guid isPermaLink="true">https://oniic.tistory.com/193</guid>
      <comments>https://oniic.tistory.com/193#entry193comment</comments>
      <pubDate>Sat, 9 May 2026 12:39:38 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;JPA&amp;gt; 일대다(1:N) 단방향 연관관계 맵핑 시 주의할 점</title>
      <link>https://oniic.tistory.com/192</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;일대다(1:N) 연관관계란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;일대다(1:N) 연관관계는 하나의 엔티티가 여러 개의 엔티티와 관계를 맺는 구조를 의미한다. 예를 들어 객체 구조에서 Team과 Member가 있을 때, 하나의 Team에는 여러 명의 Member가 속할 수 있고 각 Member는 하나의 Team에만 속하게 된다. 이러한 관계를 데이터베이스 테이블로 설계할 경우, 외래키는 항상 &amp;lsquo;다(N)&amp;rsquo;에 해당하는 쪽에 위치하게 되므로 Member 테이블에 Team의 기본키를 참조하는 외래키가 포함된다. 이때 일대다 연관관계에서 일 쪽에 외래키를 관리하며 연관관계의 주인이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;예제&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;아래의 코드는 일대다 관계에서 일 쪽에 단방향 연관관계의 주인이 될 때 주의할 점이다.&lt;/p&gt;
&lt;pre id=&quot;code_1777443190379&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Member {

    @Id
    @GeneratedValue
    @Column(name = &quot;MEMBER_ID&quot;)
    private Long id;
    
    private String name;
    
    // getter, setter 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1777514524506&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Team {

    @Id
    @GeneratedValue
    @Column(name = &quot;TEAM_ID&quot;)
    private Long id;
    
    private String name;
    
    @OneToMany
    @JoinColumn(name = &quot;TEAM_ID&quot;)
    private List&amp;lt;Member&amp;gt; members = new ArrayList&amp;lt;&amp;gt;();
    
    // getter, setter 생략
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1777443289883&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class JpaMain {

    public static void main(String[] args) {

        // 엔티티 매니저 생성 및 트랜잭션 생략

        Member member1 = new Member();
        member1.setName(&quot;member1&quot;);
        em.persist(member1);

        Team team1 = new Team();
        team1.setName(&quot;Team1&quot;);
        team1.getMembers().add(member1);
        em.persist(team1);

        // 엔티티 매니저 종료 및 트랜잭션 생략
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행결과&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1777443345369&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hibernate: 
    /* insert for
        hellojpa.Member */insert 
    into
        Member (name, MEMBER_ID) 
    values
        (?, ?)
Hibernate: 
    /* insert for
        hellojpa.Team */insert 
    into
        Team (name, TEAM_ID) 
    values
        (?, ?)
Hibernate: 
    update
        Member 
    set
        TEAM_ID=? 
    where
        MEMBER_ID=?&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위의 코드를 실행하니 쿼리가 3개가 나왔다. 분명 영속한 객체(insert 하는)는 2개인데 insert 2개랑 update 1개 발생하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;update가 발생한 이유?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;242&quot; data-start=&quot;61&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;지금 Team이 연관관계 주인이며, Member를 컬렉션으로 가지고 있는 단방향 관계이다. 즉, 외래키는 Member 테이블에 존재하지만, 이를 설정하는 주체는 Team이다. 코드를 보면 먼저 Member를 생성하고 persist를 호출하여 insert가 실행된다. 이 시점에는 Member가 어떤 Team에도 속해 있지 않기 때문에, 외래키 값(Team의 FK)은 null인 상태로 저장된다. 이후 Team을 생성하고 member1을 Team의 members 컬렉션에 추가한 뒤 persist를 호출하면, JPA는 이 시점에서 연관관계가 설정되었다고 인식한다. 하지만 이미 Member는 insert 된 상태이므로, 새로 insert 할 수는 없고 기존 데이터의 외래키 값을 수정해야 한다. 따라서 JPA는 Member 테이블의 외래키를 업데이트하기 위해 추가로 update 쿼리를 실행하게 된다.&lt;/p&gt;
&lt;p data-end=&quot;714&quot; data-start=&quot;598&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;정리하면, &lt;b&gt;Member를 먼저 저장할 때는 연관관계 정보가 없어서 FK가 null로 insert되고, 이후 Team에 Member를 추가하면서 FK 값을 반영하기 위해 update가 발생하는 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-end=&quot;714&quot; data-start=&quot;598&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-end=&quot;714&quot; data-start=&quot;598&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구조를 지양하는 이유?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-end=&quot;140&quot; data-start=&quot;61&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 구조(일대다 단방향 + Team이 연관관계 주인)를 실무에서 잘 사용하지 않는 이유는 &lt;b&gt;성능과 설계 측면에서 비효율적이기 때문이며, &lt;/b&gt;가장 큰 문제는 &lt;b&gt;불필요한 update 쿼리 발생이다. &lt;/b&gt;앞서 본 것처럼 &lt;b&gt;insert 한 번으로 끝낼 수 있는 작업이 insert + update로 늘어나 성능 손해가 발생&lt;/b&gt;한다. 또한 이 방식은 &lt;b&gt;데이터 흐름이 직관적이지 않는다. &lt;/b&gt;실제 데이터베이스에서는 외래키가 Member 테이블에 존재하기 때문에, Member가 Team을 참조하는 구조가 자연스럽다. 그런데 객체에서는 Team이 Member를 관리하게 되므로, &lt;b&gt;객체 구조와 DB 구조가 어긋나는 문제가 생긴다.&lt;/b&gt; 이로 인해 유지보수 시 혼란이 발생할 수 있다.&lt;/p&gt;
&lt;p data-end=&quot;689&quot; data-start=&quot;570&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-end=&quot;689&quot; data-start=&quot;570&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-end=&quot;689&quot; data-start=&quot;570&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;일대다 단방향 연관관계에서 &amp;lsquo;일(1)&amp;rsquo; 쪽을 연관관계의 주인으로 설정하면, 실제 외래키는 &amp;lsquo;다(N)&amp;rsquo; 쪽 테이블에 존재함에도 불구하고 이를 &amp;lsquo;일&amp;rsquo; 쪽에서 관리하게 되어 구조가 비직관적이 된다. 이로 인해 엔티티를 저장할 때 외래키 값이 즉시 반영되지 못하고, insert 이후 추가적인 update 쿼리가 발생하는 비효율이 생긴다. 또한 객체 구조와 데이터베이스 구조가 어긋나 유지보수와 성능 측면에서 불리하다. &lt;b&gt;따라서 실무에서는 일대다 단방향 대신, 외래키를 가진 &amp;lsquo;다(N)&amp;rsquo; 쪽을 연관관계의 주인으로 하는 다대일 중심의 설계를 사용하는 것이 바람직하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-end=&quot;689&quot; data-start=&quot;570&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;본 포스팅은 &amp;ldquo;자바 ORM 표준 JPA 프로그래밍&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp;- 기본편&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;/인프런&amp;rdquo;를&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;학습한 내용을 정리한 것&lt;/b&gt;&lt;/p&gt;</description>
      <category>Java/JPA</category>
      <category>jpa</category>
      <category>JPA 일대다(1:N) 연관관계</category>
      <author>면목동인간</author>
      <guid isPermaLink="true">https://oniic.tistory.com/192</guid>
      <comments>https://oniic.tistory.com/192#entry192comment</comments>
      <pubDate>Wed, 29 Apr 2026 15:51:51 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Docker&amp;gt; 볼륨(Volume)이란?</title>
      <link>https://oniic.tistory.com/191</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;볼륨이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;볼륨이란 &lt;b&gt;도커 컨테이너에서 데이터를 영속적으로 저장하기 위한 방법&lt;/b&gt;이다. 볼륨은 컨테이너 내의 저장 공간을 사용하지 않고, 호스트 내의 저장 공간을 공유해서 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/p7hdB/dJMcafsylzh/T3fFgC41HzRsBEKb9zLsh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/p7hdB/dJMcafsylzh/T3fFgC41HzRsBEKb9zLsh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/p7hdB/dJMcafsylzh/T3fFgC41HzRsBEKb9zLsh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fp7hdB%2FdJMcafsylzh%2FT3fFgC41HzRsBEKb9zLsh1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;680&quot; height=&quot;379&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;793&quot; data-origin-height=&quot;442&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;볼륨을 사용하는 이유?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;예를 들어 mysql의 이미지로 컨테이너를 실행시켰는데, mysql 업데이트를 하게 되어 새로운 mysql 이미지로 컨테이너를 띄우게 된다면 기존 내부에 있던 데이터가 같이 삭제하게 된다. 이를 막기 위해 볼륨을 활용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;볼륨 활용 전 예제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;아래 예제는 mysql 컨테이너를 실행한 후 실제 데이터베이스를 만들고 볼륨을 활용하기 전과 후의 차이를 비교하는 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1775110856682&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -d mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;mysql 이미지를 받고 컨테이너까지 실행하는 명령어다.&lt;/p&gt;
&lt;pre id=&quot;code_1775111138645&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker exec -it [MySQL 컨테이너 ID] bash&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;실행 중인 mysql 컨테이너에 접근하는 명령어이다.&lt;/p&gt;
&lt;pre id=&quot;code_1775111268774&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql -u root -p

show databases;

create database testDb

show databaeses;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;컨테이너에 접근 후 위의 명령어를 입력하면 아래와 같이 mysql 서버에 들어간 것이다. 그 후 데이터베이스를 생성하고 목록을 보면 아래와 같이 나올 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;308&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/niG2h/dJMcafTDZ5j/Iy9ZHqSt0xFNTr1G3MJESK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/niG2h/dJMcafTDZ5j/Iy9ZHqSt0xFNTr1G3MJESK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/niG2h/dJMcafTDZ5j/Iy9ZHqSt0xFNTr1G3MJESK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FniG2h%2FdJMcafTDZ5j%2FIy9ZHqSt0xFNTr1G3MJESK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;341&quot; height=&quot;308&quot; data-filename=&quot;2.png&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;308&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1775111637495&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker stop [MySQL 컨테이너 ID]
docker rm [MySQL 컨테이너 ID]

docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -d mysql
docker exec -it [MySQL 컨테이너 ID] bash

mysql -u root -p
show databases;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그 후 컨테이너를 종료 하고 삭제 후 다시 컨테이너를 실행하게 되면 아래와 같이 데이터베이스가 없어진 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;272&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eg1vWH/dJMcah4ZPhN/URnAl7ktY0WjofjPJm7Oh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eg1vWH/dJMcah4ZPhN/URnAl7ktY0WjofjPJm7Oh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eg1vWH/dJMcah4ZPhN/URnAl7ktY0WjofjPJm7Oh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feg1vWH%2FdJMcah4ZPhN%2FURnAl7ktY0WjofjPJm7Oh0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;272&quot; height=&quot;235&quot; data-filename=&quot;3.png&quot; data-origin-width=&quot;272&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;볼륨 활용 후 예제&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;볼륨을 활용하기 앞서 호스트의 저장 공간에 데이터를 저장하기 위해 다음과 같이 별도의 저장할 디렉터리를 만들어야 한다. ex) C:\Users\oniic\docker-mysql&lt;/p&gt;
&lt;pre id=&quot;code_1775112110373&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -e MYSQL_ROOT_PASSWORD=password123 -p 3306:3306 -v &quot;C:/Users/oniic/docker-mysql/mysql_data:/var/lib/mysql&quot; -d mysql&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위의 명령어에서 -v 옵션에서 앞이 호스트에 공유할 경유이고 뒤가 컨테이너의 데이터가 저장되는 경로이다. 명령어를 실행하면 호스트의 mysql 관련 데이터가 아래와 같이 저장된 것을 확인할 수 있다. 이제 mysql 접속 후 데이터베이스를 생성하고 컨테이너를 종료 및 제거 후 다시 실행하면 데이터베이스가 제거되지 않은 것으로 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;700&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eHUMv1/dJMcahcP4sC/f55a6L432MCZbUHfKpWMt1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eHUMv1/dJMcahcP4sC/f55a6L432MCZbUHfKpWMt1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eHUMv1/dJMcahcP4sC/f55a6L432MCZbUHfKpWMt1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeHUMv1%2FdJMcahcP4sC%2Ff55a6L432MCZbUHfKpWMt1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;365&quot; height=&quot;279&quot; data-filename=&quot;4.png&quot; data-origin-width=&quot;916&quot; data-origin-height=&quot;700&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;여기서 주의할 점이 있는데 호스트 디렉토리에서 mysql_data를 미리 만들어 놓으면 안 된다. 그래야 처음 이미지를 실행시킬 때 mysql 내부에 있는 /var/lib/mysql 파일들을 호스트 컴퓨터로 공유받을 수 있다. mysql_data 디렉터리를 미리 만들어놓을 경우, 기존 컨테이너의 /var/lib/mysql 파일들을 전부 삭제한 뒤에 mysql_data로 덮어씌워 버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;볼륨은 컨테이너의 데이터를 영속적으로 저장하기 위한 메커니즘이다. 기본적으로 컨테이너는 삭제되면 내부 데이터도 함께 사라지는데 이를 해결하기 위해 볼륨을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;본 포스팅은 &amp;ldquo;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;비전공자도 이해할 수 있는 Docker 입문/실전 /&lt;/span&gt;인프런&amp;rdquo;를 학습한 내용을 정리한 것&lt;/b&gt;&lt;/p&gt;</description>
      <category>DevOps/Docker</category>
      <category>Docker</category>
      <category>Docker volumn</category>
      <category>도커 볼륨</category>
      <author>면목동인간</author>
      <guid isPermaLink="true">https://oniic.tistory.com/191</guid>
      <comments>https://oniic.tistory.com/191#entry191comment</comments>
      <pubDate>Thu, 2 Apr 2026 16:23:18 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;Docker&amp;gt; 도커란?</title>
      <link>https://oniic.tistory.com/190</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;도커란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;도커는 컨테이너를 사용하여 각각의 프로그램을 분리된 환경에서 실행 및 관리할 수 있는 툴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;컨테이너란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;도커에서 컨테이너란 하나의 컴퓨터 내에서 독립적인 여러 개의 컴퓨터 환경을 구성할 수 있는데 그 여러 개 중의 하나의 컴퓨터를 컨테이너라고 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;967&quot; data-origin-height=&quot;578&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/1lL7y/dJMcabctcdY/Ch1QiXwblSk4OxUk4soW2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/1lL7y/dJMcabctcdY/Ch1QiXwblSk4OxUk4soW2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/1lL7y/dJMcabctcdY/Ch1QiXwblSk4OxUk4soW2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F1lL7y%2FdJMcabctcdY%2FCh1QiXwblSk4OxUk4soW2K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;525&quot; height=&quot;314&quot; data-filename=&quot;1.png&quot; data-origin-width=&quot;967&quot; data-origin-height=&quot;578&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위의 컨테이너들은 컴퓨터 내에서 각각 독립적인 컴퓨터 환경이라고 보면 되고, &lt;b&gt;각 컨테이너마다 각자의 저장 공간을 가지고 있으며, 일반적으로는 A 컨테이너 내부에서 B 컨테이너 내부에 있는 파일에 접근할 수 없다.&lt;/b&gt; 그리고 &lt;b&gt;각 컨테이너마다 고유의 네트워크를 가지고 있다(각각 ip를 가지고 있다).&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이미지란?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;도커에서 &lt;b&gt;이미지란 프로그램을 실행하는데 필요한 설치 과정, 설정, 버전 정보 등을 포함&lt;/b&gt;하고 있다. 즉, 프로그램을 실행하는데 필요한 모든 것을 포함하고 있다. 예를 들어 MySql 서버를 이미지로 만들었다면, 이 이미지를 Docker로 실행시키는 순간 MySql 서버가 컨테이너 환경에서 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;도커는 컨테이너를 사용하여 프로그램을 분리된 환경에서 실행할 수 있고 이미지는 프로그램을 실행하는데 필요한 정보이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;본 포스팅은 &amp;ldquo;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;비전공자도 이해할 수 있는 Docker 입문/실전 /&lt;/span&gt;인프런&amp;rdquo;를 학습한 내용을 정리한 것&lt;/b&gt;&lt;/p&gt;</description>
      <category>DevOps/Docker</category>
      <category>Docker</category>
      <category>Docker이란</category>
      <author>면목동인간</author>
      <guid isPermaLink="true">https://oniic.tistory.com/190</guid>
      <comments>https://oniic.tistory.com/190#entry190comment</comments>
      <pubDate>Mon, 23 Mar 2026 21:19:43 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;DB&amp;gt; 뷰(View)이란?</title>
      <link>https://oniic.tistory.com/189</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;뷰(View)이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;뷰는 가상의 테이블이다. 즉 데이터를 가지고 있지 않는다. 뷰는 데이터를 저장하는 테이블이 아니고, 복잡한 쿼리문 자체를 저장하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;뷰(View)를 사용하는 이유&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;편리성: 복잡한 쿼리를 단순화하여 쉽게 재사용 가능.&lt;/li&gt;
&lt;li&gt;보안성: 원본 테이블에 대한 접근 권한을 주지 않고, 뷰를 통해서만 제한된 데이터에 접근하도록 허용할 수 있다.&lt;/li&gt;
&lt;li&gt;논리적 독립성: 원본 테이블의 구조가 일부 변경 되더라도, 뷰의 정의만 수정하면 뷰를 사용하는 프로그램의 코드는 바꿀 필요가 없을 수도 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;뷰 생성&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1773499018875&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE VIEW 뷰이름 AS SELECT 쿼리문;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1773499038202&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DROP VIEW IF EXISTS v_category_order_status; -- 만약 뷰가 이미 존재한다면 제거
CREATE VIEW v_category_order_status AS
SELECT
    p.category,
COUNT(*) AS total_orders,
SUM(CASE WHEN o.status = 'COMPLETED' THEN 1 ELSE 0 END) AS 
completed_count,
SUM(CASE WHEN o.status = 'SHIPPED' THEN 1 ELSE 0 END) AS shipped_count,
SUM(CASE WHEN o.status = 'PENDING' THEN 1 ELSE 0 END) AS pending_count
FROM
    orders o
JOIN
    products p ON o.product_id = p.product_id
GROUP BY
    p.category;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;뷰 조회하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1773499061970&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM v_category_order_status;

-- WHERE 절이나 ORDER BY 사용 가능
SELECT *
FROM v_category_order_status
WHERE category = '전자기기';&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;뷰 수정하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1773499149130&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ALTER VIEW v_category_order_status AS
SELECT
    p.category,
SUM(p.price * o.quantity) AS total_sales, -- 매출액 컬럼 추가!
COUNT(*) AS total_orders,
SUM(CASE WHEN o.status = 'COMPLETED' THEN 1 ELSE 0 END) AS 
completed_count,
SUM(CASE WHEN o.status = 'SHIPPED' THEN 1 ELSE 0 END) AS shipped_count,
SUM(CASE WHEN o.status = 'PENDING' THEN 1 ELSE 0 END) AS pending_count
FROM
    orders o
JOIN
    products p ON o.product_id = p.product_id
GROUP BY
    p.category;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;뷰 삭제하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1773499170039&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DROP VIEW v_category_order_status;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;뷰를 삭제해도 원본 테이블의 데이터는 단 하나도 손상되거나 변경되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;뷰(View)의 장점&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;편리성과 재사용성: 복잡한 SELECT 쿼리를 뷰 뒤에 숨길 수 있다.&lt;/li&gt;
&lt;li&gt;보안성: 뷰는 데이터베이스에 대한 권한 제어를 가능하게 한다. 개인 정보 칼럼과 같은 민감한 정보에 대한 권한 제어가 가능하고, 특정 칼럼만 노출하게 할 수 있다.&lt;/li&gt;
&lt;li&gt;논리적 데이터 독립성: 데이터베이스 구조가 변경되더라도 기존과 같은 형태의 뷰를 만들어 두면, 응용 프로그램은 실제 테이블 구조가 바뀐 것을 알 필요 없이 기존 코드 그대로 사용할 수 있다. 즉, 뷰는 내부 테이블 구조의 변화를 숨겨 주는 추상화 계층 역할을 한다. &amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;뷰(View)의 단점&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;성능 문제: 뷰 안에 복잡한 쿼리가 있을 경우 사용자는 가벼운 쿼리라고 착각하지만 실제로는 시스템에 큰 부하를 주는 쿼리를 날리고 있을 수 있다.&lt;/li&gt;
&lt;li&gt;업데이트 제약: 뷰는 INSERT, UPDATE, DELETE가 가능하지만 JOIN, 집계 함수, GROUP BY, DISTINCE 등을 사용한 복잡한 뷰에서는 불가능하다. 왜냐하면 뷰의 한 행을 수정하는 것이 원본 테이블들의 데이터를 어떻게 바꿔야 하는지에 대한 명확한 규칙을 특정할 수 없기 때문이다. 뷰가 오직 하나의 기본 테이블만을 참조하고, 뷰의 모든 컬럼이 기본 테이블의 실제 칼럼을 직접 참조하는 경우에는 뷰에 데이터를 추가/수정할 수 있다. 이 경우 원본 테이블의 값이 변경된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;뷰(View)는 하나 이상의 테이블로부터 생성되는 가상의 테이블이다. 실제 데이터를 저장하지 않으며 주로 SELECT 문을 통해 정의된다. 이를 통해 데이터 접근을 단순화하고 보안 및 데이터 독립성을 높일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style5&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;본 포스팅은 &amp;ldquo;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;김영한의 실전 데이터베이스 기본편/&lt;/span&gt;인프런&amp;rdquo;를 학습한 내용을 정리한 것&lt;/b&gt;&lt;/p&gt;</description>
      <category>DB/DB</category>
      <category>db</category>
      <category>DB View</category>
      <category>View이란</category>
      <author>면목동인간</author>
      <guid isPermaLink="true">https://oniic.tistory.com/189</guid>
      <comments>https://oniic.tistory.com/189#entry189comment</comments>
      <pubDate>Sat, 14 Mar 2026 23:58:07 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;SQL&amp;gt; SQL 실행 순서</title>
      <link>https://oniic.tistory.com/188</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SQL 실행 순서&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;SQL 실행 순서는 우리가 SQL을 작성하는 순서와 실제 SQL이 쿼리를 처리하는 순서가 다르다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;SQL 쿼리의 논리적 순서&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;FROM: 가장 먼저 실행. 어떤 테이블에 데이터를 가져올지 결정&lt;/li&gt;
&lt;li&gt;WHERE: FROM에서 가져온 테이블의 개별 행을 필터링한다.&lt;/li&gt;
&lt;li&gt;GROUP BY: WHERE 절의 필터링을 통과한 행들을 기준으로 그룹을 형성한다.&lt;/li&gt;
&lt;li&gt;HAVING: GROUP BY를 통해 만들어진 그룹들을 필터링한다.&lt;/li&gt;
&lt;li&gt;SELECT: 위의 조건을 통과한 행 또는 그룹들에 대해 보고자 하는 칼럼을 선택하고, 집계 함수(SUM, AVG 등등), 별칭(AS) 등이 이 단계에서 이루어진다.&lt;/li&gt;
&lt;li&gt;ORDER BY: SELECT 절에서 선택된 최종 결과 후보들을 지정된 순서로 정렬한다. (SELECT가 ORDER BY 보다 먼저 실행하기 때문에 별칭을 사용할 수 있다.)&lt;/li&gt;
&lt;li&gt;LIMIT: 정렬된 결과 중에서 최종적으로 반환할 행의 개수를 제한한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FROM -&amp;gt; WHERE -&amp;gt; GROUP BY -&amp;gt; HAVING -&amp;gt; SELECT -&amp;gt; ORDER BY -&amp;gt; LIMIT&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;SQL 실행 순서 쿼리 예제&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2026년 3월 3일 이전에 들어온 주문 중에서(WHERE), 고객별로 그룹화하여(GROUP BY), 주문 건수가 3회 이상인 고객을 찾아서(HAVING), 고객의 이름과 총 구매 금액을 조회하고(SELECT), 총 구매 금액을 기준으로 내림차순 정렬하고(ORDER BY), 3개의 데이터만 출력해라(LIMIT).&lt;/p&gt;
&lt;pre id=&quot;code_1772539795840&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT
    customer_name,
SUM(price * quantity) AS total_purchase -- 5단계
FROM
    order_stat -- 1단계
WHERE
    order_date &amp;lt; '2026-03-03' -- 2단계
GROUP BY
    customer_name -- 3단계
HAVING
COUNT(*) &amp;gt;= 3 -- 4단계
ORDER BY
    total_purchase DESC -- 6단계
LIMIT 3; -- 7단계&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;SQL 실행 순서는 작성한 SQL 순서와 다를 수 있다. 실제 물리적인 실행 순서는 FROM -&amp;gt; WHERE -&amp;gt; GROUP BY -&amp;gt; HAVING -&amp;gt; SELECT -&amp;gt; ORDER BY -&amp;gt; LIMIT와 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;본 포스팅은 &amp;ldquo; &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;김영한의 실전 데이터베이스 입문 - 모든 IT인을 위한 SQL 첫걸음(SQL부터 차근차근)&lt;/span&gt;/인프런&amp;rdquo;를 학습한 내용을 정리한 것&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DB/SQL</category>
      <category>SQL</category>
      <category>SQL 실행 순서</category>
      <author>면목동인간</author>
      <guid isPermaLink="true">https://oniic.tistory.com/188</guid>
      <comments>https://oniic.tistory.com/188#entry188comment</comments>
      <pubDate>Tue, 3 Mar 2026 21:21:17 +0900</pubDate>
    </item>
    <item>
      <title>&amp;lt;운영체제&amp;gt; CPU 스케줄링 알고리즘</title>
      <link>https://oniic.tistory.com/187</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;CPU 스케줄링 알고리즘&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;CPU 스케줄링 알고리즘은 다양하고 운영체제마다 다른 알고리즘을 사용하고 있다. 아래는 7가지 알고리즘에 대한 설명이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;선입 선처리 스케줄링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;선입 선처리 스케줄링은 FCFS 스케줄링이라고 불린다. 이는 준비 큐에 삽입된 순서대로 프로세스를 처리하는 비선점형 스케줄링 방식이다. 공정해 보이지만, 프로세스들이 기다리는 시간이 길어질 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캡처1.png&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;483&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VWbaZ/dJMcacu33SN/Q0nQmrBgPP24PJ8YDMA2hK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VWbaZ/dJMcacu33SN/Q0nQmrBgPP24PJ8YDMA2hK/img.png&quot; data-alt=&quot;FCFS 스케줄링&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VWbaZ/dJMcacu33SN/Q0nQmrBgPP24PJ8YDMA2hK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVWbaZ%2FdJMcacu33SN%2FQ0nQmrBgPP24PJ8YDMA2hK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1232&quot; height=&quot;483&quot; data-filename=&quot;캡처1.png&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;483&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FCFS 스케줄링&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;A 프로세스가 15ms 실행 동안 B 프로세스는 15ms를 대기하고, B 프로세스가 5ms 실행 동안 C 프로세스는 1ms를 실행하기 위해 15ms+5ms를 대기하게 된다. 위 스케줄링의 평균 대기 시간은 (20+15+0) / 3 대략 12초 정도 걸린다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최단 작업 우선 스케줄링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;앞서 FCFS 스케줄링의 단점을 방지할려면 CPU 사용 시간이 긴 프로세스는 나중에 실행하고, CPU 사용 시간이 짧은 프로세스는 먼저 실행하면 된다. 기본적으로 비선점형 스케줄링으로 분류되며, 이를 최단 작업 우선 스케줄링이라고 하고 SJF 스케줄링이라고 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캡처2.png&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;457&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FShKP/dJMcaiBZ3Pd/DqQW0kpo4CIU15BjCt2GW1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FShKP/dJMcaiBZ3Pd/DqQW0kpo4CIU15BjCt2GW1/img.png&quot; data-alt=&quot;SJF 스케줄링&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FShKP/dJMcaiBZ3Pd/DqQW0kpo4CIU15BjCt2GW1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFShKP%2FdJMcaiBZ3Pd%2FDqQW0kpo4CIU15BjCt2GW1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1195&quot; height=&quot;457&quot; data-filename=&quot;캡처2.png&quot; data-origin-width=&quot;1195&quot; data-origin-height=&quot;457&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SJF 스케줄링&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;위의 SJF 스케줄링은 실행 시간이 짧은 프로세스 부터 실행되며 C 프로세스가 1ms 실행 동안 B 프로세스는 1ms 대기하고, B 프로세스가 5ms 실행 동안 C 프로세스는 1ms+5ms만 대기하면 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;라운드 로빈 스케줄링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;라운드 로빈 스케줄링 스케줄링은 RR 스케줄링이라고 하며, 선입 선처리(FCFS) 스케줄링에 타임 슬라이스라는 개념이 더해진 스케줄링 방식이다. 타임 슬라이스란 각 프로세스가 CPU를 사용할 수 있는 정해진 시간을 의미한다. 즉 정해진 시간 동안 돌아가며 CPU를 이용하는 선점형 스케줄링이다. 프로세스가 완료되지 않았다면 다시 큐의 맨 뒤에 삽입된다. 이때 문맥 교환이 발생한다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캡처3.png&quot; data-origin-width=&quot;1047&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/td1gf/dJMcahb3Kc8/ojYAQgB22xOEM5rE79ctZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/td1gf/dJMcahb3Kc8/ojYAQgB22xOEM5rE79ctZ0/img.png&quot; data-alt=&quot;RR 스케줄링&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/td1gf/dJMcahb3Kc8/ojYAQgB22xOEM5rE79ctZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftd1gf%2FdJMcahb3Kc8%2FojYAQgB22xOEM5rE79ctZ0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1047&quot; height=&quot;462&quot; data-filename=&quot;캡처3.png&quot; data-origin-width=&quot;1047&quot; data-origin-height=&quot;462&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;RR 스케줄링&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;RR 스케줄링은 타임 슬라이스 크기가 중요하다. 타임 슬라이스 크기가 지나치게 크면 FCFS 스케줄링과 다를 바 없고, 반대로 지나치게 작으면 문맥 교환이 발생하는 비용이 커 CPU는 프로세스를 처리하는 일보다 프로세스를 전환하는 데 사용하게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최소 잔여 시간 우선 스케줄링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;최소 잔여 시간 우선 스케줄링은 SRT 스케줄링이라고 하며, 최단 작업 우선(SJF) 스케줄링과 라운드 로빈(RR) 스케줄링을 합친 스케줄링이다. 즉, 작업 시간이 짧은 프로세스를 먼저 실행하며 타임 슬라이스 크기만큼 우선적으로 실행하는 방식이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;우선순위 스케줄링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;우선순위(Priotity) 스케줄링은 프로세스들에 우선순위를 부여하고, 가장 높은 우선순위를 사진 프로세스 부터 실행하는 스케줄링이다. 우선순위 대로 실행 되는데 만약 우선순위가 높은 프로세스가 큐에 들어오면 그 프로세스가 바로 실행하게 된다. 다만, 큐에 우선순위가 높은 프로세스가 계속 들어오게 되면 낮은 프로세스들은 연기가 되어 기아 현상이 발생하게 된다. 이를 방지하기 위해 에이징이 있는데 오랫동안 대기한 프로세스의 우선순위를 높이는 방식이다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캡처4.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;638&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yQHvW/dJMcahC7K9d/UsTDk6KjBhbIijKXXH7qfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yQHvW/dJMcahC7K9d/UsTDk6KjBhbIijKXXH7qfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yQHvW/dJMcahC7K9d/UsTDk6KjBhbIijKXXH7qfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyQHvW%2FdJMcahC7K9d%2FUsTDk6KjBhbIijKXXH7qfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;862&quot; height=&quot;638&quot; data-filename=&quot;캡처4.png&quot; data-origin-width=&quot;862&quot; data-origin-height=&quot;638&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다단계 큐 스케줄링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;다단계 큐 스케줄링 MLQ 스케줄링이라 하며 앞서 우선순위(Priority) 스케줄링의 발전된 형태이다. MLQ 스케줄링은 우선순위별로 준비 큐를 여러 개 사용하는 방식이다. 다단계 큐 하에서는 우선순위가 가장 높은 큐에 있는 프로세스들을 먼저 처리하고 비어 있으면 그다음 우선순위 큐에 있는 프로세스들을 처리한다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캡처5.png&quot; data-origin-width=&quot;1187&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZvFw6/dJMcac9CkmL/VO6R08FSFboKSFvsKAckfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZvFw6/dJMcac9CkmL/VO6R08FSFboKSFvsKAckfK/img.png&quot; data-alt=&quot;MLQ 스케줄링&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZvFw6/dJMcac9CkmL/VO6R08FSFboKSFvsKAckfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZvFw6%2FdJMcac9CkmL%2FVO6R08FSFboKSFvsKAckfK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1187&quot; height=&quot;616&quot; data-filename=&quot;캡처5.png&quot; data-origin-width=&quot;1187&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MLQ 스케줄링&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &lt;b&gt;위의 큐를 여러 개 두면 프로세스를 우선순위를 구분하여 실행하는 것이 편리하다. 또한 큐별로 타임 슬라이스를 여러 개 지정할 수도 있고, 큐마다 다른 스케줄링 알고리즘을 사용할 수 있다. 다만 우선순위가 높은 큐에 프로세스가 계속 들어온다면 우선순위가 낮은 큐에 있는 프로세스는 연기될 여지가 있다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다단계 피드백 큐 스케줄링&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;다단계 피드백 큐 스케줄링은 다단계 큐(MLQ) 스케줄링의 발전된 형태이다. MLQ 스케줄링은 프로세스들이 큐 사이를 이동할 수 없다. 이런 방식대로면 우선순위가 낮은 프로세스는 계속 연기될 여지가 있으며 기아 현상이 발생할 수 있다. 이를 보완한 스케줄링이 다단계 피드백 큐(MLFQ) 스케줄링이다. 보완한 것은 프로세스들이 큐 사이를 이동할 수 있다는 것이다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캡처6.png&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;627&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3dSQw/dJMcaaqtHrm/TXXF4lUYRAZVVdqjsiZc3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3dSQw/dJMcaaqtHrm/TXXF4lUYRAZVVdqjsiZc3k/img.png&quot; data-alt=&quot;MLFQ 스케줄링&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3dSQw/dJMcaaqtHrm/TXXF4lUYRAZVVdqjsiZc3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3dSQw%2FdJMcaaqtHrm%2FTXXF4lUYRAZVVdqjsiZc3k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1226&quot; height=&quot;627&quot; data-filename=&quot;캡처6.png&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;627&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MLFQ 스케줄링&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;위의 그림에서 프로세스가 타임슬라이스 만큼 실행한다고 가정하였다. 그런데 만약 프로세스가 해당 큐에서 실행이 끝나지 않는다면 다음 우선순위 큐에 삽입되어 실행된다. 그리고 해당 큐에서 실행이 끝나지 않는다면 프로세스는 또 다음 우선순위 큐에 삽입된다. 즉 CPU를 오래 사용하는 프로세스의 우선순위는 낮아지게 된다.&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;캡처7.png&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ApDJc/dJMcagxshSv/dyW8YjKqwORvkimTkI6zUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ApDJc/dJMcagxshSv/dyW8YjKqwORvkimTkI6zUK/img.png&quot; data-alt=&quot;MLFQ 스케줄링&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ApDJc/dJMcagxshSv/dyW8YjKqwORvkimTkI6zUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FApDJc%2FdJMcagxshSv%2FdyW8YjKqwORvkimTkI6zUK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1217&quot; height=&quot;602&quot; data-filename=&quot;캡처7.png&quot; data-origin-width=&quot;1217&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;MLFQ 스케줄링&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;위의 그림에서 실행을 못 끝낸 프로세스가 다음 우선순위로 내려갔다. 이렇게 프로세스가 못 끝낸 상태로 우선순위가 내려간다면 낮은 우선순위에 있기 때문에 큐에 오래 기다리게 되는데 우선순위가 높은 큐로 이동시키는 에이징 기법을 적용하여 기아 현상을 예방할 수 있다. MLFQ 스케줄링은 CPU 이용 시간이 길면 낮은 우선순위로 이동시키고, 낮은 우선순위에서 오래 기다린다면 높은 우선순위 큐로 이동시키는 스케줄링이다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt; 선입 선처리(FCFS) 스케줄링: 준비 큐에 삽입된 순서대로 CPU를 할당&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;최단 작업 우선(SJF) 스케줄링: 준비 큐에 삽입된 프로세스들 중 CPU 사용 시간의 길이가 가장 짧은 프로세스부터 CPU를 할당&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;라운드 로빈(RR) 스케줄링: 정해진 시간만큼만 돌아가며 CPU를 할당&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt; 최소 잔여 시간 우선(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;SRT&lt;/span&gt;) &lt;/span&gt;스케줄링: 최단 작업 우선(SJF) 스케줄링과 라운드 로빈(RR) 스케줄링을 합친 스케줄링&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt; 우선순위(Priority) 스케줄링: 가장 높은 우선순위의 프로세스에 CPU를 할당&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다단계 큐(MLQ) 스케줄링: 우선순위 별로 다단계 큐에 프로세스를 삽입하여 우선순위가 높은 큐에 CPU를 할당&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;다단계 피드백 큐(MLFQ) 스케줄링: 다단계 큐(MLQ) 스케줄링의 발전된 형태이며, 프로세스가 큐 사이를 이동할 수 있는 스케줄링&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #666666;&quot;&gt;본 포스팅은 &amp;ldquo;혼자 공부하는 컴퓨터 구조 + 운영체제/강민철 저&amp;rdquo;를 읽고 학습한 내용을 정리한 것&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>CS/운영체제</category>
      <category>FCFS 스케줄링</category>
      <category>MLFQ 스케줄링</category>
      <category>MLQ 스케줄링</category>
      <category>Priority 스케줄링</category>
      <category>RR 스케줄링</category>
      <category>SJF 스케줄링</category>
      <category>SRT 스케줄링</category>
      <category>운영체제</category>
      <author>면목동인간</author>
      <guid isPermaLink="true">https://oniic.tistory.com/187</guid>
      <comments>https://oniic.tistory.com/187#entry187comment</comments>
      <pubDate>Mon, 12 Jan 2026 21:52:12 +0900</pubDate>
    </item>
  </channel>
</rss>