Thomas Wiedmann http://www.twiedmann.de
• Sie befinden sich hier: Startseite > SQL-Backstube > Get last row - oder wie man einen DoS Angriff auf sich selbst macht

Die SQL-Backstube

Bietet Rezepte, Lösungen und ausführliche Beispiele rund um gesundes SQL und zufriedene Datenbanken.

15.05.2011: Get last row - oder wie man einen DoS Angriff auf sich selbst macht

SQL ist lustig und manchmal fällt man richtig rein. Sei es nun bei MySQL oder anderen Datenbanken. Eben habe ich in einem Forum eine Frage gelesen, wie man am Besten den "letzten" Datensatz in einer Tabelle findet. Lösungen? Jede Menge, manche einfach und genial, andere wiederum perfekt geschaffen für einen unfreiwilligen "internen" DoS (Denial of Service) Angriff.


Die Aufgabe

Um was geht es? Der letzte Datensatz (die höchste ID) soll gezielt gefunden und dieser Datensatz gelesen werden.


Getestet mit folgenden Datenbanken:

* mysql-essential-5.1.55-winx64
* mysql-5.5.9-winx64
* mysql-5.6.2-m5-winx64


Tabelle und Testdaten

Listing 1:

  1. CREATE TABLE test_last_row (
  2. id INT(11) NOT NULL AUTO_INCREMENT,
  3. wert1 INT(10) UNSIGNED NOT NULL,
  4. wert2 INT(10) UNSIGNED NOT NULL,
  5. PRIMARY KEY (id)
  6. ) ENGINE = INNODB;
  7. INSERT INTO test_last_row
  8. SELECT NULL, wert1, wert2 FROM geodaten;
  9. => 1000000 rows inserted [5,291s]

Lösungen

Zuerst zwei Lösungen, um schnell ans Ziel zu kommen. Schnell, da MySQL unmittelbar über den Index zugreifen kann.

Listing 2:

  1. /* Standard SQL */
  2. SELECT t1.*
  3. FROM test_last_row t1
  4. JOIN ( SELECT MAX(t2.id) AS max_id FROM test_last_row t2 ) t2
  5. ON t1.id = t2.max_id;
  6. Ausführdauer laut Profile => 0,000441 Sekunden

Listing 3:

  1. /* MySQL Lösung mit LIMIT */
  2. SELECT * FROM test_last_row
  3. ORDER BY id DESC
  4. LIMIT 1;
  5. Ausführdauer laut Profile => 0,000241 Sekunden

Die Lösungen aus Listing 2: und 3: sind praktisch und perfekt. Und nun eine Variante, die im ersten Moment richtig nett aussieht, bei kleinen Tabellen auch funktioniert, aber sich bei grossen Datenmengen zum selbstgemachten DoS Angriff auf die eigene Datenbank auswächst. Warum? Alle Datensätze der Tabelle T1 werden solange mit allen Datensätze der Tabelle T2 verglichen, bis das Tabellen-Ende erreicht ist. Ein klassisches kartesisches Produkt eben.

Listing 4:

  1. /* ACHTUNG! */
  2. SELECT t1.*
  3. FROM test_last_row t1
  4. LEFT JOIN test_last_row t2
  5. ON (t1.id < t2.id)
  6. WHERE t2.id IS NULL;
  7. Ausführdauer ... sehr sehr lang!

Get last row nach einem INSERT

Unmittelbar nach dem Einfügen eines neuen Datensatzes kann man mit MySQL auch folgendes machen. Einfach die letzte automatisch vergebene Auto_Increment_Id ermitteln und damit den "letzten" Datensatz lesen. Beispielsweise geht dies mit der bekannten LAST_INSERT_ID() Funktion.

Listing 5:

  1. mysql> INSERT INTO test_last_row (wert1, wert2 ) VALUES (4711, 4712);
  2. mysql> select * from test_last_row where id = last_insert_id();
  3. +----------+-------+-------+
  4. | id | wert1 | wert2 |
  5. +----------+-------+-------+
  6. | 1000001 | 4711 | 4712 |
  7. +----------+-------+-------+
  8. 1 row in set (0.00 sec)

Oder man nutzt den weniger bekannten MySQL ODBC Workaround. Jedenfalls funktioniert dieser mit MySQL 5.1.55 oder vorher. Bei den neueren MySQL-Versionen ( 5.5.9 und 5.6.2-m5) scheint es - eigentlich zum Glück - nicht mehr zu funktionieren. Zum Glück? Ja, weil diese Methode meines Erachtens vollkommen irreführend ist.

Listing 6:

  1. mysql> select version();
  2. +------------------+
  3. | version() |
  4. +------------------+
  5. | 5.1.55-community |
  6. +------------------+
  7. mysql> INSERT INTO test_last_row (wert1, wert2 ) VALUES (4711, 4712);
  8. Query OK, 1 row affected (0.00 sec)
  9. mysql> select last_insert_id();
  10. +------------------+
  11. | last_insert_id() |
  12. +------------------+
  13. | 1000001 |
  14. +------------------+
  15. mysql> SELECT * FROM test_last_row WHERE id IS NULL;
  16. +----------+-------+-------+
  17. | id | wert1 | wert2 |
  18. +----------+-------+-------+
  19. | 1000001 | 4711 | 4712 |
  20. +----------+-------+-------+

Mit dem sogenannten ODBC-Workaround läßt sich die letzte vergeben Auto_increment_id mit WHERE id IS NULL ermitteln. Obwohl die Spalte ID eine NOT NULL Spalte ist, liefert MySQL ein Ergebnis. Bei den neueren Versionen 5.5.9 und 5.6.2-m5 wird stattdessen ein leeres Resultset ausgegeben.

Listing 7:

  1. /* identisch bei 5.5.9 und 5.6.2-m5 */
  2. mysql> INSERT INTO test_last_row (wert1, wert2 ) VALUES (4711, 4712);
  3. Query OK, 1 row affected (0.00 sec)
  4. mysql> select last_insert_id();
  5. +------------------+
  6. | last_insert_id() |
  7. +------------------+
  8. | 1000001 |
  9. +------------------+
  10. mysql> SELECT * FROM test_last_row WHERE id IS NULL;
  11. Empty set (0.00 sec)

Ergebnis

SQL macht Spaß und wie bereits eingangs erwähnt, gibt es für manches Problem diverse Lösungen, aber auch Fallstricke, die man verstehen und kennen sollte. Sei es nun das Problem des kartesischen Produkts oder das Migrationsproblem von Datenbankversionen, wenn verwendete Techniken nicht mehr wie gewohnt funktionieren.




Sitemap - Inhaltsverzeichnis

© 2002-2017 by Thomas Wiedmann : kontakt (at) twiedmann (punkt) de (Stand : 11.01.2015).