巨大テーブルのデータ件数を取得する

## 目的
超巨大なテーブルの件数をサクッと知りたい！
最近数百億〜兆レコードを持つテーブルを触る機会がありました。
SELECT COUNT(1)しても10分以上かかる！ということが頻繁にあったため、簡単に件数を知る方法を調べていたら行き着きました。
それが「DBA_TAB_MODIFICATIONS」ディクショナリです。


## DBA_TAB_MODIFICATIONSとは
表の統計情報を収集した時点から現時点までの、表に対するINSERT/UPDATE/DELETE/TRUNCATEがどの程度行われたかを保持するデータ・ディクショナリです。

```
SQL> desc dba_tab_modifications 
 名前                NULL?    型
 ------------------ -------- ------------------------------------------
 TABLE_OWNER                 VARCHAR2(30)    #←スキーマ名
 TABLE_NAME                  VARCHAR2(30)    #←テーブル名
 PARTITION_NAME              VARCHAR2(30)    #←パーティション名
 SUBPARTITION_NAME           VARCHAR2(30)    #←サブパーティション名
 INSERTS                     NUMBER          #←最後に統計が取られた以降のINSERTレコード数
 UPDATES                     NUMBER          #←最後に統計が取られた以降のUPDATEレコード数
 DELETES                     NUMBER          #←最後に統計が取られた以降のDELETEレコード数
 TIMESTAMP                   DATE            #←最後に変更された日時
 TRUNCATED                   VARCHAR2(3)     #←最後に統計が取られた以降にTRUNCATEされたか(Yes/No)
 DROP_SEGMENTS               NUMBER          #←最後に統計が取られた以降にDROPされたセグメント数
```


## 実際にDBA_TAB_MODOFICATIONSがどう変わっていくかを見てみます

```
SQL> show user                                
ユーザーは"TEST_USER"です。

SQL> -- テーブルを作成する
SQL> create table test_table(id number(10), name varchar2(100));
表が作成されました。

SQL> -- テーブルのディクショナリ情報を見てみる（統計が取られていないことを確認）
SQL> select owner, table_name, num_rows, last_analyzed from dba_tables where owner = 'TEST_USER' and table_name = 'TEST_TABLE';

OWNER	   TABLE_NAME	   NUM_ROWS LAST_ANALYZED
---------- ------------- ---------- -------------------
TEST_USER  TEST_TABLE		  

SQL> -- DBA_TAB_MODIFICATIONSを見てみる
SQL> -- 統計も取られておらず、データのDMLも行われていないため、レコードが存在しない
SQL> select count(1) from dba_tab_modifications where table_owner = 'TEST_USER' and table_name = 'TEST_TABLE';

  COUNT(1)
----------
         0

SQL> --1レコードINSERTする
SQL> insert into test_table values(1, 'Mike');

1行が作成されました。

SQL> commit;

SQL> --再度DBA_TAB_MODIFICATIONSを見てみるが0件
SQL> select count(1) from dba_tab_modifications where table_owner = 'TEST_USER' and table_name = 'TEST_TABLE';

  COUNT(1)
----------
         0

SQL> --Oracleのメモリに残っている情報をDBA_TAB_MODIFICATIONSに書き出す
SQL> exec DBMS_STATS.FLUSH_DATABASE_MONITORING_INFO;

PL/SQLプロシージャが正常に完了しました。

SQL> select count(1) from dba_tab_modifications where table_owner = 'TEST_USER' and table_name = 'TEST_TABLE';

  COUNT(1)
----------
         1

SQL> --insertが1件となっている
SQL> select table_owner, table_name, inserts, updates, deletes, truncated, timestamp
  2  from dba_tab_modifications
  3  where table_owner = 'TEST_USER' and table_name = 'TEST_TABLE';

TABLE_OWNER  TABLE_NAME    INSERTS	  UPDATES    DELETES    TRU TIMESTAMP
------------ ------------- ---------- ---------- ---------- --- -------------------
TEST_USER    TEST_TABLE             1          0          0 NO 
  2019/03/04 12:24:22

SQL> --統計情報をとる
SQL> exec DBMS_STATS.GATHER_TABLE_STATS(ownname => 'TEST_USER', tabname => 'TEST_TABLE', cascade => FALSE);

PL/SQLプロシージャが正常に完了しました。

SQL> 統計情報が取られた後にDMLを実行していないためレコードがない
SQL> select table_owner, table_name, inserts, updates, deletes, truncated, timestamp
  2  from dba_tab_modifications
  3  where table_owner = 'TEST_USER' and table_name = 'TEST_TABLE';

レコードが選択されませんでした。
```

## 注意点

DBA_TAB_MODIFICATIONSの情報は常に更新されているわけではないため、精度の高い情報が欲しい場合には、直前にDBMS_STATS.FLUSH_DATABASE_MONITORING_INFOを実行する必要があります。
近似値程度でもよければ実行する必要はありません。

## 便利な使い方
データウェアハウスのような超巨大なテーブルのデータ件数を知りたい、でもSELECT COUNT(1) 〜ではとてもではないけど時間がかかりすぎる場合、DBA_TAB_MODIFICATIONSとDBA_TABLESを使うことでデータ件数をサクッと知ることができます。

例えば、368億件のレコードを持つテーブルに対して件数を取得する場合、

- SELECT COUNT(1)〜だと12分以上
- DBA_TAB_MODIFICATIONSとDBA_TABLESを使うと2秒

と非常に効率が良いことがわかります。

```
SQL> --テーブルの件数取得だけで12分かかる
SQL> select  /*+ PARALLEL(6) */ count(1) from TEST_SCHEMA.LARGE_TABLE;

     COUNT(1)
-------------
  36803211715

経過: 00:12:04.73
SQL> 
SQL> select t.owner, t.table_name, t.num_rows, m.inserts, m.updates, m.deletes, m.truncated, t.num_rows+m.inserts-m.deletes as num_rows_real
  2  from dba_tables t
  3  left outer join dba_tab_modifications m
  4  on   t.owner = m.table_owner
  5  and  t.table_name = m.table_name
  6  where t.owner = 'TEST_SCHEMA'
  7  and   t.table_name = 'LARGE_TABLE';

OWNER            TABLE_NAME              NUM_ROWS    INSERTS    UPDATES    DELETES TRU NUM_ROWS_REAL
---------------- ------------------ ------------- ---------- ---------- ---------- --- -------------
TEST_SCHEMA      LARGE_TABLE          35602049044 2387593647          0 1186430976 NO    36803211715

経過: 00:00:02.49
SQL> 
```