Archiv der Kategorie: MySQL

Den jeweils aktuellesten Datensatz zur jeweiligen Kategorie ermitteln

Eine immer wiederkehrende Problemstellung: In einer Tabelle sind zu verschiedenen Kategorien mehrere Einträge gespeichert (z.B. ein Forum o.ä.). Nun gilt es, zu jeder Kategorie den jeweils aktuellsten Datensatz zu ermitteln.

Wir haben beispielsweise folgende Tabelle:

CREATE TABLE `tab1` (
  `id` int(11) default NULL,
  `cat` int(11) default NULL,
  `titel` char(100) default NULL,
  `time` time default NULL
);

Die Tabelle enthält natürlich auch Daten:

+------+------+------------+----------+
| id   | cat  | titel      | time     |
+------+------+------------+----------+
|    1 |    1 | datensatz1 | 00:00:15 |
|    2 |    2 | datensatz2 | 00:01:00 |
|    3 |    3 | datensatz3 | 00:02:00 |
|    4 |    1 | datensatz4 | 00:01:00 |
|    5 |    2 | datensatz5 | 00:02:15 |
|    6 |    3 | datensatz6 | 00:01:15 |
+------+------+------------+----------+

(insert into tab1 values (1, 1, ‚datensatz1′, ’00:00:15‘), (2, 2, ‚datensatz2′, ’00:01:00‘), (3, 3, ‚datensatz3′, ’00:02:00‘), (4, 1, ‚datensatz4′, ’00:01:00‘), (5, 2, ‚datensatz5′, ’00:02:15‘), (6, 3, ‚datensatz6′, ’00:01:15‘);)

Um nun das gewünschte Ergebnis zu bekommen, benutz man eine sog. Self-Left-Join, also einen Left-Join mit der selben Tabelle:

SELECT x.* FROM tab1 AS x LEFT JOIN tab1 AS y
  ON x.cat = y.cat AND x.time < y.time
  WHERE y.id IS NULL ORDER BY cat;

+------+------+------------+----------+
| id   | cat  | titel      | time     |
+------+------+------------+----------+
|    4 |    1 | datensatz4 | 00:01:00 |
|    5 |    2 | datensatz5 | 00:02:15 |
|    3 |    3 | datensatz3 | 00:02:00 |
+------+------+------------+----------+

Nach einem Post von Björn Brinkhoff (bjoern-at-brinkhoff-dot-org) in der deutschen MySQL-Mailinglist mysql-de@lists.mysql.com am 13.11.2007

Zwei verschiedene MySQL-Server auf einem Rechner

Wie bringt man einen 4.1-Server und einen 5.0-Server auf einem Linux-Rechner gleichzeitig ans laufen? Diese Frage stellte sich mir kürzlich, da ich verschiedene Intra- und Extra-Websites betreue und hier gibts bei einer nun auch Mysql 5.0. Die anderen laufen aber noch mit der ‚alten‘ Version 4.1

Nach einiger Recherche fand ich zwar eine Anleitung von Kristian Köhntopp, wie man viele verschiedenen MySQL Instanzen auf einem Server recht komfortabel zum Laufen bringt, aber ich wollte nur eine zusätzliche Instanz und hatte auch keine Lust, den Server selbst zu kompilieren (was von MySQL AB auch nicht wirklich empfohlen wird). So suchte ich nach einer Lösung, das Ganze mit den von MySQL bereitgestellten RPMs zu machen.

Zzt. ist auf meinem Linux-Rechner die Version 4.1 installiert (von den SuSE-10.0 CDs) und läuft.

Zwei Dinge mussten bewerkstelligt werden:

  1. Die Installation des zweiten Servers muss in andere Verzeichnisse erfolgen, als im RPM-Paket vorgesehen.
  2. Der 5.0er Server muss einen anderen TCP-Port und einen anderen Socket benutzen. Dazu muss eine eigene my.cnf benutzt werden.

Punkt 1 kann mit der relocate-Funktion von RPM bewerkstelligt werden. Zusätzlich mussten noch ein paar Dateien gepatcht werden, damit der Server auch die neuen Verzeichnisse findet. Doch dazu später mehr.

Die Sachen mit den Config-Dateien ist relativ einfach, der Server (und auch der Client) sucht sie entweder in /etc, im sog. Datadir (normalerweise /var/lib/mysql) oder als .my.cnf im Home-Verzeichnis des Users. Wenn also in /etc nichts liegt aber im jeweiligen Datadir (was ja unbedingt unterschiedlich sein muss), findet jeder Server automatisch die richtige Config-Datei. Na ja, so ganz automatisch nicht, dazu muss ein wenig an den Start-Scripten geschraubt werden. Doch zunächst mal zur

Installation.

Man besorge sich die aktuellen RPMs bei www.mysql.com, bei mir waren es

  • MySQL-server-5.0.21-0.glibc23.i386.rpm
  • MySQL-Max-5.0.21-0.glibc23.i386.rpm
  • MySQL-client-5.0.21-0.glibc23.i386.rpm

Wer die speziellen Funktionalitäten des Max-Servers nicht braucht lässt ihn weg. Ebenso kann auch auf das Client-Paket verzichtet werden, aber wo man gerade schon mal dabei ist …

Als Nächstes sollte man die bestehenden Datenbanken sichern, man weiß ja nie 😎

Dazu wird erstmal der laufenden Server gestoppt:

rcmysql stop

Danach die Datenbanken sichern:

cd /var/lib
cp -prv mysql mysql41

Die Config-Datei und das Start-Script sollten auch vorsichtshalber in Sicherheit gebracht werden:

cp -pv /etc/my.cnf /etc/my41.cnf
cp -pv /etc/init.d/mysql /etc/init.d/mysql41

Nun die Verzeichnisse anlegen:

mkdir -p /usr/local/mysql50 /var/lib/mysql50

Wir sehen, das Installationsverzeichnis soll /usr/local/mysql50 sein und das Datadir, in dem später u.a. die Datenbank-Dateien liegen, soll /var/lib/mysql50 sein.

Entsprechend werden die Installationspfade per –relocate=<alt>=<neu> verbogen (siehe man rpm).
Mit –test macht man erstmal ein Testlauf:

rpm -vv --test
  --relocate=/usr/share/mysql/=/usr/local/mysql50/share/mysql/
  --relocate=/usr/share/man/=/usr/local/mysql50/man/
  --relocate=/usr/share/info/=/usr/local/mysql50/info/
  --relocate=/usr/sbin/=/usr/local/mysql50/sbin/
  --relocate=/usr/lib/mysql/=/usr/local/mysql50/lib/
  --relocate=/usr/bin/=/usr/local/mysql50/bin/
  --relocate=/etc/=/usr/local/mysql50/etc/
    -ih MySQL-server-5.0.21-0.glibc23.i386.rpm

Wenn das soweit gut aussieht kann man den Test-Schalter weglassen und installieren.
Nach dem Server kann man nun auf die gleiche Art das Max- und das Client-Paket installieren, so man will.

Konfiguration und Einstellung

Nun sollte man eine Konfigdatei erstellen und nach /var/lib/mysql50 legen. Wichtig ist, dass sich die Angaben für Port und Socket von denen der 4.1er Konfigdatei unterscheiden.
Desweiteren ganz wichtig: in der my.cnf für den 5.0er Server muss die folgenden Zeile beim Abschnitt [mysqld] rein:

language = /usr/local/mysql50/share/mysql/english

Sonst sucht der Server diese Datei in /usr/share/mysql/english/errmsg.sys und findet logischerweise die der 4.1er-Version, was nicht passt (im error-log steht dann: „Error message file ‚/usr/share/mysql/english/errmsg.sys‘ had only 304 error messages, but it should contain at least 463 error messages.“)
Meine Konfig-Datei sieht so aus: my.cnf

Die alte my.cnf aus /etc wird nach /var/lib/mysql verschoben, damit jeder Server seine eigene Datei benutzt. In /etc darf keine mehr sein!

Nun das Startscript ins richtige Verzeichnis kopieren und umbenennen:

cp /usr/local/mysql50/etc/init.d/mysql /etc/init.d/mysql50

In dieser Datei müssen basedir und datadir entspr. gesetzt werden:
basedir=/usr/local/mysql50
datadir=/var/lib/mysql50

Dann muss ein wenig gebastelt werden, denn das Hilfs-Startscript mysqld-safe in /usr/local/mysql50/bin interessiert sich nicht richtig für die vom eigentlichen Startscript übergebenen Werte sondern versucht selbst basedir und datadir zu ermitteln und findet, da dort die falschen Vorgaben drin stehen, dabei die falschen Pfade. Dadurch wird schlussendlich die falsche my.cnf gefunden und somit die falschen Vorgaben verwendet (bei SuSEs mysqld-safe sind andere defaults eingebaut). Ich habe das korrigiert:

116c116
< if test -f ./share/mysql/english/errmsg.sys -a -x ./bin/mysqld
---
> if test -f ./share/mysql/english/errmsg.sys -a -x ./sbin/mysqld
119c119
<   ledir=$MY_BASEDIR_VERSION/bin               # Where mysqld is
---
>   ledir=$MY_BASEDIR_VERSION/sbin      # Where mysqld is
150c150
<   DATADIR=/var/lib/mysql
---
>   DATADIR=/var/lib/mysql50

Zum Einspielen des Patches benennt man mysqld_safe in mysqld_safe-orig um und verwendet das Programm patch zum editieren:

patch -o mysqld_safe mysqld_safe-orig mysqld_safe.patch

… 3 – 2 – 1 – Start

Nun sollte sich der Server mit

/etc/init.d/mysql50 start

zum Leben erwecken lassen. Mit netstat -ltn kann man prüfen, ob der Server auf dem neuen Port lauscht.

Dem Client muss man nun natürlich sagen, mit welchem Server er in Verbindung treten soll:

mysql -S /var/lib/mysql50/mysql.sock -u root -p

Dabei wird der alte Client der SuSE-Installation verwendet. Um den neuen Client zu verwenden, muss man ihn mit vollen Pfad ansprechen:

/usr/local/mysql50/bin/mysql -S /var/lib/mysql50/mysql.sock -u root -p

Klappt natürlich nur, wenn man ihn wie oben angegeben installiert hat.

Man kann das Ganze aber mit Hilfe der entspr. cnf-Datei machen:

/usr/local/mysql50/bin/mysql --defaults-file=/var/lib/mysql50/my.cnf -u root -p mysql

Bei PHP muss man entweder in der php.ini den entsprechenden Socket angeben oder die PHP-Variable mysql.default_socket bzw. mysql.default_port in der Apache-Konfig oder per htaccess definieren. Natürlich kann man auch den Port oder Socket beim Aufruf von mysql_connect() an dem Hostnamen anhängen. Man sollte bei der Verwendung von Port aber beachten, das man „127.0.0.1“ statt „localhost“ benutzt, mysql_connect zieht sonst u.U. den (falschen) Socket vor. Ich empfehle die Lektüre der entsprechenden PHP-Doku.

So, und nun wünsche ich viel Spaß beim nachbasteln…