datenbankoptimierung für ruby on rails
TRANSCRIPT
Datenbankoptimierung
Karsten Meiermeier-online.com
Beispiele für die Optimierung an der
Ruby-on-Rails-Schnittstelle
2
Mein Background
● 1986: SQL im Studium● 1996: QuarkXpress -> HTML Converter● 1998-2001: WebObjects, MVC, ORM● 2004: Erster Kontakt mit Ruby (Pleac)● Seit 2005: Handylearn Projects● Seit 2009: Nutzung von Rails
Reduktion der Datenbankzugriffe
● Bessere Antwortzeiten
● Weniger Datenbanklast
● 300% höherer Durchsatz
● Höhere Stabilität
Fette Objekte
id
draft
name
teubuild_year
legal_countrycompany
call_signimo
imo_certificate
speech_of_sponsor
grt
machine
Schattenobjekte
ContainerVessel.select('id, name')order('name')
● Read-Only● Nur angegebene Attribute● Exception falls
unbekannt● ID wirft keine Exception
ActiveRecord::MissingAttributeError
ActiveRecord::ReadOnlyRecord
Rosinen picken
● Nur eine Spalte● Objekt unwichtig● pluck(column) ● ab Rails 3.2
ContainerVessel.pluck(:name)['Australia', 'Brisbane', 'Busan',...]
Outsourcing
● Gewicht aller Container● Berechnung kann die DB durchführen● Rails sieht die einzelnen Container nicht
@vessel.containers.inject{...}
@vessel.containers.sum('weight')
includes()
@container_vessels = @company.container_vessels.
order(:name). includes(:legal_country)
SELECT "container_vessels".* FROM "container_vessels" WHERE "container_vessels"."company_id" = 2 ORDER BY name
SELECT "countries".* FROM "countries" WHERE "countries"."id" IN (8, 7, 4)
includes()
● Jede Abfrage liefert einen Objekttyp
● Rails behält Kontrolle
● Schachtelung möglich
● Feintuning schwierig
.includes(:legal_country => :tax_rates)
.select('country.image????')
Rails joins
● Keine flaggenlose Schiffe
● Keine Staaten
@container_vessels = @company.container_vessels.
order(:name).joins(:legal_country)
Filtern mit joins()
● Filtern anhand von verbundenen Daten● Nur Zielobjekte werden geliefert● Vorsicht vor Vervielfachung
@companies = Company.order(:name). joins(:container_vessels).
where(["container_vessels.build_year > ?", 2009])
SELECT "companies".* FROM "companies" INNER JOIN "container_vessels" ON "container_vessels"."company_id" = "companies"."id" WHERE (container_vessels.build_year > 2009) ORDER BY name
Automatischer Join in Associationen
class Country < ActiveRecord::Base has_many :registering_companies, :through => :registered_vessels, :source => 'company', :class_name => 'Company', :uniq => true ...@companies = @country.registering_companies
SELECT DISTINCT "companies".* FROM "companies" INNER JOIN "container_vessels" ON "companies"."id" = "container_vessels"."company_id" WHERE "container_vessels"."legal_country_id" = 10
Echte Datenbankjoins aus Rails
connection = Company.connection
columns = "container_vessels.id, container_vessels.name,\ container_vessels.imo, container_vessels.teu, \
countries.name as legal_country_name"
sql = 'SELECT ' + columns + ' FROM "container_vessels" \ JOIN "countries" \ ON "countries"."id" = "container_vessels"."legal_country_id" \ WHERE "container_vessels"."company_id" = ' + @company.id.to_s + ' ORDER BY "container_vessels".name'
@vessel_data = connection.select_all(sql, 'ContainerVessel Overview Load')
select_all Rückgabewerte
● select_all: array of hashes● select_rows: array of arrays
<% @vessel_data.each do |data| %> <tr> <td><%= data['name'] %></td> <td><%= data['imo'] %></td> <td><%= data['teu'] %></td> <td><%= data['legal_country_name'] %></td>
...<% end %>
Parameter-Überprüfung
● SQL-Injection● Methoden leider
etwas versteckt● Ab Rails 3.2:
ActiveRecord::Sanitization
● Bei IDs: to_i.to_str
Company.where('name like '%?', input)
record.sanitize_sql_array(..)
replace_bind_variables()quote_bound_value()
connection.quote_string()
Transaktionen
● gewährleisten die Konsistenz (ACID)● weniger Sperren, schnelleres Schreiben● Ab 2 Schreiboperationen -> nutzen!
Massenupdates
UPDATE container_vesselsSET company_id = 7WHERE company_id = 5
● Firma wird verkauft● Alle Schiffen bekommen neuen Besitzer
connection.update_sql(sql, "Updating vessel...")
Verbundene Updates
● Beispiel Denormalisierung● Name des Landes soll auch im
Schiffsdatensatz gespeichert werden
UPDATE container_vessels, countrySET container_vessels.country_name = country.nameWHERE container_vessels.legal_country_id = country.id
Keine Angst for SQL
"Many people treat the relational database like a crazy aunt who's shut up in an attic and whom nobody wants to talk about"
Martin Fowler: OrmHate
... end
meier-online.com
Website von Karsten Meier:
Bilder: Container ship by jogdragoon, openclipart.orgHammer5 by Krystof Jetmar, openclipart.orgOOCL Montreal & Cosco Hope fotografiert von Karsten Meier im Hamburger Hafen 2012