Pandas SQLiteでの大量データ読み込み速度比較

PandasやSQLiteで大量のデータを読み込む際の速度比較と実行によるメモリ使用率を調べてみました。検証パターンは、下記の3パターンです。

1.sqlite3の```fetchall```でDBの読み込み
2.SQLiteのテーブルをpandasの```read_sql_query```でDBの読み込み
3.pandasの```read_csv```でCSVファイルの読み込み

# 目次
- [環境](#環境)
- [検証用データ作成](#検証用データ作成)
- [各パターン 検証コード](#各パターン検証コード)
- [検証結果](#検証結果)
- [まとめ](#まとめ)

# 環境
Python 3.11.9
pandas 2.2.3
sqlite3 3.45.1
memory-profiler 0.61.0
Faker 33.1.0

# 検証用データ作成
カラム数 10 × 行数（10万・100万・500万）をSQLiteとCSVファイルで作成し検証しました。
```データ作成.py
import sqlite3
import csv
import random
import time
from datetime import datetime, timedelta
from faker import Faker

# 日本語のFakerインスタンスを作成
fake = Faker("ja_JP")

conn = sqlite3.connect("test.db")
cur = conn.cursor()

# 既存のテーブルを削除（存在する場合）
cur.execute("DROP TABLE IF EXISTS sample_data")

# 新しいテーブルを作成
cur.execute(
    """
CREATE TABLE sample_data (
    col1 INTEGER PRIMARY KEY,
    col2 TEXT,
    col3 INTEGER,
    col4 REAL,
    col5 TEXT,
    col6 TEXT,
    col7 INTEGER,
    col8 TEXT,
    col9 REAL,
    col10 TEXT
)
"""
)

with open("sample_data.csv", "w", newline="", encoding="utf-8") as file:
    writer = csv.writer(file)
    writer.writerow([f"col{i}" for i in range(1, 11)])  # CSVに列名を生成

    end_date = datetime.now()
    start_date = end_date - timedelta(days=365)

    for i in range(1, 5000001):
        row_data = [
            i,  # ID
            fake.name(),  # 名前
            random.randint(1000, 1000000),  # 数値1
            random.uniform(0.1, 100.0),  # 浮動小数点
            fake.date_between(start_date=start_date, end_date=end_date).strftime("%Y-%m-%d"),  # 日付
            random.choice(["A", "B", "C", "D", "E"]),  # カテゴリ
            random.randint(1, 100),  # 数値2
            fake.email(),  # メールアドレス
            random.random(),  # 0-1の乱数
            random.choice(["active", "inactive", "pending"]),  # ステータス
        ]

        writer.writerow(row_data) # CSVにデータ書き込み

        # SQLiteへの挿入処理
        sql = "INSERT INTO sample_data VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
        cur.execute(sql, row_data)

```

検証用のデータ作成には**Faker**ライブラリを使用しました。

https://qiita.com/yunosuken/items/5835b6ba26981c56eeda

# 各パターン 検証コード
**■ sqlite3　fetchall**

```sample.py
import sqlite3
import time
from memory_profiler import profile


@profile
def fetch_data():
    start = time.time()
    with sqlite3.connect("test.db") as conn:
        cur = conn.cursor()
        sql = "SELECT * FROM sample_data"
        
        cur.execute(sql)
        cur.fetchall()

    end_time = time.time()
    elapsed_time = end_time - start
    print(f"実行時間: {elapsed_time/60:.2f}分 ({elapsed_time:.2f}秒)")
```

**■ pandas　read_sql_query**
```sample.py
import pandas
import sqlite3
import time
from memory_profiler import profile


@profile
def pandas_sqlite_db_read():
    with sqlite3.connect("test.db") as conn:
        start = time.time()
        sql = "select * from sample_data"
        
        df = pandas.read_sql_query(sql, conn)

        end_time = time.time()
        elapsed_time = end_time - start
        print(f"実行時間:{elapsed_time/60}分({elapsed_time:.2f}秒)")
```

**■ pandas　read_csv**
```sample.py
import pandas
import time
from memory_profiler import profile

READ_PATH = R"large_data.csv"


@profile
def pandas_csv_read():
    start = time.time()
    
    df = pandas.read_csv(READ_PATH)

    end = time.time()
    elapsed_time = end - start
    print(f"実行時間:{elapsed_time/60}分({elapsed_time:.2f}秒)")
```

```memory_profiler```を使用してプログラムのメモリ使用状況を確認することが可能です。

https://brian0111.com/python-memory-profiler-guide/

# 検証結果
### 10万件読み込み

**■ sqlite3.fetchall**
<table> 
  <thead>
    <tr>
      <th>回数</th> <th>実行時間</th><th>読み込み行でのメモリ使用量</th><th>読み込み行での増加量</th>
    </tr>
  </thead>
  <tr>
    <td>1回目</td> 
    <td>0.44秒</td>
     <td>29.9 MiB</td>
     <td>5.6 MiB</td>
  </tr>
 <tr>
    <td>2回目</td> 
    <td>0.31秒</td>
     <td>30.8 MiB</td>
     <td>6.5 MiB</td>
  </tr>
  <tr>
    <td>3回目</td> 
    <td>0.30秒</td>
     <td>29.5 MiB</td>
     <td>5.0 MiB</td>
  </tr>
</table>

**■ pandas.read_sql_query**
<table> 
  <thead>
    <tr>
      <th>回数</th> <th>実行時間</th><th>読み込み行でのメモリ使用量</th><th>読み込み行での増加量</th>
    </tr>
  </thead>
  <tr>
    <td>1回目</td> 
    <td>0.46秒</td>
     <td>131.9 MiB</td>
     <td> 64.4 MiB</td>
  </tr>
 <tr>
    <td>2回目</td> 
    <td>0.47秒</td>
     <td>131.8 MiB </td>
     <td>64.6 MiB</td>
  </tr>
  <tr>
    <td>3回目</td> 
    <td>0.47秒</td>
     <td>131.9 MiB</td>
     <td>64.5 MiB</td>
  </tr>
</table>

**■ pandas.read_csv**
<table> 
  <thead>
    <tr>
      <th>回数</th> <th>実行時間</th><th>読み込み行でのメモリ使用量</th><th>読み込み行での増加量</th>
    </tr>
  </thead>
  <tr>
    <td>1回目</td> 
    <td>0.26秒</td>
     <td>79.8 MiB</td>
     <td>12.2 MiB</td>
  </tr>
 <tr>
    <td>2回目</td> 
    <td>0.24秒</td>
     <td>79.6 MiB</td>
     <td>12.3 MiB</td>
  </tr>
  <tr>
    <td>3回目</td> 
    <td>0.25秒</td>
     <td>79.7 MiB</td>
     <td>12.4 MiB</td>
  </tr>
</table>

### 100万件読み込み
**■ sqlite3.fetchall**
<table> 
  <thead>
    <tr>
      <th>回数</th> <th>実行時間</th><th>読み込み行でのメモリ使用量</th><th>読み込み行での増加量</th>
    </tr>
  </thead>
  <tr>
    <td>1回目</td> 
    <td>3.42秒</td>
     <td>23.8 MiB</td>
     <td> 0.4 MiB</td>
  </tr>
 <tr>
    <td>2回目</td> 
    <td>3.32秒</td>
     <td>24.5 MiB </td>
     <td>0.4 MiB</td>
  </tr>
  <tr>
    <td>3回目</td> 
    <td>3.30秒</td>
     <td>24.3 MiB</td>
     <td>0.4 MiB</td>
  </tr>
</table>

**■ pandas.read_sql_query**
<table> 
  <thead>
    <tr>
      <th>回数</th> <th>実行時間</th><th>読み込み行でのメモリ使用量</th><th>読み込み行での増加量</th>
    </tr>
  </thead>
  <tr>
    <td>1回目</td> 
    <td>4.62秒</td>
     <td>593.6 MiB</td>
     <td>526.2 MiB</td>
  </tr>
 <tr>
    <td>2回目</td> 
    <td>4.60秒</td>
     <td>686.3 MiB</td>
     <td>619.1 MiB</td>
  </tr>
  <tr>
    <td>3回目</td> 
    <td>4.08秒</td>
     <td>686.8 MiB</td>
     <td>619.4 MiB</td>
  </tr>
</table>

**■ pandas.read_csv**
<table> 
  <thead>
    <tr>
      <th>回数</th> <th>実行時間</th><th>読み込み行でのメモリ使用量</th><th>読み込み行での増加量</th>
    </tr>
  </thead>
  <tr>
    <td>1回目</td> 
    <td>2.13秒</td>
     <td>181.5 MiB</td>
     <td>114.2 MiB</td>
  </tr>
 <tr>
    <td>2回目</td> 
    <td>2.01秒</td>
     <td>179.5 MiB</td>
     <td>112.1 MiB</td>
  </tr>
  <tr>
    <td>3回目</td> 
    <td>1.88秒</td>
     <td>180.3 MiB</td>
     <td>113.0 MiB</td>
  </tr>
</table>

### 500万件読み込み
**■ sqlite3.fetchall**
<table> 
  <thead>
    <tr>
      <th>回数</th> <th>実行時間</th><th>読み込み行でのメモリ使用量</th><th>読み込み行での増加量</th>
    </tr>
  </thead>
  <tr>
    <td>1回目</td> 
    <td>17.11秒</td>
     <td>24.4 MiB</td>
     <td>0.4 MiB</td>
  </tr>
 <tr>
    <td>2回目</td> 
    <td>17.59秒</td>
     <td>24.3 MiB</td>
     <td>0.4 MiB</td>
  </tr>
  <tr>
    <td>3回目</td> 
    <td>17.27秒</td>
     <td>24.4 MiB </td>
     <td>0.4 MiB</td>
  </tr>
</table>

**■ pandas.read_sql_query**
<table> 
  <thead>
    <tr>
      <th>回数</th> <th>実行時間</th><th>読み込み行でのメモリ使用量</th><th>読み込み行での増加量</th>
    </tr>
  </thead>
  <tr>
    <td>1回目</td> 
    <td>51.16秒</td>
     <td>2915.4 MiB</td>
     <td>2847.9 MiB</td>
  </tr>
 <tr>
    <td>2回目</td> 
    <td>39.05秒</td>
     <td>3085.6 MiB</td>
     <td>3018.3 MiB</td>
  </tr>
  <tr>
    <td>3回目</td> 
    <td>40.58秒</td>
     <td>2894.4 MiB</td>
     <td>2827.0 MiB</td>
  </tr>
</table>

**■ pandas.read_csv**
<table> 
  <thead>
    <tr>
      <th>回数</th> <th>実行時間</th><th>読み込み行でのメモリ使用量</th><th>読み込み行での増加量</th>
    </tr>
  </thead>
  <tr>
    <td>1回目</td> 
    <td>12.57秒</td>
     <td>338.0 MiB</td>
     <td>270.6 MiB </td>
  </tr>
 <tr>
    <td>2回目</td> 
    <td>10.86秒</td>
     <td>622.5 MiB</td>
     <td>555.3 MiB </td>
  </tr>
  <tr>
    <td>3回目</td> 
    <td>10.30秒</td>
     <td>623.0 MiB</td>
     <td>555.4 MiB</td>
  </tr>
</table>

# まとめ
**■ sqlite3.fetchall**
* 速度: 比較的速いが、大量データ（500万件）では実行時間が増加。
* メモリ使用量: 他の方法と比較してメモリ使用量の増加が非常に少なく、効率的。

**■ pandas.read_sql_query**
* 速度: 全てのパターンで遅い。特に大量データの場合、実行時間が顕著に増加。 
* メモリ使用量: データ量に対して大幅に増加。大量データではメモリ不足になる可能性がある。

**■ pandas.read_csv**
* 速度: 最速（10万件～500万件で安定）。
* メモリ使用量: pandas.read_sql_queryよりも効率的だが、sqlite3.fetchallよりは高い。