메뉴 여닫기
개인 메뉴 토글
로그인하지 않음
만약 지금 편집한다면 당신의 IP 주소가 공개될 수 있습니다.
Dbstudy (토론 | 기여)님의 2026년 9월 9일 (수) 22:51 판 (새 문서: == 통계정보 == * SQL Server에서 **테이블/인덱스 통계정보**를 확인하려면 Oracle의 `DBA_TAB_STATISTICS`, `DBA_IND_STATISTICS`, `DBA_TAB_COL_STATISTICS`와 대응해서 보면 이해하기 좋습니다. * 가장 중요한 것은 SQL Server의 **통계 객체(Statistics)** 자체를 조회하는 것입니다. === 특정 테이블의 통계정보 === 예를 들어 `dbo.ORDERS` 테이블: <source lang=sql> SELECT s.name AS schema_name, t.name AS table...)
(차이) ← 이전 판 | 최신판 (차이) | 다음 판 → (차이)

통계정보

  • SQL Server에서 **테이블/인덱스 통계정보**를 확인하려면 Oracle의 `DBA_TAB_STATISTICS`, `DBA_IND_STATISTICS`, `DBA_TAB_COL_STATISTICS`와 대응해서 보면 이해하기 좋습니다.
  • 가장 중요한 것은 SQL Server의 **통계 객체(Statistics)** 자체를 조회하는 것입니다.

특정 테이블의 통계정보

예를 들어 `dbo.ORDERS` 테이블:

SELECT
    s.name AS schema_name,
    t.name AS table_name,
    st.name AS statistics_name,
    st.stats_id,

    st.auto_created,
    st.user_created,
    st.no_recompute,

    sp.last_updated,
    sp.rows,
    sp.rows_sampled,
    sp.modification_counter,

    sp.steps,

    CAST(
        sp.rows_sampled * 100.0
        / NULLIF(sp.rows, 0)
        AS DECIMAL(10,2)
    ) AS sample_pct

FROM sys.tables t
JOIN sys.schemas s
    ON t.schema_id = s.schema_id

JOIN sys.stats st
    ON t.object_id = st.object_id

OUTER APPLY sys.dm_db_stats_properties(
    st.object_id,
    st.stats_id
) sp

WHERE
    s.name = 'dbo'
    AND t.name = 'ORDERS'

ORDER BY
    st.stats_id;

결과는 대략:

schema  table   statistics_name          last_updated       rows       sampled    mod_count

dbo     ORDERS  _WA_Sys_00000001_...     2026-09-09 20:30   50,000,000  5,000,000   250,000
dbo     ORDERS  IX_ORDERS_CUSTOMER       2026-09-09 20:31   50,000,000  50,000,000    2,500
dbo     ORDERS  IX_ORDERS_DATE           2026-09-08 09:10   49,000,000  4,900,000   800,000

에서 특히 중요하게 볼 것은:

last_updated
rows
rows_sampled
modification_counter

입니다.


  1. 2. Oracle `DBA_TAB_STATISTICS`와 비슷하게 보기

테이블별 통계 업데이트 상태를 보고 싶다면:

SELECT
    s.name AS schema_name,
    t.name AS table_name,

    sp.last_updated,
    sp.rows,
    sp.rows_sampled,
    sp.modification_counter,

    CAST(
        sp.modification_counter * 100.0
        / NULLIF(sp.rows, 0)
        AS DECIMAL(10,2)
    ) AS modification_pct

FROM sys.tables t
JOIN sys.schemas s
    ON t.schema_id = s.schema_id

JOIN sys.stats st
    ON t.object_id = st.object_id

OUTER APPLY sys.dm_db_stats_properties(
    st.object_id,
    st.stats_id
) sp

WHERE t.is_ms_shipped = 0

ORDER BY
    sp.modification_counter DESC;

예를 들어:

TABLE          ROWS         MODIFICATION_COUNTER   MODIFICATION_%
-----------------  --------------  ----------------------------  --------------------
ORDERS         50,000,000          5,000,000              10.00
CUSTOMER        8,000,000          1,200,000              15.00
PRODUCT         2,000,000             5,000               0.25

이런 결과가 나온다면 `ORDERS`, `CUSTOMER`부터 통계를 점검하는 것이 좋습니다.


  1. 3. 인덱스별 통계정보

SQL Server에서는 **인덱스에 연결된 통계**와 일반 column statistics가 존재합니다.

인덱스 목록과 해당 통계를 같이 보고 싶다면:

SELECT
    s.name AS schema_name,
    t.name AS table_name,

    i.name AS index_name,
    i.index_id,
    i.type_desc AS index_type,

    st.name AS statistics_name,

    st.auto_created,
    st.user_created,
    st.no_recompute,

    sp.last_updated,
    sp.rows,
    sp.rows_sampled,
    sp.modification_counter

FROM sys.tables t

JOIN sys.schemas s
    ON t.schema_id = s.schema_id

JOIN sys.indexes i
    ON t.object_id = i.object_id

LEFT JOIN sys.stats st
    ON t.object_id = st.object_id
   AND st.name = i.name

OUTER APPLY sys.dm_db_stats_properties(
    st.object_id,
    st.stats_id
) sp

WHERE
    t.is_ms_shipped = 0
    AND i.index_id > 0

ORDER BY
    s.name,
    t.name,
    i.index_id;

그런데 여기서 중요한 점이 있습니다.

    • 모든 인덱스 통계가 단순히 `statistics_name = index_name` 형태라고 가정하면 안 됩니다.**

따라서 실제 인덱스와 통계를 정확하게 연결하려면 `sys.stats`와 `sys.index_columns`까지 보는 것이 좋습니다.


  1. 4. 인덱스 + 통계의 선두 컬럼까지 확인

튜닝할 때는 이 쿼리를 추천합니다.

SELECT
    s.name AS schema_name,
    t.name AS table_name,

    i.name AS index_name,
    i.index_id,
    i.type_desc,

    st.name AS statistics_name,

    STATS_DATE(
        st.object_id,
        st.stats_id
    ) AS stats_date,

    sp.rows,
    sp.rows_sampled,
    sp.modification_counter,

    STRING_AGG(
        CASE
            WHEN ic.is_included_column = 0
            THEN c.name
        END,
        ', '
    ) WITHIN GROUP
    (
        ORDER BY ic.key_ordinal
    ) AS key_columns,

    STRING_AGG(
        CASE
            WHEN ic.is_included_column = 1
            THEN c.name
        END,
        ', '
    ) AS include_columns

FROM sys.tables t

JOIN sys.schemas s
    ON t.schema_id = s.schema_id

JOIN sys.indexes i
    ON t.object_id = i.object_id

JOIN sys.stats st
    ON t.object_id = st.object_id
   AND st.name = i.name

JOIN sys.index_columns ic
    ON i.object_id = ic.object_id
   AND i.index_id = ic.index_id

JOIN sys.columns c
    ON ic.object_id = c.object_id
   AND ic.column_id = c.column_id

OUTER APPLY sys.dm_db_stats_properties(
    st.object_id,
    st.stats_id
) sp

WHERE
    t.is_ms_shipped = 0

GROUP BY
    s.name,
    t.name,
    i.name,
    i.index_id,
    i.type_desc,
    st.name,
    st.object_id,
    st.stats_id,
    sp.rows,
    sp.rows_sampled,
    sp.modification_counter

ORDER BY
    s.name,
    t.name,
    i.index_id;

이렇게 보면:

TABLE       INDEX                KEY_COLUMNS              INCLUDE       LAST_UPDATE     MOD
-------------  -------------------------  ------------------------------  ----------------  ------------------  -----
ORDERS      PK_ORDERS            ORDER_ID                 NULL          2026-09-09      100
ORDERS      IX_ORDERS_CUSTOMER   CUSTOMER_ID, ORDER_DATE  AMOUNT        2026-09-09   120000
ORDERS      IX_ORDERS_STATUS     STATUS, ORDER_DATE       AMOUNT        2026-08-20   900000

처럼 확인할 수 있습니다.


  1. 5. 가장 중요한 것: Histogram 확인

Oracle DBA가 SQL Server 통계를 튜닝하려면 **Histogram을 반드시 보시는 것을 추천**합니다.

SQL Server에서는:

DBCC SHOW_STATISTICS

를 사용합니다.

예를 들어:

DBCC SHOW_STATISTICS
(
    'dbo.ORDERS',
    'IX_ORDERS_CUSTOMER'
);

하면 크게 3개가 나옵니다.

1. STAT_HEADER
2. DENSITY_VECTOR
3. HISTOGRAM

이건 Oracle의:

DBA_TAB_HISTOGRAMS

를 보는 것과 가장 비슷한 부분입니다.


  1. 6. Histogram만 보고 싶다면
DBCC SHOW_STATISTICS
(
    'dbo.ORDERS',
    'IX_ORDERS_CUSTOMER'
)
WITH HISTOGRAM;

결과 예:

RANGE_HI_KEY    EQ_ROWS    DISTINCT_RANGE_ROWS    AVG_RANGE_ROWS
-----------------  ------------  ----------------------------  ------------------
100               5000           0                    1
200              12000          50                  120
300             500000        1000                  500

이 정보가 **Cardinality Estimation**을 볼 때 매우 중요합니다.


  1. 7. 통계가 오래되었는지 Top으로 확인

운영 DB에서 제가 특히 추천하는 쿼리입니다.

SELECT TOP (50)

    s.name AS schema_name,
    t.name AS table_name,

    st.name AS statistics_name,

    sp.last_updated,

    sp.rows,
    sp.rows_sampled,

    sp.modification_counter,

    CAST(
        sp.modification_counter * 100.0
        / NULLIF(sp.rows, 0)
        AS DECIMAL(10,2)
    ) AS modification_pct,

    st.auto_created,
    st.user_created,
    st.no_recompute

FROM sys.tables t

JOIN sys.schemas s
    ON t.schema_id = s.schema_id

JOIN sys.stats st
    ON t.object_id = st.object_id

OUTER APPLY sys.dm_db_stats_properties(
    st.object_id,
    st.stats_id
) sp

WHERE
    t.is_ms_shipped = 0

ORDER BY
    modification_pct DESC;

예:

TABLE       STATISTICS              ROWS       MODIFICATION   MOD%
-------------  -----------------------------  ------------  ----------------   ------
ORDERS      IX_ORDERS_CUSTOMER      50,000,000    5,000,000   10.00
CUSTOMER    IX_CUSTOMER_REGION       8,000,000    700,000     8.75
SALES       IX_SALES_DATE           100,000,000  4,000,000    4.00

이렇게 나오면 **통계가 stale일 가능성이 높은 객체**부터 조사할 수 있습니다.


  1. 8. 통계 업데이트 여부를 판단할 때 주의

`modification_counter`가 크다고 무조건 통계가 잘못된 것은 아닙니다.

예를 들어:

rows = 100,000,000
modification_counter = 100,000

이면:

변경률 = 0.1%

밖에 안 됩니다.

반면:

rows = 1,000,000
modification_counter = 300,000

이면:

변경률 = 30%

입니다.

따라서 저는:

last_updated
+
rows
+
modification_counter
+
Histogram
+
실행계획의 Estimated Rows / Actual Rows

를 같이 봅니다.


  1. 9. 자동 통계인지 확인

`sys.stats`에서는 다음을 확인할 수 있습니다.

SELECT
    s.name AS schema_name,
    t.name AS table_name,
    st.name AS statistics_name,

    st.auto_created,
    st.user_created,
    st.no_recompute,

    STATS_DATE(
        st.object_id,
        st.stats_id
    ) AS last_updated

FROM sys.tables t
JOIN sys.schemas s
    ON t.schema_id = s.schema_id
JOIN sys.stats st
    ON t.object_id = st.object_id

WHERE t.is_ms_shipped = 0

ORDER BY
    s.name,
    t.name,
    st.stats_id;

여기서:

auto_created = 1

이면 SQL Server가 자동 생성한 통계이고,

user_created = 1

이면 사용자가 만든 통계입니다.


  1. 10. 통계의 실제 컬럼 확인

어떤 통계가 **어떤 컬럼을 대상으로 만들어졌는지** 확인하려면:

SELECT
    s.name AS schema_name,
    t.name AS table_name,

    st.name AS statistics_name,

    sc.stats_column_id,
    c.name AS column_name,

    sc.stats_column_id

FROM sys.stats st

JOIN sys.tables t
    ON st.object_id = t.object_id

JOIN sys.schemas s
    ON t.schema_id = s.schema_id

JOIN sys.stats_columns sc
    ON st.object_id = sc.object_id
   AND st.stats_id = sc.stats_id

JOIN sys.columns c
    ON sc.object_id = c.object_id
   AND sc.column_id = c.column_id

WHERE
    t.is_ms_shipped = 0

ORDER BY
    s.name,
    t.name,
    st.name,
    sc.stats_column_id;

예:

TABLE    STATISTICS                 COLUMN
---------  ---------------------------------  ----------------
ORDERS   IX_ORDERS_CUSTOMER         CUSTOMER_ID
ORDERS   IX_ORDERS_CUSTOMER         ORDER_DATE
ORDERS   _WA_Sys_...                STATUS

이걸 보면 **인덱스 통계와 Auto Created Statistics를 구분**하기 좋습니다.


  1. 11. Oracle DBA 관점에서 대응시키면

대략 이렇게 생각하시면 됩니다.

| Oracle | SQL Server | | -------------------------------- | ------------------------------------------------------ | | `DBA_TAB_STATISTICS` | `sys.stats` + `dm_db_stats_properties()` | | `DBA_IND_STATISTICS` | `sys.stats` + `sys.indexes` | | `DBA_TAB_COL_STATISTICS` | `sys.stats_columns` | | `DBA_TAB_HISTOGRAMS` | `DBCC SHOW_STATISTICS ... WITH HISTOGRAM` | | `NUM_ROWS` | `rows` | | `SAMPLE_SIZE` | `rows_sampled` | | `LAST_ANALYZED` | `last_updated` | | 변경량 | `modification_counter` |


    1. 실전에서 특히 유용한 조합

지금까지 질문하신 **SQL 튜닝 + 파티션 + 테이블/인덱스 사이즈**까지 연결해서 보면, 저는 문제 SQL 하나를 잡은 다음 아래 순서로 확인하는 것을 추천합니다.

① Actual Execution Plan
        ↓
② Estimated Rows vs Actual Rows
        ↓
③ 해당 테이블/인덱스 Statistics
        ↓
④ last_updated
        ↓
⑤ modification_counter
        ↓
⑥ DBCC SHOW_STATISTICS → Histogram
        ↓
⑦ Logical Reads
        ↓
⑧ Index / Query 수정

특히 **`Estimated Rows`와 `Actual Rows`가 크게 차이 나는 SQL**이라면 통계정보가 문제의 원인인지 먼저 확인할 가치가 큽니다.

예를 들어 실행계획이:

Estimated Rows = 10
Actual Rows    = 2,500,000

이면 단순히 인덱스가 없다는 문제만 볼 것이 아니라 **통계의 `last_updated`, `modification_counter`, histogram**을 같이 확인해야 합니다.

또 **파티션 테이블**이라면 일반 테이블보다 한 단계 더 들어가서 **파티션별 통계와 Incremental Statistics 여부**까지 보는 것이 중요합니다. SQL Server의 파티션 테이블을 튜닝 중이시라면 이 부분을 별도로 확인하는 쿼리까지 붙여서 보는 것이 좋습니다.