2008-11-12

Flytt och migrering av MySQL

I ett uppdrag nyligen behövde jag kopiera över en MySQL-databas till en annan maskin och samtidigt automatisera en migrering från det gamla databasschemat till ett nytt. Det finns självklart proffs som vet hur man gör sådana här saker på Det Sanna Rätta Sättet, men det här är det recept jag använde och det fungerade för mig.

Det första steget är att helt och hållet kopiera en databas till en annan "tom" maskin. Eftersom jag var (och är) en nybörjar-MySQL-administratör behövde jag först få ett grepp om hur MySQL lagrar användare och hur "databases" särskiljs osv. Det slutade med följande harang för att dumpa den gamla databasen:

mysqldump --opt -u "someone" --password="zonk" -B mydb | bzip2 -c > mydb-dump.sql.bz2

Det här använder lite trevliga defaultvärden (–opt) och sparar MySQL-databasen kallad "mydb" till en bzip2-komprimerad fil med SQL-uttryck som kan återskapa den, inklusive schema, via användaren "someone" med lösenord "zonk".

För att bara få ut schemat kan du lägga till växeln "-d" till mysqldump och skippa kompressionen så här:

mysqldump --opt -u "someone" --password="zonk" -d -B mydb > mydb-schema.sql

Detta visade sig vara användbart för att göra några enkla verifikationer av schemaförändringar mellan utvecklingsversionen av databasen och den som var i produktion - dumpa ut båda schemana och kör diff på filerna.

För att kunna ladda ovanstående dump in i en tom MySQL-installation måste du först skapa databasen ("database" i MySQL-terminologi) "mydb", dumpfilen innehåller inte instruktioner för det, men det är trivialt:

mysqladmin -u root -pmyrootpwd create mydb

Här använder vi root-kontot på MySQL-installationen som du borde ställt in då du installerade MySQL, men ifall du missade det gör du det såhär:

mysqladmin -u root password 'myrootpwd'

Nu har vi en skapat en tom databas "mydb" och vi kan ladda dumpen in i den med hjälp av root-kontot:

cat mydb-dump.sql.bz2 | bunzip2 | mysql -C -u root -pmyrootpwd mydb

Sedan måste vi förstås återskapa åtminstone en reguljär användare med tillgång till "mydb", vilket vi kan göra med lite SQL:

mysqladmin -u root -pmyrootpwd -e "grant all privileges on mydb.* to someone@'localhost' identified by 'zonk';"

Den här informationen hamnar i den inbyggda "mysql"-databasen vilken har tabeller för användare osv. Låt oss se ifall det fungerade:

mysqladmin -u someone -pzonk -e "use mydb; show tables;"

 Migrering

De ovanstående stegen återskapar bara databasen och flyttar datat. Ofta vill du kanske också göra lite migrering. För mig slutade det med att jag använde ett SQL-skript kombinerat med de mer komplicerade transformationerna i ett skript-språk, i mitt fall använde jag PHP eftersom jag använt det språket i det här projektet - vilket jag gärna får fört till protokollet inte är mitt första val av programmeringsspråk. :) Att köra ett SQL-skript är förstås enkelt, stoppa in koden i en fil kallad "migrate.sql" och gör:

cat migrate.sql | mysql -u root -pmyrootpwd mydb

Diffen för schemat var användbar för att få migrate.sql korrekt, den har typiskt rader såsom:

USE mydb;
CREATE TABLE PARTS (
        partID int not null auto_increment,
        quantity decimal(8,2),
        partno varchar(36),
        description varchar(255),
        part_state int(11) default 0,
        PRIMARY KEY(partID));
ALTER TABLE OBJECTS ADD downtime DECIMAL(8,2);
ALTER TABLE SUBSCRIPTIONS MODIFY subID mediumint;
UPDATE OBJECTS SET severity=2 WHERE sequence=10;

...vilket skapar nya tabeller, lägger till eller modifierar fält och också gör några transformationer som är enkla att uttrycka i SQL. Sedan använde jag följande triviala PHP-skript för att hantera resten av transformationerna:

<?php
$hostname = "localhost:3306";
$username = "someone";
$password = "zonk";
$database = "mydb";

$mysql = mysql_connect($hostname, $username, $password);
if (!$mysql) {
  echo "Could not open database!";
  exit;
}

$mysql = mysql_select_db($database);

# Recreate releases
$SQL = "SELECT distinct release from OBJECT;";
$result = mysql_query($SQL);
while ($row = mysql_fetch_array($result)) {
    $text = trim($row["release"]);
    if ($text != "") {
        mysql_query("INSERT INTO RELEASES (releaseName) VALUES
('$text');");
    }
}
?>

Detta skript kan vi köra och använda samma ini-fil som används under Apache:

php -c /etc/php4/apache2/php.ini -f migrate.php

Självklart är ovanstående bara ett fingerat kodexempel som antagligen lika enkelt kunde gjorts i SQL direkt.

Nu kan ni glömma att jag nämnde PHP :) och lycka till med dina kommande MySQL-manövrar.

/Göran Krampe