Tips17. count(*)は遅いのか-PostgreSQL編-

引き続き、 Tips.6 本当にCOUNT(*)は遅いのか?COUNT(列名)は速いのか?~処理時間を計測~ での、Oracleでの count(*) が遅くなるという件のPostgreSQL版です。
環境については前回同様、 Tips.15 様々なDB環境を Docker で構築 で作成したものを使用します。

docker コンテナの作成

次のコマンドでdockerコンテナを作成・実行し、docker execでコンテナ内に入ります。

docker pull ubuntu/postgres 
docker run -d --name postgres_container --volume ./postgres:/home/postgres \ 
 -e TZ=JST -e POSTGRES_DB=count_test \ 
 -e POSTGRES_USER=ctest_usr \ 
 -e POSTGRES_PASSWORD=ctestpwd \ 
 -e POSTGRES_PASSWORD=postgresroot \ 
 -e POSTGRES_INITDB_ARGS="--encoding=UTF8 --lc-collate=C --lc-ctype=C" \ 
 -p 30432:5432 ubuntu/postgres 
docker exec -it postgres_container /bin/bash 

このコマンドでは、

  • dockerイメージは「ubuntu/postgres」を使用。事前に docker pull ubuntu/postgres でダウンロードしておいてください。
  • 「postgres_container」という名前のコンテナを作成
  • ローカルのカレントディレクトリ下「./postgres」と、コンテナ側の「/home/postgres」をマッピング
  • タイムゾーンはJST(日本時間)
  • 環境変数の設定内容は下記の通り。
  • POSTGRES_DB=count_test ・・・データベース名
  • POSTGRES_USER=ctest_usr ・・・データベースユーザー
  • POSTGRES_PASSWORD=postgresroot ・・・データベースパスワード
  • POSTGRES_INITDB_ARGS="–encoding=UTF8 –lc-collate=C –lc-ctype=C" ・・・文字コードにはUTF8を使用
  • ポート番号は、ローカル側が30432、コンテナ側が5432

を設定しています。

コンテナ内で、PostgreSQLの起動・実行~テーブル作成

$ docker exec -it postgres_container /bin/bash  
# psql -p 5432 -U ctest_usr -d count_test  
■ データベース一覧  
count_test=# \l  
                               List of databases  
    Name    |   Owner   | Encoding | Collate | Ctype |    Access privileges  
------------+-----------+----------+---------+-------+-------------------------  
 count_test | ctest_usr | UTF8     | C       | C     |  
 postgres   | ctest_usr | UTF8     | C       | C     |  
 template0  | ctest_usr | UTF8     | C       | C     | =c/ctest_usr           +  
            |           |          |         |       | ctest_usr=CTc/ctest_usr  
 template1  | ctest_usr | UTF8     | C       | C     | =c/ctest_usr           +  
            |           |          |         |       | ctest_usr=CTc/ctest_usr  
(4 rows)  
  
■ データベース選択  
count_test=# use count_test  
   
■ テーブル作成  
count_test=# create table count_test  
(  
  id int primary key,  
  last_name varchar(30),  
  first_name varchar(30),  
  sex varchar(4),  
  date_of_birth date,  
  age int,  
  address varchar(1000)  
);  
  
■ インデックス作成  
count_test=# create index count_test_idx on count_test(age); 
 
■ テーブル定義の確認  
count_test=# \d count_test  
                        Table "public.count_test"  
    Column     |          Type           | Collation | Nullable | Default  
---------------+-------------------------+-----------+----------+---------  
 id            | integer                 |           | not null |  
 last_name     | character varying(30)   |           |          |  
 first_name    | character varying(30)   |           |          |  
 sex           | character varying(4)    |           |          |  
 date_of_birth | date                    |           |          |  
 age           | integer                 |           |          |  
 address       | character varying(1000) |           |          |  
Indexes:  
    "count_test_pkey" PRIMARY KEY, btree (id)  
    "count_test_idx" btree (age) 
 
■ クエリ発行確認  
count_test=# select current_timestamp;  
       current_timestamp  
-------------------------------  
 2024-09-05 13:52:22.946513+00  

テストデータの取り込み

PostgreSQLでは、COPYコマンドを使用してCSV/TSVファイルの取り込みを行う事ができます。

■ データロード  
count_test=# COPY count_test (id, last_name, first_name, sex, date_of_birth, age, address)  
 FROM '/home/postgres/person_testdata.tsv'  
 WITH (FORMAT csv, DELIMITER E'\t', HEADER false);  
COPY 1000000  
  
■ データ取込み確認  
count_test=# select * from count_test limit 10;  
 id | last_name | first_name | sex | date_of_birth | age |                                 address  
----+-----------+------------+-----+---------------+-----+--------------------------------------------------------------------------  
  1 | 滝口      | 沙織       | 女  | 1988-03-02    |  36 | 京都府京都市東山区三条通南裏二筋目白川筋西入二丁目北側北木之元町  
  2 | 横尾      | 和也       | 男  | 1988-04-15    |  36 | 静岡県静岡市大字上土新田下足洗新田川合新田請新田  
  3 | 松山      | 隆         | 男  | 1955-07-06    |  69 | 京都府京都市上京区寺町通今出川上る一丁目西入三筋目上る上塔之段町  
  4 | 清水      | 節子       | 女  | 1947-06-18    |  77 | 静岡県静岡市大字上土新田下足洗新田川合新田請新田  
  5 | 竹田      | 恵子       | 女  | 1962-02-23    |  62 | 愛知県海部郡飛島村大字飛島新田字竹之郷ヨタレ南ノ割  
  6 | 大谷      | 優斗       | 男  | 2004-05-16    |  20 | 京都府京都市東山区三条通南裏二筋目白川筋西入二丁目南側南木之元町  
  7 | 福田      | 浩         | 男  | 1964-09-22    |  59 | 京都府京都市東山区三条通南裏二筋目白川筋西入二丁目南側南木之元町  
  8 | 川嶋      | 舞         | 女  | 1990-06-29    |  34 | 長野県岡谷市川岸字内山芦ノ沢笹見平木伐通芓久保馬ふ入岩良久保大久保細久保  
  9 | 中野      | 悦子       | 女  | 1955-01-29    |  69 | 長野県岡谷市川岸字内山芦ノ沢笹見平木伐通芓久保馬ふ入岩良久保大久保細久保  
 10 | 波多野    | 幸子       | 女  | 1966-10-18    |  57 | 京都府京都市東山区三条通南裏二筋目白川筋西入二丁目北側北木之元町  
(10 rows)  
  
count_test=# select count(1) from count_test;  
  count  
---------  
 1000000  
(1 row)  

select count(*) の実行

あとは前回同様、下記のSQLを実行します。

-- COUNT --  
select count(*) from count_test;  

select count(1) from count_test;  

-- プライマリキー項目  
select count(id) from count_test;  

-- インデックス項目  
select count(age) from count_test;  

-- 長い文字列項目  
select count(address) from count_test;  

-- 関数のある項目  
select count(concat(first_name, last_name, address)) from count_test;  

結果は下記の通りです。

PostgreSQL1回目2回目3回目
count(*)17.98926.45218.358
count(1)22.26724.29423.565
count(id)30.80450.70431.331
count(age)23.12424.45725.434
count(address)36.34335.21734.802
count(concat(first_name, last_name, address))86.63194.91688.725

単位はmsです。

当然ながら、count(*)が遅くなるという事はありません。

count(*),count(1),count(age) は同じくらいで、count(id)が少し遅いのが気になりました。
explain planを取得して確認したところ、速い3つは「count_test_idx」インデックスを使用しており、count(id)は「count_test_pkey」インデックスを使用していました。
文字列型列を参照する最後の2つは「Parallel Seq Scan」となっています。

SQL,パフォーマンス

Posted by tfurukaw