{"id":7222,"date":"2010-08-12T23:27:39","date_gmt":"2010-08-12T21:27:39","guid":{"rendered":"http:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/?p=7222"},"modified":"2010-08-12T23:27:39","modified_gmt":"2010-08-12T21:27:39","slug":"korrekte-nutzung-von-mysql-wirkt-wunder-insert-delayed-update-low_priority-indizes","status":"publish","type":"post","link":"https:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/2010\/08\/korrekte-nutzung-von-mysql-wirkt-wunder-insert-delayed-update-low_priority-indizes\/","title":{"rendered":"Korrekte Nutzung von MySQL wirkt Wunder &#8211; INSERT DELAYED\/UPDATE LOW_PRIORITY\/Indizes"},"content":{"rendered":"<p>Es gibt Datenbanken, die stehen unter grosser Last, weil die Queries sehr komplex sind. Andere Datenbanken sind immens gross. Eine meiner Datenbanken hat sehr viele Zugriffe, sowohl lesend als auch schreibend. Heute wurde der Server an die Lastgrenze getrieben, was mich dazu zwang, Optimierungen vorzunehmen.<\/p>\n<p><strong>1. <code>INSERT DELAYED<\/code> und <code>UPDATE LOW_PRIORITY<\/code><\/strong><\/p>\n<p>Bisher habe ich mich immer davon fern gehalten, diese Funktionen zu nutzen, einfach weil ich keine Veranlassung dazu sah. Warum schreibende Zugriffe verz\u00f6gern, wenn sie doch auch unmittelbar vollzogen werden k\u00f6nnen? Die Tatsache, dass mehrere hunderte gleichzeitiger Zugriffe sich gegenseitig behinderten, brachte mich dazu, meine Denkweise zu \u00fcberdenken.<\/p>\n<p><!--more-->Ich habe zwei schreibende SQL-Statements kurzerhand von &#8220;<code>INSERT INTO <em>TABLE<\/em>...<\/code>&#8221; in &#8220;<code>INSERT DELAYED INTO <em>TABLE<\/em>...<\/code>&#8221; bzw. &#8220;<code>UPDATE <em>TABLE<\/em>...<\/code>&#8221; in &#8220;<code>UPDATE LOW_PRIORITY <em>TABLE<\/em>...<\/code>&#8221; umgeschrieben.<\/p>\n<p>Nat\u00fcrlich gibt es Nachteile. Erstens sind die geschriebenen Daten nicht unmittelbar abrufbar, zweitens gehen diese unweigerlich verloren, sofern der MySQL-Server unsauber beendet wird, bevor die Queries auf Platte geschrieben werden konnten.<\/p>\n<p>Die Vorteile \u00fcberwiegen jedoch, denn ich konnte damit die Serverlast unglaublich reduzieren. Es sollte allerdings wohl \u00fcberlegt werden, welche Statements man verz\u00f6gert schreiben l\u00e4sst.<\/p>\n<p><strong>2. Die sinnvolle Verwendung von Indizes<\/strong><\/p>\n<p>Eine weitere Optimierung, zu der ich mich gezwungen sah, war der Einsatz von Indizes in MySQL-Tabellen. Dauert die Verarbeitung eines SQL-Queries sehr lang, weil unheimlich viele Daten analysiert werden m\u00fcssen, so bietet sich der Einsatz eines Index&#8217; durchaus an. Beispiel:<\/p>\n<pre class=\"brush: php\">SELECT COUNT(wa_sessions.ID) AS Sessions\r\nFROM wa_sessions, wa_sites\r\nWHERE wa_sites.site_name = &#039;foo&#039;\r\nAND wa_sites.ID = wa_sessions.site_id\r\nAND wa_sessions.begin\r\nBETWEEN NOW() - INTERVAL 5 MINUTE\r\nAND NOW() - INTERVAL 0 MINUTE;\r\n+----------+\r\n| Sessions |\r\n+----------+\r\n| \u00a0 \u00a0 \u00a0 20 |\r\n+----------+\r\n\r\n1 row in set (5.00 sec)<\/pre>\n<p>Im obigen Beispiel dauert die Abfrage exakt 5 Sekunden, was f\u00fcr eine Webanwendung unbrauchbar lang ist. Mit MySQL-Boardmitteln kann man sich anschauen, warum die Abfrage so lange dauert:<\/p>\n<pre class=\"brush: php\">EXPLAIN SELECT COUNT(wa_sessions.ID) AS Sessions\r\nFROM wa_sessions, wa_sites\r\nWHERE wa_sites.site_name = &#039;foo&#039;\r\nAND wa_sites.ID = wa_sessions.site_id\r\nAND wa_sessions.begin\r\nBETWEEN NOW() - INTERVAL 5 MINUTE\r\nAND NOW() - INTERVAL 0 MINUTE\\G\r\n*************************** 1. row ***************************\r\nid: 1\r\nselect_type: SIMPLE\r\ntable: wa_sites\r\ntype: ref\r\npossible_keys: PRIMARY,site_sites\r\nkey: site_sites\r\nkey_len: 35\r\nref: const\r\nrows: 1\r\nExtra: Using where\r\n*************************** 2. row ***************************\r\nid: 1\r\nselect_type: SIMPLE\r\ntable: wa_sessions\r\ntype: ALL\r\npossible_keys: NULL\r\nkey: NULL\r\nkey_len: NULL\r\nref: NULL\r\nrows: 831752\r\nExtra: Using where\r\n2 rows in set (0.00 sec)<\/pre>\n<p>Man erkennt, dass zwei Tabellen durchsucht werden. F\u00fcr die erste Tabelle wird ein Key verwendet, f\u00fcr die zweite Tabelle jedoch nicht. Aus diesem Grund liefert die Abfrage der ersten Tabelle auch nur genau ein Ergebnis zur\u00fcck, w\u00e4hrend in der zweiten Tabelle noch\u00a0831752 Ergebnisse auf den <code>WHERE<\/code>-Teil hin \u00fcberpr\u00fcft werden m\u00fcssen. Das dauert nat\u00fcrlich eine Weile.<\/p>\n<p>Besser geht es mit dem Einsatz eines Indizes im entsprechenden Tabellen-Feld, welches vom <code>WHERE<\/code>-Teil abgefragt wird:<\/p>\n<pre class=\"brush: php\">CREATE INDEX session_begin ON wa_sessions (begin(18));\r\nQuery OK, 831761 rows affected (34.52 sec)<\/pre>\n<p>Das gleiche <code>EXPLAIN SELECT<\/code> Statement von gerade eben zeigt, dass nun auch in der zweiten Tabelle ein Key verwendet wird:<\/p>\n<pre class=\"brush: php\">EXPLAIN SELECT COUNT(wa_sessions.ID) AS Sessions\r\nFROM wa_sessions, wa_sites\r\nWHERE wa_sites.site_name = &#039;foo&#039;\r\nAND wa_sites.ID = wa_sessions.site_id\r\nAND wa_sessions.begin\r\nBETWEEN NOW() - INTERVAL 5 MINUTE\r\nAND NOW() - INTERVAL 0 MINUTE \\G\r\n\r\n*************************** 1. row ***************************\r\nid: 1\r\nselect_type: SIMPLE\r\ntable: wa_sites\r\ntype: ref\r\npossible_keys: PRIMARY,site_sites\r\nkey: site_sites\r\nkey_len: 35\r\nref: const\r\nrows: 1\r\nExtra: Using where\r\n*************************** 2. row ***************************\r\nid: 1\r\nselect_type: SIMPLE\r\ntable: wa_sessions\r\ntype: range\r\npossible_keys: session_begin\r\nkey: session_begin\r\nkey_len: 21\r\nref: NULL\r\nrows: 55\r\nExtra: Using where\r\n2 rows in set (0.00 sec)<\/pre>\n<p>Sofort ist erkennbar, dass in der zweiten Tabelle nur noch 55 Ergebnisse mittels der <code>WHERE<\/code>-Abfrage analysiert werden m\u00fcssen. Die meisten der weit \u00fcber 800.000 Ergebnisse in der zweiten Tabelle wurden durch das reine Vorhandensein des Indizes bereits ausgeschlossen. Und das SQL-Statement ist damit volle 5 Sekunden schneller:<\/p>\n<pre class=\"brush: php\">SELECT COUNT(wa_sessions.ID) AS Sessions\r\nFROM wa_sessions, wa_sites\r\nWHERE wa_sites.site_name = &#039;foo&#039;\r\nAND wa_sites.ID = wa_sessions.site_id\r\nAND wa_sessions.begin\r\nBETWEEN NOW() - INTERVAL 5 MINUTE\r\nAND NOW() - INTERVAL 0 MINUTE;\r\n\r\n+----------+\r\n| Sessions |\r\n+----------+\r\n| \u00a0 \u00a0 \u00a0 12 |\r\n+----------+\r\n1 row in set (0.00 sec)<\/pre>\n<p>Auch bei Indizes ist Vorsicht geboten. Zu grosse oder falsche Indizes k\u00f6nnen die Datenbank unter Umst\u00e4nden nicht nur unn\u00f6tig gross, sondern auch langsamer machen.<\/p>\n<p>Erl\u00e4uterungen finden sich in der MySQL-Dokumentation auf <a href=\"http:\/\/dev.mysql.com\/doc\/\" target=\"_blank\">http:\/\/dev.mysql.com\/doc\/<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Es gibt Datenbanken, die stehen unter grosser Last, weil die Queries sehr komplex sind. Andere Datenbanken sind immens gross. Eine meiner Datenbanken hat sehr viele Zugriffe, sowohl lesend als auch schreibend. Heute wurde der Server an die Lastgrenze getrieben, was mich dazu zwang, Optimierungen vorzunehmen. 1. INSERT DELAYED und UPDATE LOW_PRIORITY Bisher habe ich mich [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"ngg_post_thumbnail":0,"footnotes":""},"categories":[11],"tags":[1282,1272,61,612,1302,1292,101],"class_list":["post-7222","post","type-post","status-publish","format-standard","hentry","category-tech","tag-database","tag-datenbank","tag-linux","tag-mysql","tag-performance","tag-tuning","tag-unix"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/wp-json\/wp\/v2\/posts\/7222","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/wp-json\/wp\/v2\/comments?post=7222"}],"version-history":[{"count":3,"href":"https:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/wp-json\/wp\/v2\/posts\/7222\/revisions"}],"predecessor-version":[{"id":7252,"href":"https:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/wp-json\/wp\/v2\/posts\/7222\/revisions\/7252"}],"wp:attachment":[{"href":"https:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/wp-json\/wp\/v2\/media?parent=7222"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/wp-json\/wp\/v2\/categories?post=7222"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.thomasgericke.de\/v4\/interactive\/blog\/wp-json\/wp\/v2\/tags?post=7222"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}