ALTER TABLE mit Rails 3 und MySQL

Da nicht immer von Anfang an die Datenbank- bzw. Tabellenstruktur eindeutig ist und geänderte Anforderungen das Datenbankschema beeinflussen, müssen Anpassungen vorgenommen werden. Dies ist in der Regel einfach in den Migration-Scripten konfigurierbar. Mit dem vorgestellten Commit wird es in Zukunft in einer optimierteren Version als Bulk ablaufen.

Der erwähnte Commit ist im GitHub einsehbar (https://github.com/rails/rails/commit/30176f28a41681c7607eed39d03501327869d40c).

 

Das Problem, das bisher bei Änderungen an Tabellen bestand, war die Art wie MySQL diese ausführt. Wenn man von folgendem Beispiel ausgeht:
Es sollen zwei Spalten hinzugefügt werden und der Datentyp einer weiteren soll geändert werden.

[ruby]
change_table(:users) do |t|
t.string :im_handle
t.belongs_to :company
t.change :birthdate, :datetime
end
[/ruby]

 

Diese Migration führte bisher zu drei separat ausgeführten Befehlen:

 

[bash]
ALTER TABLE `users` ADD `im_handle` varchar(255)
ALTER TABLE `users` ADD `company_id` int(11)
ALTER TABLE `users` CHANGE `updated_at` `updated_at` datetime DEFAULT NULL
[/bash]

 

Diese Aktion ist bei kleinen Datenbanken „relativ“ unproblematisch, aber führt zu Problemen bei größeren.Denn wenn man einen Blick in die MySQL Dokumentation wirft, wird man feststellen, dass MySQL einiges im Hintergrund ausführt, um die Änderung durchzuführen. Die grobe Reihenfolge sieht ungefähr so aus:

 
  1. Die Tabelle wird gesperrt (lock). Kein UPDATE und INSERT mehr zugelassen
  2. Eine temporäre Kopie der Tabelle samt Inhalt wird angelegt
  3. Die gewünschten Änderungen werden auf die Kopie angewendet
  4. Die originale Tabelle wird durch die geänderte Kopie ersetzt
 

Wenn nun das oben gezeigte Beispiel ausgeführt wird, würden diese Schritte drei mal ausgeführt werden. Das ist nicht unbedingt performant und nicht immer gewünscht.

In den kommenden Rails-Versionen kann dieser Vorgang als Bulk durchgeführt werden. Dadurch werden alle Änderungen als einziger Befehl durchgeführt:

[ruby]
change_table(:users, :bulk => true) do |t|
t.string :im_handle
t.belongs_to :company
t.change :birthdate, :datetime
end
[/ruby]

Durch den zusätzlichen Parameter „bulk => true“ werden die einzelnen Statements zu einer zusammengefasst:

[bash]
ALTER TABLE `users` ADD COLUMN `im_handle` varchar(255), ADD COLUMN `company_id` int(11), CHANGE `updated_at` `updated_at` datetime DEFAULT NULL
[/bash]

Dadurch werden Datenbankänderungen in Zukunft nicht nur schneller sondern auch atomarer.

Problem mit MySQL Update auf Version 5.1 (Debian Squeeze)

In der Regel verläuft auf Debian-Systemen ein Versions-Upgrade ohne Probleme. Letzes Wochenende ist Debian 6 (Squeeze) erschienen und bringt in dieser Version neben anderen Paketen auch eine aktuellere Version von MySQL mit. In diesem Fall kann man beim Upgrade auf einen Fehler stossen, der die endgültige Installation und somit später auch den Start der MySQL-Datenbank verhindert.

Eine mögliche Ausgabe des Upgrades und/oder Installation sieht folgender Maßen aus:

[bash]
Reading package lists… Done
Building dependency tree
Reading state information… Done
The following extra packages will be installed:
mysql-client-5.1 mysql-server-5.1
Suggested packages:
libterm-readkey-perl tinyca
The following NEW packages will be installed:
mysql-client mysql-client-5.1 mysql-server mysql-server-5.1
0 upgraded, 4 newly installed, 0 to remove and 0 not upgraded.
Need to get 0 B/16.8 MB of archives.
After this operation, 41.7 MB of additional disk space will be used.
Do you want to continue [Y/n]? Y
Preconfiguring packages …
Selecting previously deselected package mysql-client-5.1.
(Reading database … 48418 files and directories currently installed.)
Unpacking mysql-client-5.1 (from …/mysql-client-5.1_5.1.49-3_amd64.deb) …
Selecting previously deselected package mysql-server-5.1.
Unpacking mysql-server-5.1 (from …/mysql-server-5.1_5.1.49-3_amd64.deb) …
Selecting previously deselected package mysql-client.
Unpacking mysql-client (from …/mysql-client_5.1.49-3_all.deb) …
Selecting previously deselected package mysql-server.
Unpacking mysql-server (from …/mysql-server_5.1.49-3_all.deb) …
Processing triggers for man-db …
Setting up mysql-client-5.1 (5.1.49-3) …
Setting up mysql-server-5.1 (5.1.49-3) …
Stopping MySQL database server: mysqld.
Starting MySQL database server: mysqld . . . . . . . . . . . . . . failed!
invoke-rc.d: initscript mysql, action "start" failed.
dpkg: error processing mysql-server-5.1 (–configure):
subprocess installed post-installation script returned error exit status 1
Setting up mysql-client (5.1.49-3) …
configured to not write apport reports
dpkg: dependency problems prevent configuration of mysql-server:
mysql-server depends on mysql-server-5.1; however:
Package mysql-server-5.1 is not configured yet.
dpkg: error processing mysql-server (–configure):
dependency problems – leaving unconfigured
configured to not write apport reports
Errors were encountered while processing:
mysql-server-5.1
mysql-server
E: Sub-process /usr/bin/dpkg returned an error code (1)
[/bash]

Leider sind die Meldungen nicht präzise genug um den Fehler einfach zu lokalisieren.
Der Grund für die abgebrochene Installation liegt in der /etc/mysql/my.cnf.
Dort befindet sich der Konfigurations-Parameter „skip-bdb“ und war in der vorherigen Version noch erlaubt bzw. gültig.
Leider findet man auch dbzgl. nichts genaueres in den MySQL-Logdateien.
Allerdings gibt es in den syslogs hilfreiche Einträge:

[bash]

Feb  8 00:03:38 maw-db mysqld_safe[2190]: 110208  0:03:38 [ERROR] /usr/sbin/mysqld: unknown option ‚–skip-bdb‘
Feb  8 00:03:38 maw-db mysqld_safe[2190]: 110208  0:03:38 [ERROR] Aborting

[/bash]

Ein Blick in die Konfigurationsdatei zeigt, dass der Parameter obsolete sein wird bzw. nach dem Upgrade obsolete ist.

[bash]

# Using BerkeleyDB is now discouraged as its support will cease in 5.1.12.
skip-bdb
….
[/bash]

Um den Installationsvorgang nun wieder funktionsfähig zu kriegen muss der Parameter einfach auskommentiert werden:

[bash]

# skip-bdb
….
[/bash]

Nach dieser Anpassung sollte die Installation normal durchlaufen.

MySQL DB in UTF8 konvertieren

In der Regel sollte man das Encoding für seine Datenbanken auf UTF8 stellen um später keine bösen Überraschungen zu erleben. Falls beim Entwicklungstart diese Einstellung vergessen wurde oder wenn man sich später zu UTF8 „bekennt“ kann man folgendes Ruby-Script benutzen um die Datenbank zu konvertieren.

Ein Wort der Warnung: Wie immer sollte man IMMER ein Backup anlegen bevor man seine Daten in großem Stil verändert –> Ihr seid gewarnt!

Hier nun das Script:
Es ruft im Grunde die bekannten Shellscript-Befehle (iconv, mysqldump, etc.) auf um die Konvertierung durchzuführen.

[ruby wraplines=“true“]

class ConvertDbToUtf8

def self.up

db_name = ‚DATENBANK-NAME‘
db_user = ‚DATENBAK-BENUTZER‘
db_pass = ‚DATENBANK-PASSWORT‘
db_host = ‚DATENBANK-HOST‘

latin1_mysql_dump = ‚latin1_mysql_dump.sql‘
utf8_mysql_dump = ‚utf8_mwsql_dump.sql‘

print "DB-Dump holen…. "
system "mysqldump –user=#{db_user} –password=’#{db_pass}‘ –host=#{db_host} –add-drop-table –default-character-set=latin1 –insert-ignore –skip-set-charset #{db_name} > #{latin1_mysql_dump}"
puts "…fertig"

print "Konvertierung nach UTF8 starten… "
system "iconv -f ISO-8859-1 -t UTF-8 #{latin1_mysql_dump} | sed ’s/latin1/utf8/‘ > #{utf8_mysql_dump}"
puts "…fertig"

print "Datenbank neu anlegen"
system "mysql –user=#{db_user} –password=’#{db_pass}‘ –host=#{db_host} –execute="DROP DATABASE #{db_name};""
system "mysql –user=#{db_user} –password=’#{db_pass}‘ –host=#{db_host} –execute="CREATE DATABASE #{db_name} CHARACTER SET utf8 COLLATE utf8_unicode_ci;""
puts "…fertig"

print "Konvertierten UTF8 Dump einspielen…"
system "mysql –user=#{db_user} –password=’#{db_pass}‘ –host=#{db_host} –default-character-set=utf8 #{db_name} < #{utf8_mysql_dump}"
puts "…fertig"

puts " Konvertierung abgeschlossen "
end

def self.down
raise "Konvertierung fehlgeschlagen"
end

end

[/ruby]

MySQL Queries in irb ausgeben lassen

Manchmal möchte man auch in einer irb-Session die MySQL-Queries ausgeben lassen, die von Rails erzeugt wurden. Dies ist mit einer kleinen Anpassung in der environments.rb möglich.

Dazu muss die config/environment.rb um folgenden Eintrag ergänzt werden
[ruby]
def log_to(stream)
ActiveRecord::Base.logger = Logger.new(stream)
ActiveRecord::Base.clear_active_connections!
end
[/ruby]

Danach kann man in der Konsole diese Methode benutzen:

[ruby]
>> log_to STDOUT
=> …
>> Post.find(:first)
Post Load (0.000138) SELECT * FROM posts LIMIT 1
=> #<Post:0x1234 …>
>>
[/ruby]

So kann man nun die Queries prüfen oder sich einfach einen Überblick verschaffen was im Hintergrund passiert 🙂

Ruby, Rails, LightHTTPD und MySQL auf Tiger

Eine Anleitung um Ruby, Rails, LightHTTPD, auf MySQL auf einem Mac OS X 10.4 (Tiger) zu installieren.

Das Tutorial beschreibt schrittweise wie die einzelnen Komponenten gebaut, installiert und konfiguriert werden.

Am Ende des Tutorials steht einem eine komplette Railsumgebung zur Verfügung auf der man seine Applikationen entwickeln kann.

URL: http://hivelogic.com/articles/2005/12/01/ruby_rails_lighttpd_mysql_tiger