Archiv der Kategorie: Datenbank

MySQL: RENAME SCHEMA or DATABASE Workarounds

Leider ist es nicht möglich ein Schema bzw. Datenbank in MySQL mittels eines SQL-Standard-Befehls umzubennen. Hier folgen einige Workarounds:

SQL-Kommando in einer Bash-Schleife

mysql [OPTIONS] old_schema -sNe 'show tables' | 
while read table; 
  do mysql [OPTIONS] -sNe 
    "RENAME TABLE old_schema.$table TO old_schema.$table"; 
  done

mysqldump

mysqldump [OPTIONS] --database oldSchema > oldSchema.sql
mysql new_schema < oldSchema.sql

ein SQL-Kommando bauen

SET SESSION group_concat_max_len = 4294967295;

SELECT 
    CONCAT(
      'RENAME TABLE ', 
      GROUP_CONCAT( 
        table_schema,'.',table_name, ' TO ','new_schema.',table_name,' '
      ),
      ';'
    ) as stmt 
  FROM information_schema.TABLES 
  WHERE table_schema LIKE 'oldSchema' 
  GROUP BY table_schema;

Welche Alternative man jetzt nimmt, ist zum einen Geschmacksache und zum Anderen hängt es von der Datenbank ab, z.B. ist sie Groß, steht sie unter Dampf etc.

PostgreSQL: Passwort MD5 ermitteln

Immer mal wieder geschieht es, dass ich den PostgreSQL MD5 Hash im Vorfeld ermitteln möchte, z.B. in SQL-Skripten, die Datenbankbenutzer anlegen und ich das Passwort nicht im Klartext ablegen möchte.

Nach ein wenig Recherche und ausprobieren, ist folgendes Bash-Kommando entstanden, um den Hash für das PostgreSQL-Passwort zu generieren.

echo "md5$(echo -n 'PasswordUser' | md5sum )" | tr -d "-"


# Beispiel
# user: Thomas
# pwd: 123

echo "md5$(echo -n '123Thomas' | md5sum )" | tr -d "-"

# Ausgabe: md5b5a51046812abff6595fc57b4e8adb4f

Dies Kommando erspart ab sofort den umständlichen Weg, den Benutzer mit CREATE USER … PASSWORD ‚…‘ anzulegen und dann in der Tabelle pg_catalog.pg_shadow nachzuschauen, wie der MD5 Hash vom vergebenen Passwort ist.

SQL spielend lernen

Um SQL nicht nur kennenzulernen, sondern auch auf eine spielerische Weise auszuprobieren, ist dieses Text Adventure eine gute Wahl: SQL ISLAND

Das Spielprinzip ist recht simple: Man landet auf einer Insel und um weiter voran zu kommen, sind SQL Kommandos einzugeben. In die SQL Kommandos wird man an passender Stelle auch eingeführt.

MySQL: Nachverfolgung von Änderungen der Datenbankstruktur

Es kommt immer irgendwann ein Zeitpunkt, wo man wissen möchte, wann sich Tabelle X bzw. Prozedur Y wie geändert hat.

In der Softwareentwicklung wird für die Code-Änderungen ein Versionierungstool (z.B. GIT) eingesetzt. Nach dem gleichen Prinzip sind auch Datenbankstrukturänderungen versionierbar.

Der einfachste Weg ist, sich mit mysqldump (per cron-job einmal täglich) einen Datenbank-Dump zu ziehen und einzuchecken. Der Nachteil dabei ist, dass immer nur die komplette Datei versioniert ist. Somit ist schlecht sichtbar, wann ein spezielles Datenbankobjekt sich wie geändert hat.

Um diesen Nachteil zu bereinigen, ist der Dump in die einzelnen Datenbankobjekte zu unterteilen, so dass für jede Tabelle, jede Prozedur etc. eine Datei existiert. Diese vielen kleinen Dateien werden dann eingecheckt.

#!/bin/bash 
HOST= 
PORT= 
USER=
DATABASE=

# jede Tabelle eine Datei
mysqldump -h $HOST -P $PORT -u $USER --tab=../dump/table --skip-dump-date --no-data --skip-opt $DATABASE 

# Prozeduren und danach mittels Perl-Skript in kleine Dateien aufteilen
mysqldump -h $HOST -P $PORT -u $USER  --routines --no-create-info --no-data --no-create-db --compact $DATABASE | ./seperate_proc.pl

Und hier noch das kleine Perl-Skript

#!/usr/bin/perl
#
use strict;
use warnings;
use utf8;

my $output_dir = "../dump/procedure";

sub getProcName() {
    (my $name = $_) =~ s/^CREATE\s+([^\s]+)\s+`([^\s`(]+)`\s*\(?.*/$2/eg;
    return $name;
}

## main
my %procs = ();

my $proc_name = "";
my $delimiter = ';';

while (<>) {
    if (/^DELIMITER\s+/) {
        $delimiter = $_;
        $delimiter =~ s/^DELIMITER\s+(.+)$/$1/eg;
    }

    if (/^CREATE\s+/) {
        if (/DEFINER=/) {
            # remove DEFINER
            s/^(.+\s+)DEFINER=`[^\s]+`\s+(.+)$/$1$2/;
        }
        if (/^CREATE\s+(FUNCTION|PROCEDURE)/) {
            $proc_name = "$1." . &getProcName($_);
            # append DELIMITER to first line
            $procs{$proc_name} .= "DELIMITER $delimiter\n";
        }
    }

    if ($proc_name) {
        $procs{$proc_name} .= $_;
    }

    if (/END\s+$delimiter/) {
        $proc_name = "";
    }
}

# store files
mkdir($output_dir);
for (keys %procs) {
    my $key = $_;
    chomp;
    my $filename = "$output_dir/$_.sql";
    print "Dump $_ -> $filename\n";
    open(my $fh, '>', $filename) or die "Could not open file '$filename' $!";
    print $fh $procs{$key};
    close $fh;
}

print "done\n";

 

PostgreSQL: Datenbankmonitoring

Um die Datenbank besser zu verstehen, muss sie gut mit verschiedenen Tools bzw. Skripten überwacht werden. Jedes Tool hat seine Stärken und Schwächen, weil jedes Tool sein Hauptaugenmerk auf was anderes legt.

Tool
Beschreibung
Datenbasis
Stärken
Schwächen / nicht geeignet für
Links
icinga ein Standard-Monitoring Tool
für Admins zur Infrastruktur-Überwachung
  • Daten aus HW & BS
  • check_postgres Plugin
  • einzelne Abfragen (z.B. Anz. Verbindungen)

 

  • Alarmierung
  • Überwachung
    • Hardware
    • Betriebssystem
    • Postgres als Dienst
Postgres-interne Analyse, dafür müßte viel erweitert werden https://www.icinga.org/
Graphite
ein Standard-Daten-Visualisierungs-Tool Beliebige Daten, die hingesendet werden mit Grafana läßt sich ein cooles Dashboard bauen http://graphite.readthedocs.io/en/latest/

https://graphiteapp.org/

http://grafana.org/

pgtop Skript Daten aus pg_stat_activity zeigt in real-time die aktuellen Prozesse http://search.cpan.org/dist/pgtop/
pgbadger ein Perl-Skript, dass das PostgreSQL-Logfile

  1. parst
  2. Daten aggregiert
  3. Grafiken erstellt
  4. in HTML visualisiert

 

Daten vom

  • postgresql.log
  • pgbouncer (zukünftig)
Analyse der aufgetretenden

  • Fehler
  • Locks
  • VACUUM / ANALYZE
  • langangsamen Abfragen
  • Verbindungen
DB Informationen, wie

  • keine Größen von Indexen & Tabellen
  • nicht real-time => Daten vom Vortag
http://dalibo.github.io/pgbadger/
pgcluu ein Perl-Skript, dass

  1. Daten sammelt
  2. aggregiert
  3. Grafiken erstellt
  4. in HTML visualisiert
Daten vom

  • Betriebssystem
  • PostgreSQL Systemtabellen
nicht real-time => Daten vom Vortag http://pgcluu.darold.net/
PoWA Real-Time Monitoring Daten vom PostgreSQL-Systemtabellen
  • Real-Time Monitoring
  • aktuelle Prozesse & Locks (inkl. Verbesserungsvorschläge vom Tool)
keine Langzeitanalysen http://dalibo.github.io/powa/

Teste die Datenbank!

Testautomatisierung, Test Driven Development und andere Buzzwords rund ums Testen sind bereits in der Software Entwicklung seit mehreren Jahren keine Neuigkeiten mehr. Dennoch werden Tests auf der Datenbank meist stiefmütterlich behandelt, obwohl die Datenbank ein wichtige zentrale Rolle in der gesamten Applikationslandschaft hat. Um Datenbankfunktionalitäten zu testen, begegnen mir oft gewaltige applikationsseitige Integrationstest, die beim Continuous Integration Prozess sehr lange dauern können. Wäre es nicht schön Datenbankänderungen schneller zu testen? Mit jeder Programmiersprache können einfache und schnelle Unit Tests geschrieben werden. Warum soll das bei SQL anders sein?

In diesem Vortrag wird mit Hilfe des pgTAP Frameworks gezeigt, wie verschiedene Datenbankobjekte (Tabellen, Sichten, Funktion, Trigger, Operatoren u.a.) mit einfachen Unit Test direkt und ohne Applikationslogik auf der PostgreSQL schnell getestet werden können. Dabei werden auch Best Practices zur Vorbereitung, Ausführungen und Aufräumen von Testdaten vorgestellt, die ich in den vergangenen Jahren sammeln konnte.

Hier die Slides von meinem Vortrag auf der 2. Swiss PostgreSQL Conference in Rapperswil.

Teste_Die_Datenbank-Thomas_Koch

 

Eine ausführliche Beschreibung zu pgTAP ist auch hier und natürlich auf pgtap.org zu finden.

Anzahl der Datenbankverbindungen

Die aktuelle Anzahl der Datenbankverbindungen ist stets im Auge zu behalten. Wieviel die Datenbank maximal an Verbindungen zulässt, ist bei jeder Datenbank konfigurierbar. Die maximale Anzahl der Datenbankverbindungen sollte nie zu hoch und nicht zu gering eingestellt werden.

Um die Datenbankverbindungen im Auge zu behalten, bietet sich an den Wert mittels eines Alert-System (z.B. Icinga) zu überwachen. Hier mal eine PostgreSQL-Abfrage zum Ermitteln des Schwellwertes.

SELECT connection_cnt, connection_max,  connection_cnt/(connection_max::FLOAT)*100 AS connection_percent	
  FROM
    (SELECT COUNT(*) AS connection_cnt FROM pg_stat_activity) a,	
    (SELECT setting AS connection_max FROM pg_settings WHERE name LIKE 'max_connections') s;

 

Problem: Zwei verschiedene IDs in einer Fremdschlüsselspalte

Ich bin heute auf folgendes Problem gestoßen:

In einer ID-Spalte sollte ein Foreign Key Constraint angelegt werden. Dabei wurde festgestellt, dass die IDs aus zwei verschiedenen Tabellen kommen, z.B. Tabellen author und administrator.

Einige werden bestimmt gleich aufschreien: „Wozu zwei Tabellen, wenn Sie sich nur in Ihrer Rolle unterscheiden?“ Die kurze Antwort darauf: „Sie ist bereits seit einigen Jahren da und die ganze Software arbeitet damit schon immer.“ Abgesehen von der Ursache gilt es das Problem zu lösen.

CREATE TABLE author(id BIGINT, name TEXT);
CREATE TABLE administrator(id BIGINT, description TEXT);

INSERT INTO author VALUES (100, 'Rainer');
INSERT INTO adminstrator VALUES (1, '...');

CREATE TABLE tableA (id INT, user_id, description TEXT);

 

1. Möglichkeit: Inheritance

Es wird eine Tabelle user erstellt. Diese Tabelle dient als Elterntabelle für administrator und author.

CREATE TABLE user(id BIGINT PRIMARY KEY);
ALTER TABLE author INHERIT user;
ALTER TABLE administrator INHERIT user;

-- liefert alle user IDs
SELECT * FROM user;

ALTER TABLE tableA 
  ADD CONSTRAINT "fk_tableA#user_id2user#id" 
    FOREIGN KEY (user_id) REFERENCES user(id);

Schön gedacht – aber funktioniert nicht. Der Grund ist in der PostgreSQL Dokumentation beschrieben.

ERROR:  insert or update on table „tableA“ violates foreign key constraint „fk_tableA#user_id2user#id“
DETAIL:  Key (user_id)=(100) is not present in table „user“.

2. Möglichkeit: Selbstdefinierte Trigger

Die Idee ist einfach: In einem Trigger wird geprüft, ob die user_id vorhanden ist oder nicht. Beim Fehler der user_id mit einer RAISE EXCEPTION abgebrochen.

CREATE OR REPLACE FUNCTION tr_user_check()
  RETURNS TRIGGER AS
$BODY$
BEGIN
  IF ((TG_OP = 'INSERT') OR (TG_OP = 'UPDATE')) THEN
    IF NOT EXISTS (SELECT id FROM user WHERE id = NEW.user_id) THEN
      RAISE EXCEPTION 'user_id=% NOT FOUND in user (administrator, author)', NEW.user_id;
    END IF;
  END IF;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql;
CREATE TRIGGER trIU_tableA_user_id_check
BEFORE INSERT OR UPDATE
ON tableA
FOR EACH ROW
EXECUTE PROCEDURE tr_user_check();

INSERT INTO tableA (user_id) VALUES (113); -- error user not exists
INSERT INTO tableA (user_id) VALUES (114); -- yes user exists

Auf ähnliche Weise noch ein DELETE/UPDATE-Trigger auf die Tabellen author, administrator sowie user definiert, der das Ändern der IDs verhindert (Constraint attribute ON UPDATE NO ACTION) oder beim Löschen aufräumt (Constraint ON DELETE CASCADE) und schon ist der Constraint-Check selbst implementiert.

3. Möglichkeit: Table Refactoring

Natürlich bleibt auch immer die Möglichkeit. Eine Tabelle user anlegen, die eine Spalte role (mit den Werten: author, adminstrator) besitzt und die Daten aus den Tabellen author und administrator migrieren. Natürlich stellt sich hier die Frage, wie man mit dem Problem umgeht, wenn die Attribute beide Tabellen zu unterschiedlich sind. Aber auch dafür gibt es mehrere Lösungen.

Skript zum Säubern

Wer die Kommandos mal ausgeführt hat, um selber mal rumzuspielen hier die Befehle zum Säubern.

DELETE FROM tableA WHERE user_id IN (111,113);
DROP TRIGGER trIU_tableA_user_id_check ON tableA;
DROP FUNCTION tr_user_check();
ALTER TABLE author NO INHERIT user;
ALTER TABLE administrator NO INHERIT user;
DROP TABLE user;
DROP TABLE author;
DROP TABLE adminstrator;

 

Abfrageergebnis als eine komma-separierte Liste einer Gruppierung

Bin heute über eine schlechte Implementierung von folgender Aufgabe gestoßen:

Gebe eine komma-separierte Liste (comma separate value) innerhalb der Gruppierung aus.

Bevor wir zur Lösung kommen, sollten wir uns noch ein Set von Daten zum Testen vorbereiten. Danach werden wir uns verschiedene Lösungen anschauen, die in den unterschiedlichsten PostgreSQL Versionen funktionieren.

Vorbereitung

-- Schema als Spielwiese anlegen
CREATE SCHEMA play;

-- Tabelle mit den Testdaten erstellen
CREATE TABLE play.str_agg(value INT, value_group INT);

INSERT INTO play.str_agg VALUES (1,1);
INSERT INTO play.str_agg VALUES (3,1);
INSERT INTO play.str_agg VALUES (5,1);
INSERT INTO play.str_agg VALUES (4,1);
INSERT INTO play.str_agg VALUES (2,1);

Vor PostgreSQL 8.4

Vor der 8.4 (und natürlich auch die nachfolgenden Versionen) benötigen wir für die Erledigung der Aufgabe ein besonderes PostgreSQL-Objekt. Wenn wir von Gruppierungen sprechen, liegt es nahe die Lösung mit Aggregaten anzugehen.

CREATE AGGREGATE play.to_csv(
  basetype    = TEXT,
  sfunc       = textcat,
  stype       = TEXT,
  initcond    = ''
);

SELECT value_group, 
    play.to_csv(value::TEXT || ',') AS _csv
  FROM play.str_agg 
  GROUP BY value_group;

Ab PostgreSQL 8.4

Mit der Version 8.4 kamen einige array-Funktionen (array_agg, array_to_string) mit, die wir jetzt benötigen und somit auf das Aggregat verzichten können.

SELECT value_group, 
    array_to_string(
      array_agg(value), ',') AS _csv
  FROM play.str_agg
  GROUP BY value_group;

Ab PostgreSQL 9.0

Mit der Version 9.0 existiert für die Aufgabe nun eine spezielle Funktion – string_agg.

SELECT value_group,
    string_agg(value::text, ',') AS _csv
  FROM play.str_agg 
  GROUP BY value_group;