#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ SQLite 資料庫優化腳本 建議在低流量時段執行(晚上 8 點後) 功能: 1. 添加缺失的索引(提升查詢效能) 2. 執行 VACUUM 清理(回收空間、優化儲存) 3. 分析資料庫統計(更新查詢規劃器統計資訊) """ import sqlite3 import os import sys import time from datetime import datetime # 資料庫路徑 DB_PATH = '/home/ogt/momo_pro_system/data/momo_database.db' # 日誌函數 def log(message, level="INFO"): timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S') print(f"[{timestamp}] [{level}] {message}") # 檢查資料庫大小 def get_db_size(): size_bytes = os.path.getsize(DB_PATH) size_mb = size_bytes / (1024 * 1024) return size_bytes, size_mb # 檢查索引是否存在 def index_exists(cursor, index_name): cursor.execute( "SELECT name FROM sqlite_master WHERE type='index' AND name=?", (index_name,) ) return cursor.fetchone() is not None # 添加索引 def add_indexes(conn): log("開始添加索引...") cursor = conn.cursor() indexes = [ # products 表索引 ("idx_products_category", "CREATE INDEX IF NOT EXISTS idx_products_category ON products (category)"), ("idx_products_status", "CREATE INDEX IF NOT EXISTS idx_products_status ON products (status)"), ("idx_products_updated_at", "CREATE INDEX IF NOT EXISTS idx_products_updated_at ON products (updated_at DESC)"), # price_records 表索引 ("idx_price_records_product_id", "CREATE INDEX IF NOT EXISTS idx_price_records_product_id ON price_records (product_id)"), ("idx_price_records_product_time", "CREATE INDEX IF NOT EXISTS idx_price_records_product_time ON price_records (product_id, timestamp DESC)"), # promo_products 表索引 ("idx_promo_crawled_at", "CREATE INDEX IF NOT EXISTS idx_promo_crawled_at ON promo_products (crawled_at DESC)"), ("idx_promo_batch_id", "CREATE INDEX IF NOT EXISTS idx_promo_batch_id ON promo_products (batch_id)"), ("idx_promo_status_change", "CREATE INDEX IF NOT EXISTS idx_promo_status_change ON promo_products (status_change)"), ] added_count = 0 skipped_count = 0 for index_name, create_sql in indexes: try: if index_exists(cursor, index_name): log(f" 索引已存在,跳過: {index_name}", "INFO") skipped_count += 1 continue log(f" 創建索引: {index_name}...", "INFO") start_time = time.time() cursor.execute(create_sql) conn.commit() elapsed = time.time() - start_time log(f" ✓ 索引創建成功: {index_name} (耗時 {elapsed:.2f} 秒)", "SUCCESS") added_count += 1 except Exception as e: log(f" ✗ 索引創建失敗: {index_name} - {e}", "ERROR") conn.rollback() log(f"索引添加完成:新增 {added_count} 個,跳過 {skipped_count} 個", "INFO") return added_count # 執行 VACUUM def run_vacuum(conn): log("開始執行 VACUUM 清理...", "INFO") log("⚠️ 注意:此操作會鎖定整個資料庫,期間無法讀寫", "WARNING") try: start_time = time.time() conn.execute("VACUUM") elapsed = time.time() - start_time log(f"✓ VACUUM 執行成功 (耗時 {elapsed:.2f} 秒)", "SUCCESS") return True except Exception as e: log(f"✗ VACUUM 執行失敗: {e}", "ERROR") return False # 分析資料庫統計 def analyze_database(conn): log("開始分析資料庫統計...", "INFO") try: start_time = time.time() conn.execute("ANALYZE") elapsed = time.time() - start_time log(f"✓ 資料庫分析完成 (耗時 {elapsed:.2f} 秒)", "SUCCESS") return True except Exception as e: log(f"✗ 資料庫分析失敗: {e}", "ERROR") return False # 顯示優化前後對比 def show_statistics(before_size, after_size): log("=" * 60) log("資料庫優化統計報告", "INFO") log("=" * 60) log(f"優化前大小: {before_size:.2f} MB", "INFO") log(f"優化後大小: {after_size:.2f} MB", "INFO") if before_size > after_size: saved = before_size - after_size saved_pct = (saved / before_size) * 100 log(f"節省空間: {saved:.2f} MB ({saved_pct:.1f}%)", "SUCCESS") else: log("空間無變化(資料庫已經很緊湊)", "INFO") log("=" * 60) def main(): log("=" * 60) log("SQLite 資料庫優化腳本啟動", "INFO") log("=" * 60) # 檢查資料庫是否存在 if not os.path.exists(DB_PATH): log(f"錯誤:資料庫不存在 - {DB_PATH}", "ERROR") sys.exit(1) # 記錄開始時間和大小 before_bytes, before_mb = get_db_size() log(f"當前資料庫大小: {before_mb:.2f} MB", "INFO") try: # 連接資料庫 log("連接到資料庫...", "INFO") conn = sqlite3.connect(DB_PATH, timeout=30.0) # 步驟 1: 添加索引 log("\n" + "=" * 60) log("步驟 1/3: 添加索引", "INFO") log("=" * 60) added_count = add_indexes(conn) # 步驟 2: 執行 VACUUM log("\n" + "=" * 60) log("步驟 2/3: 執行 VACUUM 清理", "INFO") log("=" * 60) vacuum_success = run_vacuum(conn) # 步驟 3: 分析資料庫 log("\n" + "=" * 60) log("步驟 3/3: 分析資料庫統計", "INFO") log("=" * 60) analyze_success = analyze_database(conn) # 關閉連接 conn.close() log("資料庫連接已關閉", "INFO") # 顯示統計 after_bytes, after_mb = get_db_size() log("") show_statistics(before_mb, after_mb) # 最終結果 log("") log("=" * 60) if added_count > 0 or vacuum_success: log("✓ 資料庫優化完成!", "SUCCESS") else: log("資料庫優化執行完畢(無顯著變化)", "INFO") log("=" * 60) except sqlite3.OperationalError as e: log(f"資料庫操作錯誤: {e}", "ERROR") log("可能原因:資料庫正在被其他程序使用", "ERROR") sys.exit(1) except Exception as e: log(f"未預期的錯誤: {e}", "ERROR") import traceback traceback.print_exc() sys.exit(1) if __name__ == "__main__": main()