mariadb-optimization.py hinzugefügt
This commit is contained in:
394
mariadb-optimization.py
Normal file
394
mariadb-optimization.py
Normal file
@@ -0,0 +1,394 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
JTL Shop MariaDB Index-Optimierung
|
||||
==================================
|
||||
Erstellt fehlende Indizes und aktiviert FULLTEXT-Suche auf allen JTL-Shops.
|
||||
|
||||
Features:
|
||||
- Backup aller Sucheinstellungen vor Änderungen
|
||||
- Erstellt 5 Index-Typen auf allen betroffenen Shops
|
||||
- Aktiviert FULLTEXT-Suche
|
||||
- Generiert Rollback-Skript für Notfälle
|
||||
|
||||
Autor: Claude
|
||||
Datum: 2025-12-12
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
import json
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# Konfiguration
|
||||
TIMESTAMP = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
BACKUP_FILE = f"/root/jtl_search_backup_{TIMESTAMP}.json"
|
||||
ROLLBACK_FILE = f"/root/jtl_search_rollback_{TIMESTAMP}.py"
|
||||
LOG_FILE = f"/root/jtl_index_log_{TIMESTAMP}.txt"
|
||||
|
||||
# Index-Definitionen
|
||||
INDEXES = [
|
||||
{
|
||||
"name": "idx_kLieferscheinPos",
|
||||
"table": "tlieferscheinposinfo",
|
||||
"columns": "kLieferscheinPos",
|
||||
"check_column": "kLieferscheinPos"
|
||||
},
|
||||
{
|
||||
"name": "idx_kLieferschein",
|
||||
"table": "tlieferscheinpos",
|
||||
"columns": "kLieferschein",
|
||||
"check_column": "kLieferschein"
|
||||
},
|
||||
{
|
||||
"name": "idx_kKampagneDef_kKey",
|
||||
"table": "tkampagnevorgang",
|
||||
"columns": "kKampagneDef, kKey",
|
||||
"check_column": "kKampagneDef"
|
||||
},
|
||||
{
|
||||
"name": "idx_kXSellArtikel",
|
||||
"table": "txsellkauf",
|
||||
"columns": "kXSellArtikel",
|
||||
"check_column": "kXSellArtikel"
|
||||
}
|
||||
]
|
||||
|
||||
FULLTEXT_INDEX = {
|
||||
"name": "idx_tartikel_fulltext",
|
||||
"table": "tartikel",
|
||||
"columns": "cName, cSeo, cSuchbegriffe, cArtNr, cKurzBeschreibung, cBeschreibung, cBarcode, cISBN, cHAN"
|
||||
}
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, log_file):
|
||||
self.log_file = log_file
|
||||
self.messages = []
|
||||
|
||||
def log(self, message, level="INFO"):
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
formatted = f"[{timestamp}] [{level}] {message}"
|
||||
print(formatted)
|
||||
self.messages.append(formatted)
|
||||
|
||||
def save(self):
|
||||
with open(self.log_file, 'w') as f:
|
||||
f.write("\n".join(self.messages))
|
||||
print(f"\nLog gespeichert: {self.log_file}")
|
||||
|
||||
|
||||
def run_mysql(query, database=None):
|
||||
"""Führt MySQL-Query aus und gibt Ergebnis zurück."""
|
||||
cmd = ["mysql", "-N", "-e", query]
|
||||
if database:
|
||||
cmd.extend(["-D", database])
|
||||
|
||||
result = subprocess.run(cmd, capture_output=True, text=True)
|
||||
if result.returncode != 0:
|
||||
return None, result.stderr
|
||||
return result.stdout.strip(), None
|
||||
|
||||
|
||||
def get_all_shops():
|
||||
"""Ermittelt alle JTL-Shop-Datenbanken."""
|
||||
query = """
|
||||
SELECT DISTINCT table_schema
|
||||
FROM information_schema.tables
|
||||
WHERE table_name = 'tartikel'
|
||||
AND table_schema NOT IN ('information_schema', 'mysql', 'performance_schema', 'sys')
|
||||
ORDER BY table_schema;
|
||||
"""
|
||||
result, err = run_mysql(query)
|
||||
if err:
|
||||
return []
|
||||
return [line.strip() for line in result.split('\n') if line.strip()]
|
||||
|
||||
|
||||
def check_index_exists(shop, table, check_column):
|
||||
"""Prüft ob ein Index für die Spalte bereits existiert."""
|
||||
query = f"""
|
||||
SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = '{shop}'
|
||||
AND table_name = '{table}'
|
||||
AND column_name = '{check_column}'
|
||||
AND index_name != 'PRIMARY';
|
||||
"""
|
||||
result, _ = run_mysql(query)
|
||||
return result and int(result) > 0
|
||||
|
||||
|
||||
def check_fulltext_exists(shop):
|
||||
"""Prüft ob FULLTEXT-Index bereits existiert."""
|
||||
query = f"""
|
||||
SELECT COUNT(*) FROM information_schema.statistics
|
||||
WHERE table_schema = '{shop}'
|
||||
AND table_name = 'tartikel'
|
||||
AND index_type = 'FULLTEXT';
|
||||
"""
|
||||
result, _ = run_mysql(query)
|
||||
return result and int(result) > 0
|
||||
|
||||
|
||||
def check_table_exists(shop, table):
|
||||
"""Prüft ob Tabelle existiert."""
|
||||
query = f"""
|
||||
SELECT COUNT(*) FROM information_schema.tables
|
||||
WHERE table_schema = '{shop}'
|
||||
AND table_name = '{table}';
|
||||
"""
|
||||
result, _ = run_mysql(query)
|
||||
return result and int(result) > 0
|
||||
|
||||
|
||||
def get_search_setting(shop):
|
||||
"""Liest aktuelle suche_fulltext Einstellung."""
|
||||
query = f"SELECT cWert FROM `{shop}`.teinstellungen WHERE cName = 'suche_fulltext' LIMIT 1;"
|
||||
result, _ = run_mysql(query)
|
||||
return result if result else "NICHT_GESETZT"
|
||||
|
||||
|
||||
def set_search_setting(shop, value):
|
||||
"""Setzt suche_fulltext Einstellung."""
|
||||
query = f"UPDATE `{shop}`.teinstellungen SET cWert = '{value}' WHERE cName = 'suche_fulltext';"
|
||||
_, err = run_mysql(query)
|
||||
return err is None
|
||||
|
||||
|
||||
def create_index(shop, index_name, table, columns, is_fulltext=False):
|
||||
"""Erstellt einen Index."""
|
||||
if is_fulltext:
|
||||
query = f"CREATE FULLTEXT INDEX `{index_name}` ON `{shop}`.`{table}` ({columns});"
|
||||
else:
|
||||
query = f"CREATE INDEX `{index_name}` ON `{shop}`.`{table}` ({columns});"
|
||||
|
||||
_, err = run_mysql(query)
|
||||
return err
|
||||
|
||||
|
||||
def generate_rollback_script(backup_data):
|
||||
"""Generiert ein Rollback-Skript."""
|
||||
script = f'''#!/usr/bin/env python3
|
||||
"""
|
||||
JTL Search Settings Rollback Script
|
||||
====================================
|
||||
Generiert am: {datetime.now().strftime("%Y-%m-%d %H:%M:%S")}
|
||||
|
||||
Dieses Skript stellt die ursprünglichen suche_fulltext Einstellungen wieder her.
|
||||
"""
|
||||
|
||||
import subprocess
|
||||
|
||||
BACKUP_DATA = {json.dumps(backup_data, indent=4)}
|
||||
|
||||
def run_mysql(query):
|
||||
result = subprocess.run(["mysql", "-N", "-e", query], capture_output=True, text=True)
|
||||
return result.returncode == 0
|
||||
|
||||
def main():
|
||||
print("=== JTL Search Settings Rollback ===")
|
||||
print(f"Stelle {{len(BACKUP_DATA)}} Einstellungen wieder her...\\n")
|
||||
|
||||
success = 0
|
||||
failed = 0
|
||||
|
||||
for shop, value in BACKUP_DATA.items():
|
||||
if value == "NICHT_GESETZT":
|
||||
print(f" [SKIP] {{shop}}: Keine Einstellung vorhanden")
|
||||
continue
|
||||
|
||||
query = f"UPDATE `{{shop}}`.teinstellungen SET cWert = '{{value}}' WHERE cName = 'suche_fulltext';"
|
||||
if run_mysql(query):
|
||||
print(f" [OK] {{shop}}: suche_fulltext = '{{value}}'")
|
||||
success += 1
|
||||
else:
|
||||
print(f" [FEHLER] {{shop}}")
|
||||
failed += 1
|
||||
|
||||
print(f"\\n=== Rollback abgeschlossen ===")
|
||||
print(f"Erfolgreich: {{success}}")
|
||||
print(f"Fehlgeschlagen: {{failed}}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
'''
|
||||
|
||||
with open(ROLLBACK_FILE, 'w') as f:
|
||||
f.write(script)
|
||||
|
||||
# Ausführbar machen
|
||||
subprocess.run(["chmod", "+x", ROLLBACK_FILE])
|
||||
|
||||
|
||||
def main():
|
||||
logger = Logger(LOG_FILE)
|
||||
|
||||
print("=" * 60)
|
||||
print("JTL Shop MariaDB Index-Optimierung")
|
||||
print("=" * 60)
|
||||
print()
|
||||
|
||||
# Phase 0: Shops ermitteln
|
||||
logger.log("Ermittle alle JTL-Shop-Datenbanken...")
|
||||
shops = get_all_shops()
|
||||
logger.log(f"Gefunden: {len(shops)} Shops")
|
||||
print()
|
||||
|
||||
if not shops:
|
||||
logger.log("Keine Shops gefunden. Abbruch.", "ERROR")
|
||||
return
|
||||
|
||||
# Phase 1: Backup der Sucheinstellungen
|
||||
print("=" * 60)
|
||||
print("PHASE 1: Backup der Sucheinstellungen")
|
||||
print("=" * 60)
|
||||
|
||||
backup_data = {}
|
||||
for shop in shops:
|
||||
setting = get_search_setting(shop)
|
||||
backup_data[shop] = setting
|
||||
logger.log(f" {shop}: suche_fulltext = '{setting}'")
|
||||
|
||||
# Backup speichern
|
||||
with open(BACKUP_FILE, 'w') as f:
|
||||
json.dump(backup_data, f, indent=2)
|
||||
logger.log(f"Backup gespeichert: {BACKUP_FILE}")
|
||||
|
||||
# Rollback-Skript generieren
|
||||
generate_rollback_script(backup_data)
|
||||
logger.log(f"Rollback-Skript generiert: {ROLLBACK_FILE}")
|
||||
print()
|
||||
|
||||
# Phase 2: Standard-Indizes erstellen
|
||||
print("=" * 60)
|
||||
print("PHASE 2: Standard-Indizes erstellen")
|
||||
print("=" * 60)
|
||||
|
||||
index_stats = {"created": 0, "skipped": 0, "failed": 0}
|
||||
|
||||
for idx in INDEXES:
|
||||
logger.log(f"\n--- Index: {idx['name']} auf {idx['table']} ---")
|
||||
|
||||
for shop in shops:
|
||||
# Prüfen ob Tabelle existiert
|
||||
if not check_table_exists(shop, idx['table']):
|
||||
continue
|
||||
|
||||
# Prüfen ob Index existiert
|
||||
if check_index_exists(shop, idx['table'], idx['check_column']):
|
||||
logger.log(f" [SKIP] {shop}: Index existiert bereits")
|
||||
index_stats["skipped"] += 1
|
||||
continue
|
||||
|
||||
# Index erstellen
|
||||
err = create_index(shop, idx['name'], idx['table'], idx['columns'])
|
||||
if err:
|
||||
logger.log(f" [FEHLER] {shop}: {err}", "ERROR")
|
||||
index_stats["failed"] += 1
|
||||
else:
|
||||
logger.log(f" [OK] {shop}: Index erstellt")
|
||||
index_stats["created"] += 1
|
||||
|
||||
print()
|
||||
|
||||
# Phase 3: FULLTEXT-Index erstellen
|
||||
print("=" * 60)
|
||||
print("PHASE 3: FULLTEXT-Index erstellen")
|
||||
print("=" * 60)
|
||||
|
||||
fulltext_stats = {"created": 0, "skipped": 0, "failed": 0}
|
||||
|
||||
for shop in shops:
|
||||
# Prüfen ob FULLTEXT bereits existiert
|
||||
if check_fulltext_exists(shop):
|
||||
logger.log(f" [SKIP] {shop}: FULLTEXT existiert bereits")
|
||||
fulltext_stats["skipped"] += 1
|
||||
continue
|
||||
|
||||
# FULLTEXT-Index erstellen
|
||||
logger.log(f" [WORK] {shop}: Erstelle FULLTEXT-Index (kann dauern)...")
|
||||
err = create_index(
|
||||
shop,
|
||||
FULLTEXT_INDEX['name'],
|
||||
FULLTEXT_INDEX['table'],
|
||||
FULLTEXT_INDEX['columns'],
|
||||
is_fulltext=True
|
||||
)
|
||||
|
||||
if err:
|
||||
logger.log(f" [FEHLER] {shop}: {err}", "ERROR")
|
||||
fulltext_stats["failed"] += 1
|
||||
else:
|
||||
logger.log(f" [OK] {shop}: FULLTEXT-Index erstellt")
|
||||
fulltext_stats["created"] += 1
|
||||
|
||||
print()
|
||||
|
||||
# Phase 4: FULLTEXT-Suche aktivieren
|
||||
print("=" * 60)
|
||||
print("PHASE 4: FULLTEXT-Suche aktivieren")
|
||||
print("=" * 60)
|
||||
|
||||
search_stats = {"updated": 0, "skipped": 0, "failed": 0}
|
||||
|
||||
for shop in shops:
|
||||
current = backup_data.get(shop, "NICHT_GESETZT")
|
||||
|
||||
if current == "Y":
|
||||
logger.log(f" [SKIP] {shop}: Bereits aktiviert")
|
||||
search_stats["skipped"] += 1
|
||||
continue
|
||||
|
||||
if current == "NICHT_GESETZT":
|
||||
logger.log(f" [SKIP] {shop}: Keine Einstellung vorhanden")
|
||||
search_stats["skipped"] += 1
|
||||
continue
|
||||
|
||||
# Einstellung ändern
|
||||
if set_search_setting(shop, "Y"):
|
||||
logger.log(f" [OK] {shop}: suche_fulltext = 'Y' (war: '{current}')")
|
||||
search_stats["updated"] += 1
|
||||
else:
|
||||
logger.log(f" [FEHLER] {shop}", "ERROR")
|
||||
search_stats["failed"] += 1
|
||||
|
||||
print()
|
||||
|
||||
# Zusammenfassung
|
||||
print("=" * 60)
|
||||
print("ZUSAMMENFASSUNG")
|
||||
print("=" * 60)
|
||||
print()
|
||||
print("Standard-Indizes:")
|
||||
print(f" Erstellt: {index_stats['created']}")
|
||||
print(f" Übersprungen: {index_stats['skipped']}")
|
||||
print(f" Fehlgeschlagen: {index_stats['failed']}")
|
||||
print()
|
||||
print("FULLTEXT-Index:")
|
||||
print(f" Erstellt: {fulltext_stats['created']}")
|
||||
print(f" Übersprungen: {fulltext_stats['skipped']}")
|
||||
print(f" Fehlgeschlagen: {fulltext_stats['failed']}")
|
||||
print()
|
||||
print("Sucheinstellung (suche_fulltext = 'Y'):")
|
||||
print(f" Aktualisiert: {search_stats['updated']}")
|
||||
print(f" Übersprungen: {search_stats['skipped']}")
|
||||
print(f" Fehlgeschlagen: {search_stats['failed']}")
|
||||
print()
|
||||
print("=" * 60)
|
||||
print("WICHTIGE DATEIEN")
|
||||
print("=" * 60)
|
||||
print(f" Backup: {BACKUP_FILE}")
|
||||
print(f" Rollback: {ROLLBACK_FILE}")
|
||||
print(f" Log: {LOG_FILE}")
|
||||
print()
|
||||
print("Bei Problemen Rollback ausführen mit:")
|
||||
print(f" python3 {ROLLBACK_FILE}")
|
||||
print()
|
||||
|
||||
# Log speichern
|
||||
logger.save()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user