Files
Snippets/mysql_binary_search.md
2026-07-06 11:32:17 +02:00

161 lines
4.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# MySQL Binary Search Fehlerhafte Zeile automatisch finden
## Hintergrund
Bei der Migration von Daten zwischen zwei Tabellen treten gelegentlich Fehler auf. Mit `LIMIT` und `OFFSET` lässt sich der fehlerhafte Bereich manuell eingrenzen dieser Prozess kann jedoch durch einen Binary-Search-Ansatz vollständig automatisiert werden.
---
## Einfaches Skript (SELECT *)
Für ein simples `SELECT *` ohne komplexe Query:
```bash
#!/bin/bash
# Konfiguration
DB_USER="root"
DB_PASS="password"
DB_NAME="datenbank"
SOURCE_TABLE="quelle"
TARGET_TABLE="ziel"
# Gesamtanzahl Zeilen ermitteln
TOTAL=$(mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -sNe \
"SELECT COUNT(*) FROM $SOURCE_TABLE;")
echo "Gesamt Zeilen: $TOTAL"
LOW=0
HIGH=$TOTAL
while [ $((HIGH - LOW)) -gt 1 ]; do
MID=$(( (LOW + HIGH) / 2 ))
FETCH=$((MID - LOW))
echo "Teste OFFSET=$LOW LIMIT=$FETCH ..."
ERROR=$(mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" 2>&1 <<EOF
INSERT INTO $TARGET_TABLE
SELECT * FROM $SOURCE_TABLE
LIMIT $FETCH OFFSET $LOW;
EOF
)
if echo "$ERROR" | grep -qi "error"; then
HIGH=$MID
echo " → Fehler gefunden, eingrenzen bis Zeile $HIGH"
else
LOW=$MID
echo " → OK, weiter ab Zeile $LOW"
fi
done
echo ""
echo "=== Fehlerhafte Zeile gefunden ==="
echo "OFFSET: $LOW"
mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e \
"SELECT * FROM $SOURCE_TABLE LIMIT 1 OFFSET $LOW;"
```
---
## Erweitertes Skript (komplexe Query mit JOINs, MAX etc.)
Wenn die Source-Query komplex ist (JOINs, Aggregatfunktionen, GROUP BY), wird sie als Variable definiert und als Subquery genutzt.
> **Wichtig:** Ein `ORDER BY` ist zwingend erforderlich, damit die Zeilenreihenfolge zwischen den Aufrufen deterministisch bleibt.
```bash
#!/bin/bash
# Konfiguration
DB_USER="root"
DB_PASS="password"
DB_NAME="datenbank"
# Deine Source-Query OHNE LIMIT/OFFSET als Subquery nutzbar
SOURCE_QUERY="SELECT a.id, MAX(b.wert), a.name
FROM quelle a
JOIN andere b ON a.id = b.ref_id
GROUP BY a.id, a.name
ORDER BY a.id"
TARGET_TABLE="ziel"
# Gesamtanzahl Zeilen der Query ermitteln
TOTAL=$(mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -sNe \
"SELECT COUNT(*) FROM ($SOURCE_QUERY) AS _count_query;")
echo "Gesamt Zeilen: $TOTAL"
LOW=0
HIGH=$TOTAL
while [ $((HIGH - LOW)) -gt 1 ]; do
MID=$(( (LOW + HIGH) / 2 ))
FETCH=$((MID - LOW))
echo "Teste OFFSET=$LOW LIMIT=$FETCH ..."
ERROR=$(mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" 2>&1 <<EOF
INSERT INTO $TARGET_TABLE
$SOURCE_QUERY
LIMIT $FETCH OFFSET $LOW;
EOF
)
if echo "$ERROR" | grep -qi "error"; then
HIGH=$MID
echo " → Fehler, eingrenzen bis Zeile $HIGH"
else
LOW=$MID
echo " → OK, weiter ab Zeile $LOW"
fi
done
echo ""
echo "=== Fehlerhafte Zeile ==="
mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e \
"$SOURCE_QUERY LIMIT 1 OFFSET $LOW;"
```
---
## Gezielten Wert aus der fehlerhaften Zeile ausgeben
### Variante 1 Subquery-Wrapping
```bash
echo "=== Fehlerhafte Zeile ==="
mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e \
"SELECT a.id, a.name FROM ($SOURCE_QUERY) AS _fehler LIMIT 1 OFFSET $LOW;"
```
### Variante 2 Direkte Query (empfohlen bei Aggregaten)
```bash
echo "=== Fehlerhafte Zeile Detailansicht ==="
mysql -u"$DB_USER" -p"$DB_PASS" "$DB_NAME" -e \
"SELECT
a.id AS 'ID',
a.name AS 'Name',
MAX(b.wert) AS 'Max-Wert'
FROM quelle a
JOIN andere b ON a.id = b.ref_id
GROUP BY a.id, a.name
ORDER BY a.id
LIMIT 1 OFFSET $LOW;"
```
Variante 2 ist bei `MAX()` und anderen Aggregatfunktionen übersichtlicher, da die Rohwerte direkt aus der Query kommen und nicht erst durch ein Subquery-Wrapping gehen.
---
## Hinweise
- **ORDER BY** ist Pflicht für deterministische Ergebnisse bei wiederholten Aufrufen.
- Die erfolgreichen INSERT-Blöcke bleiben in der Zieltabelle erhalten bei Bedarf kann jeder Block in eine Transaktion mit `ROLLBACK` bei Fehler gekapselt werden.
- Der Binary Search reduziert die Anzahl der Testläufe auf `log₂(n)` bei 1000 Zeilen also maximal 10 Schritte.