<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>0day.pl</title>
	<atom:link href="http://0day.pl/index.php/feed" rel="self" type="application/rss+xml" />
	<link>http://0day.pl</link>
	<description>o życiu, programowaniu i sporcie</description>
	<lastBuildDate>Sat, 11 Dec 2010 14:31:00 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>MySQL &#8211; kompresja pól tekstowych</title>
		<link>http://0day.pl/index.php/archives/31</link>
		<comments>http://0day.pl/index.php/archives/31#comments</comments>
		<pubDate>Sun, 09 May 2010 15:52:37 +0000</pubDate>
		<dc:creator>bochen</dc:creator>
				<category><![CDATA[mysql]]></category>
		<category><![CDATA[Ogólne]]></category>
		<category><![CDATA[optymalizacje]]></category>
		<category><![CDATA[Optimizations]]></category>

		<guid isPermaLink="false">http://0day.pl/?p=31</guid>
		<description><![CDATA[Dosyć często w bazach danych używa się dosyć krótkich pól tekstowych &#8211; varchar, lub char. Nie wszyscy jednak wiedzą iż pola te mogą podlegać kompresji, jest tylko jeden warunek. Jeżeli wielkość kolumny nie przekroczy 8 znaków, dane nie będą kompresowane. Dla deweloperów może to jednak mieć duże znaczenie. W procesie analizy danych i co za [...]]]></description>
			<content:encoded><![CDATA[<p>Dosyć często w bazach danych używa się dosyć krótkich pól tekstowych &#8211; varchar, lub char.<br />
Nie wszyscy jednak wiedzą iż pola te mogą podlegać kompresji, jest tylko jeden warunek.<br />
Jeżeli wielkość kolumny nie przekroczy 8 znaków, dane nie będą kompresowane.<br />
Dla deweloperów może to jednak mieć duże znaczenie. W procesie analizy danych i co za tym idzie tworzenia struktur dla nich staramy się często zminimalizować ich ilość aby nie rezerwować/zajmować niepotrzebnie przestrzeni pamięciowej.</p>
<p>Załóżmy iż chcemy w bazie przechować okrojoną datę w formie RRRR-MM. Na potrzeby tego rodzaju danych programista stworzy kolumne o danych takich jak:</p>
<blockquote><p>CREATE TABLE test (<br />
testId INT UNSIGNED NOT NULL AUTO_INCREMENT,<br />
shortDate CHAR(7) NOT NULL,<br />
relatedId INT UNSIGNED NOT NULL,<br />
testOptions INT UNSIGNED NOT NULL,<br />
PRIMARY KEY(testId),<br />
KEY (shortDate)<br />
) Engine=MyISAM;</p></blockquote>
<p>Tym silnika niekoniecznie tutaj ma znaczenie, warto sprawdzic i dla MyISAM oraz dla InnoDB.</p>
<p>Oczywiscie typ char został wybrany ponieważ zawsze dane mają taką samą długość co ułatwia mysqlowi zarządzanie rekordem a przy okazji nie marnuje miejsca.</p>
<p>Po wrzuceniu 1M rekordów za pomocą:</p>
<blockquote><p>DELIMITER //<br />
CREATE PROCEDURE fillTable()<br />
BEGIN<br />
SET @x:=0;<br />
REPEAT INSERT INTO test<br />
SET shortDate=DATE_FORMAT(NOW() &#8211; INTERVAL FLOOR(0 + RAND() * (10000)) DAY, &#8216;%Y-%m&#8217;),<br />
relatedId=FLOOR(1 + RAND() * 10000), testOptions=FLOOR(1 + RAND() * x&#8217;7fffffff&#8217;);<br />
SET @x = @x + 1;<br />
UNTIL @x &gt; 1000000 END REPEAT;<br />
END //<br />
DELIMITER ;</p></blockquote>
<p>Wywolanie dodawania rekordow:</p>
<blockquote><p>call fillTable();</p></blockquote>
<p>otrzymujemy taką oto wielkość indeksu:</p>
<blockquote style="margin-right: 40px; font-family: 'Courier New'; font-size: 11px;"><p>select TABLE_NAME,ENGINE,ROW_FORMAT,DATA_LENGTH,INDEX_LENGTH FROM information_schema.tables WHERE TABLE_SCHEMA=&#8217;test&#8217;;</p>
<p style="font-family: 'Courier New'; margin-right: 40px;">+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8211;+<br />
| TABLE_NAME | ENGINE | ROW_FORMAT | DATA_LENGTH | INDEX_LENGTH |<br />
+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8211;+<br />
| test       | MyISAM | Fixed      |    20000020 |     26477568 |<br />
+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8211;+</p>
</blockquote>
<p>Jedna drobna zmiana, zdawało by się &#8211; na gorsze <img src='http://0day.pl/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<blockquote style="font-family: 'Courier New'; margin-right: 40px;"><p>ALTER TABLE test CHANGE shortDate shortDate CHAR(8) NOT NULL;</p></blockquote>
<blockquote style="font-family: 'Courier New'; margin-right: 40px;"><p>Spójrzmy teraz co stało się z wielkościami indeksów:<br />
select TABLE_NAME,ENGINE,ROW_FORMAT,DATA_LENGTH,INDEX_LENGTH FROM information_schema.tables WHERE TABLE_SCHEMA=&#8217;test&#8217;;</p>
<p>+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8211;+<br />
| TABLE_NAME | ENGINE | ROW_FORMAT | DATA_LENGTH | INDEX_LENGTH |<br />
+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8211;+<br />
| test       | MyISAM | Fixed      |    21000021 |     13034496 |<br />
+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8211;+&#8212;&#8212;&#8212;&#8212;+&#8212;&#8212;&#8212;&#8212;-+&#8212;&#8212;&#8212;&#8212;&#8211;+</p>
<p>Co zaszło? Otóż pomimo straty jednego bajta na rekord uzyskaliśmy całkiem sporo na indeksach (50%), co za tym idzie? Mniej operacji IO i szybciej uzyskujemy wyniki… Oczywiscie zmiana jest pozytywna wtedy gdy mocno są obciążone dyski CPU zaś trochę się nudzi.</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://0day.pl/index.php/archives/31/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Maksymalizowanie okna w MacOSX</title>
		<link>http://0day.pl/index.php/archives/29</link>
		<comments>http://0day.pl/index.php/archives/29#comments</comments>
		<pubDate>Tue, 04 May 2010 08:07:14 +0000</pubDate>
		<dc:creator>bochen</dc:creator>
				<category><![CDATA[MacOSX]]></category>
		<category><![CDATA[Ogólne]]></category>

		<guid isPermaLink="false">http://0day.pl/?p=29</guid>
		<description><![CDATA[Znalazłem ostatnio ciekawą aplikacyję rozwiązującą denerwujący problem z makami. Otóż chodzi o mocno upierdliwe i dosyć nieprzewidywalne zachowanie przycisku maksymalizuj. Rozwiązaniem jest zainstalowanie aplikacji o nazwie Right Zoom i uruchomienie jej, reszta będzie już jasna. Polecam obejrzenie filmiku związanego z aplikacja:]]></description>
			<content:encoded><![CDATA[<p>Znalazłem ostatnio ciekawą aplikacyję rozwiązującą denerwujący problem z makami.<br />
Otóż chodzi o mocno upierdliwe i dosyć nieprzewidywalne zachowanie przycisku maksymalizuj. Rozwiązaniem jest zainstalowanie aplikacji o nazwie <a title="Right Zoom" href="http://www.blazingtools.com/mac/RightZoom.zip" target="_blank">Right Zoom</a> i uruchomienie jej, reszta będzie już jasna.</p>
<p>Polecam obejrzenie filmiku związanego z aplikacja:</p>
<p><object width="640" height="385"><param name="movie" value="http://www.youtube.com/v/v-NYGK2MJzc&#038;color1=0xb1b1b1&#038;color2=0xd0d0d0&#038;hl=en_US&#038;feature=player_embedded&#038;fs=1"></param><param name="allowFullScreen" value="true"></param><param name="allowScriptAccess" value="always"></param><embed src="http://www.youtube.com/v/v-NYGK2MJzc&#038;color1=0xb1b1b1&#038;color2=0xd0d0d0&#038;hl=en_US&#038;feature=player_embedded&#038;fs=1" type="application/x-shockwave-flash" allowfullscreen="true" allowScriptAccess="always" width="640" height="385"></embed></object></p>
]]></content:encoded>
			<wfw:commentRss>http://0day.pl/index.php/archives/29/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Z Kroniki Mistrza Wincentego&#8230;</title>
		<link>http://0day.pl/index.php/archives/18</link>
		<comments>http://0day.pl/index.php/archives/18#comments</comments>
		<pubDate>Tue, 05 May 2009 13:28:56 +0000</pubDate>
		<dc:creator>Lechitman</dc:creator>
				<category><![CDATA[Ogólne]]></category>
		<category><![CDATA[Polska]]></category>

		<guid isPermaLink="false">http://0day.pl/?p=18</guid>
		<description><![CDATA[Rzecz dziwna, lecz zupełnie wiarygodna. Istnieje bowiem księga listów Aleksandra zawierająca prawie dwieście listów. W jednym z nich pisze w ten sposób do Arystotelesa: &#8220;Żeby ciebie, ciągle zaniepokojonego o nasze położenie, wątpliwość i wahanie się nie utrzymywały w niepewności, wiedz, że doskonale powodzi się nam u Lechitów. Jest zaś sławne miasto Lechitów bardzo blisko północnych [...]]]></description>
			<content:encoded><![CDATA[<p>Rzecz dziwna, lecz zupełnie wiarygodna. Istnieje bowiem księga listów Aleksandra zawierająca prawie dwieście listów. W jednym z nich pisze w ten sposób do Arystotelesa: &#8220;Żeby ciebie, ciągle zaniepokojonego o nasze położenie, wątpliwość i wahanie się nie utrzymywały w niepewności, wiedz, że doskonale powodzi się nam u Lechitów. Jest zaś sławne miasto Lechitów bardzo blisko północnych stron Panonii, które nazywają Caraucas, raczej ludne niż bogate, dobrze zabezpieczone raczej dzięki sztuce niż położeniu; nad tym miastem i nad sąsiednimi triumfowaliśmy zgodnie z życzeniem&#8221;. W tym zaś liście, który mu odpisał Arystoteles, czytamy te słowa:<br />
&#8220;Wieść głosi, że wraz ze swoimi odniosłeś tryumf nad miastem Lechitów Caraucas, lecz oby chwała tego tryumfu nigdy nie była pomnożyła twoich tytułów do sławy! Odkąd bowiem wlano haniebną daninę w posłów twoich wnętrzności, odkąd doświadczyłeś lechickich argyraspidów, blask twego słońca u wielu zagasł, a nawet zdawało się, że korona twego państwa się zachwiała&#8221;.<br />
Gdyby nie dobrodziejstwo twego opowiadania, wyznaję doprawdy, że do dziś nie wiedziałbym, o kim to powiedziano. Lecz jakże zdumiewająca wprost zuchwałość [owych] mężów! Bo chociaż podrażniony, nie do tego jednak stopnia obrażony został Aleksander przez zniewagę wyrządzoną mu przez Koryntian. Gdy tymczasem inne miasta pozwoliły mu wejść, pierwszy Korynt zamknął przed nim bramy. Do jego mieszkańców napisał Aleksander: &#8220;Jeśli jesteście mądrzy, ostaniecie się, w przeciwnym razie &#8211; nie&#8221;. Oni zaś nie bacząc na nietykalność ukrzyżowali jego posłów. Lecz niemniej podziwu godny jest wielce dowcipny pomysł owego męża, przez który tak baczny na podstępy mistrz dał się zwieść. W dosyć podobny bowiem sposób tenże Aleksander zwiódł wojska Dariusza: widząc, że są one o wiele większe niż jego, kazał przywiązać wołom do ogonów i rogów gałęzie, aby zdawało się, że nawet lasy idą za nim.<br />
Dlatego ów wynalazca tak zbawiennego podstępu został ustanowiony naczelnikiem w ojczyźnie, którą uratował, a wkrótce potem, wsparty zasługami cnót, odznaczony został wysoką godnością królewską. Dano mu imię Lestek, to jest przebiegły, ponieważ więcej nieprzyjaciół zniszczył przebiegłością niż siłą.</p>
]]></content:encoded>
			<wfw:commentRss>http://0day.pl/index.php/archives/18/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL, 10 na 90</title>
		<link>http://0day.pl/index.php/archives/13</link>
		<comments>http://0day.pl/index.php/archives/13#comments</comments>
		<pubDate>Thu, 28 Feb 2008 22:42:59 +0000</pubDate>
		<dc:creator>bochen</dc:creator>
				<category><![CDATA[mysql]]></category>
		<category><![CDATA[programowanie]]></category>

		<guid isPermaLink="false">http://0day.pl/index.php/archives/13</guid>
		<description><![CDATA[Trochę dziwny tytuł i z pewnością nie każdy na pierwszy rzut oka rozumie o co chodzi, wyjaśnię zatem po krótce. Nie jest to punktacja dla tejże bazy danych. Jest to tak zwana zasada o lokalności danych. Twierdzi ona iż 10% kodu wykonuje się 90% czasu. Równie dobrze mógłbym tutaj wrzucić zasadę &#8216;pareto&#8217; jednak nazwa czy [...]]]></description>
			<content:encoded><![CDATA[<p>Trochę dziwny tytuł i z pewnością nie każdy na pierwszy rzut oka rozumie o co chodzi, wyjaśnię zatem po krótce.</p>
<p>Nie jest to punktacja dla tejże bazy danych. Jest to tak zwana zasada o lokalności danych.<br />
Twierdzi ona iż 10% kodu wykonuje się 90% czasu. Równie dobrze mógłbym tutaj wrzucić zasadę &#8216;pareto&#8217; jednak nazwa czy drobna różnica w liczbach zupełnie nie ma tutaj znaczenia. Nie bedę jednak opowiadał o trzewiach tego DBMSa, powiem zaś o czymś co dotyczy każdego użytkownika bazy oraz jego danych.</p>
<p>Wszelkiego rodzaju systemy które teraz wytwarzamy, skoncentrowane są zazwyczaj wokół jednego użytkownika, dla którego gromadzone są dane, lub też wokół czasu w jakim są otrzymywane. Oznacza to, że zalogowanemu użytkownikowi pokazujemy dane które dotyczą tylko jego osoby (wiadomości, koszyki, produkty), lub też dane które dotyczą danego czasu (najświeższe wiadomości, najnowsze promocje, etc). Nieczęsto się zdarza iż przy generowaniu głównej strony serwisu pokazujemy wszystko co tylko znajduje się w bazie. Zazwyczaj jest jakieś &#8216;ziarno&#8217; wokół którego należy się skupić.<br />
Niestety pomimo iż skupiamy sie na tym &#8216;ziarnie&#8217; podczas generowania zawartości strony, nie skupiamy się na tym podczas opiekowania się wytworzonym systemem.<br />
<span id="more-13"></span></p>
<p>Dane które gromadzimy w ciagu cyklu ich życia znajdują sobie miejsce w różnych miejscach dysku. Progamiści budują odpowiednie indeksy tak aby był szybki dostęp do nich, jednak to nie wszystko co można zrobić. Jeżeli dane gromadzone były tygodniami to odczytanie naszych 10, czy 20% zasobów może wygenerować dziesątki odczytów z IO (odczytów z dysków, czy macierzy dyskowych). Nieistotne czy dane są ladnie poukładane czy nie. Nieczęsto się zdarza iż do pobierania danych wykorzystany zostaje tylko i wyłącznie indeks, koniec końców i tak chcemy się dobić do całego wiersza danych (lub sporej jego części).<br />
Z pewnością wielu czytelnikom zaświta już w głowie &#8216;defragmentacja&#8217; a zaraz po tym &#8216;optimize&#8217;. Niestety to nie wszystko.</p>
<p>Rozwiązaniem tego ciekawego problemu jest cykliczne sortowanie danych wg specyficznego klucza, który to wczesniej nazwany został przezemnie ziarnem.<br />
Sortowanie to może przebiegać następująco:</p>
<pre class="programlisting">
<ul>
<li>w przypadku MyISAM używając jednego z mechanizmów
<ul>
<li> ALTER TABLE nazwa_tabeli ORDER BY [ziarno]</li>
<li><a href="http://dev.mysql.com/doc/refman/5.0/en/myisamchk.html" title="myisamchk --sort-records" target="_blank">myisamchk --sort-records ....</a></li>
</ul>
</li>
<li>w przypadku InnoDB używając 'ziarna' jako PK (Primary Key)</li>
</ul>
</pre>
<p>Zazwyczaj systemy DBMS poruszają się na granicy możliwości mechanizmów IO (input/output) co się w języku angielskim ładnie nazywa &#8216;io bound&#8217;. Dbanie o poprawne uporządkowanie danych znakomicie oddala nas w wielu przypadkach od krytycznej granicy.</p>
<p>Z pewnością wielu dostawców hostingowych stosuje praktykę sortowania danych klientów po PK, jeżeli nie jesteście pewni czy tak się dzieje, przeczytajcie dokumentacje lub zapytajcie. Jeżeli nie ma takiej możliwości sami dodajcie zapytanie do cyklicznych wywołań (np crona). W zależności od ilości danych napływających do systemu należy odpowiednio dobrać okres co jaki bazy się optymalizują. Tym samym odradzam wykonywać takie zabawy codziennie, zazwyczaj jedna optymalizacja w tygodniu lub w miesiącu będzie wystarczająca.</p>
<p>To była jedna część opowieści, z której jasno wypływa stwierdzenie: trzymaj najczęściej używane dane tak blisko siebie, jak tylko się da. Jednak temat ten nie jest wyczerpany. Jeżeli przyjrzycie się dokumentacji MySQL, w wielu miejscach da się znaleźć informacje z których jasno wynika, iż należy być w dziedzinie przechowywania danych minimalistą. Przechowywać tylko konieczne dane i to w postaci najbardziej dopasowanej do samych danych. Oznacza to iż, jeżeli pole &#8216;type&#8217; zawiera tylko wartości od 1 &#8211; 10, to nie jest konieczne stosowanie dla niego typu INTEGER. Warto zmienić typ pola na mniejsze.<br />
Praktyka jednak często jest inna, dodajemy informacje i kolumny na zapas, gromadzimy wiecej danych niż jest to potrzebne w danej chwili. Nie mogę negować takiego zachowania, nie zawsze wiemy z czego bedziemy generować statystyki, czy też jakie wielkości pojawią sie w stworzonych przez nas kolumnach. Nie jest to jednak patowa sytuacja.</p>
<p>W przypadku gromadzenia zawyżonej ilości danych warto w przypadku ich pobierania zawęzić ilość pobieranych kolumn do niezbędnego minimum, oraz przesunąć wszelkie kolumny, z których aktywnie korzystacie na poczatek wiersza.<br />
Przykładowo, jeżeli mamy taki oto układ tabeli:</p>
<pre class="programlisting">
id: integer, count: integer; referer: varchar; cookie: varchar; link: varchar</pre>
<p>podczas tworzenia raportów wykorzystujemy TYLKO pola: id, count, link; to warto te kolumny trzymać obok siebie.</p>
<p>Jeżeli przechowujemy znaczną ilość danych, szczególnie w polach tekstowych i cierpimy z powodu niedostatków operacji IO, natomiast mamy spory zapas CPU, doradzałbym użycie <a href="http://dev.mysql.com/doc/refman/5.0/en/encryption-functions.html#function_compress" title="kompresji" target="_blank">kompresji</a>. Z pewnością mocniej obciąży CPU, jednakże okazać się może iż wszystko zacznie pracować o wiele wydajniej.</p>
<p>W przypadku gdy na początku projektu nie można było określić odpowiednich typów danych, warto jest co jakiś czas analizować to co znajduje sie w tabelach i odpowiednio modyfikować jej strukture<br />
W tym celu należy wykorzystac polecenie:</p>
<pre class="programlisting"><a href="http://dev.mysql.com/doc/refman/5.0/en/procedure-analyse.html" title="SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([max_elements,[max_memory]])  " target="_blank">SELECT ... FROM ... WHERE ... PROCEDURE ANALYSE([<em class="replaceable"><code>max_elements</code></em>,[<em class="replaceable"><code>max_memory</code></em>]])</a></pre>
<p>Wygenerowany raport może okazać się bardzo przydatny.</p>
<p>Na koniec chciałbym przeprosić iż cały artykuł pisany jest tak na sucho bez przykładów i wykresów. Zostanie on zmodyfikowany i uzupełniony w skończonym czasie <img src='http://0day.pl/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://0day.pl/index.php/archives/13/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Jak działa MySQL. MyISAM &#8211; Optymalizacje II</title>
		<link>http://0day.pl/index.php/archives/12</link>
		<comments>http://0day.pl/index.php/archives/12#comments</comments>
		<pubDate>Fri, 06 Apr 2007 07:37:22 +0000</pubDate>
		<dc:creator>bochen</dc:creator>
				<category><![CDATA[mysql]]></category>
		<category><![CDATA[Ogólne]]></category>
		<category><![CDATA[programowanie]]></category>

		<guid isPermaLink="false">http://0day.pl/index.php/archives/12</guid>
		<description><![CDATA[Artykuł ten jest kontynuacją artykułu &#8220;Jak działa MySQL. MyISAM &#8211; Optymalizacje I&#8221;. Osobom które nie zapoznały się z wcześniejszym materiałem sugeruję przejrzenie go, w innym wypadku część informacji może się wydawać niespójna. Poprzednio artykuł zakończyłem informacją jak budować zapytania aby stworzony indeks został wykorzystany. Przemilczałem jednak pewne pchające się na usta pytanie. Skąd mamy wiedzieć [...]]]></description>
			<content:encoded><![CDATA[<p>Artykuł ten jest kontynuacją artykułu &#8220;Jak działa MySQL. MyISAM &#8211; Optymalizacje I&#8221;. Osobom które nie zapoznały się z wcześniejszym materiałem sugeruję przejrzenie go, w innym wypadku część informacji może się wydawać niespójna.</p>
<p>Poprzednio artykuł zakończyłem informacją jak budować zapytania aby stworzony indeks został wykorzystany. Przemilczałem jednak pewne pchające się na usta pytanie. Skąd mamy wiedzieć czy MySQL rzeczywiście wykorzystał indeks, oraz czy wykorzystał indeks właściwy. Ten artykuł chciałbym przeznaczyć właśnie na ten cel.<br />
MySQL prawdę ci powie&#8230; Aby dowiedzieć się w jaki wykonywana jest robota najlepiej spytać tego kto pracuje a nie tego kto pracę zleca. MySQL daje nam taką możliwość. Polecenie EXPLAIN potrafi wyjaśnić jak zbudowana jest tabela, oraz jak wykona się dane zapytanie.</p>
<p><span id="more-12"></span></p>
<p>Zacznijmy zatem od prostszej wersji polecenia EXPLAIN:<br />
<code></code></p>
<p><code>EXPLAIN [nazwa tabeli]<br />
Działa identycznie jak:<br />
DESCRIBE [nazwa tabeli]<br />
</code></p>
<p><code>Przykładowy wynik dla "EXPLAIN items":<br />
*************************** 1. row ***************************<br />
Field: item_id<br />
Type: int(11)<br />
Null: NO<br />
Key:<br />
Default: 0<br />
Extra:<br />
*************************** 2. row ***************************<br />
Field: name<br />
Type: varchar(255)<br />
Null: YES<br />
Key:<br />
Default: NULL<br />
Extra:<br />
*************************** 3. row ***************************<br />
Field: color<br />
Type: varchar(255)<br />
Null: YES<br />
Key:<br />
Default: NULL<br />
Extra:</code></p>
<p>Jak widać ta wersja EXPLAIN’a pozwala nam stwierdzić z jakich kolumn składa się tabela, jest to synonim poleceń &#8220;DESCRIBE&#8221; oraz &#8220;SHOW COLUMNS FROM&#8221;.<br />
Tym razem interesuje nas bardziej polecenie: EXPLAIN SELECT&#8230; i nim się teraz zajmiemy.<br />
Ta wersja zapytania informuje nas o tym jak zachowa się serwer przy wykonywaniu danego zapytania. Zacznijmy od prostego zapytania:</p>
<p><code>mysql&gt; EXPLAIN SELECT * FROM items;<br />
*************************** 1. row ***************************<br />
id: 1<br />
select_type: SIMPLE<br />
table: items<br />
type: ALL<br />
possible_keys: NULL<br />
key: NULL<br />
key_len: NULL<br />
ref: NULL<br />
rows: 4<br />
Extra: Using where</code></p>
<p>Co nam mówi ten rezultat? Ano, wiele.</p>
<ol>
<li>  id &#8211; identyfikator zapytania. Jest to numer sekwencyjny widać co w jakiej kolejności się wykonuje. Z działania wynika iż wyższy numer oznacza wcześniejsze wykonanie komendy.</li>
<li> selelect_type &#8211; Informuje nas jakiego typu jest zapytanie.
<ul>
<li>  SIMPLE (proste zapytanie, brak unii i podzapytań),</li>
<li>  PRIMARY (główne zapytanie &#8211; w przypadku gdy mamy podzapytanie),</li>
<li>  UNION (gdy korzystamy z unii),</li>
<li>  DEPENDENT UNION (gdy podzapytanie jest unią zależną od głównego zapytania),</li>
<li>  UNION RESULT (wynik unii),</li>
<li>  SUBQUERY (podzapytanie),</li>
<li>  DEPENDENT SUBQUERY (podzapytanie zależne od głównego zapytania),</li>
<li>  DERIVED (pobieramy dane nie z tabeli ale z podzapytania)</li>
</ul>
</li>
<p>Tutaj może się pojawić:</p>
<li>table &#8211; nazwa tabeli na jakiej działa zapytanie.</li>
<li>type &#8211; informuje w jaki sposób wyszukiwane są wyniki.
<ul>
<li>  system: tabela ma tylko jedna kolumnę. Specjalny przypadek typu &#8216;const&#8217;;</li>
<li>  const: Tabela ma co najwyżej jeden pasujący wynik (np., gdy szukamy po unikalnym kluczu głównym);</li>
<li>  eq_ref: Dla każego wiersza z tabeli pierwszej (t1), wybrana kolumna (k1) jest porównywana z każdym wierszem tabeli drugiej po wskazanej kolumnie (k2).</li>
<li>  ref: wyświetlane wtedy gdy jest działanie na zakresach wyników lub gdy możliwy jest wynik większy niż 1 wiersz (klucz po którym odbywa się wyszukiwanie nie jest unikalny).</li>
<li>  ref_or_null: podobnie jak wyżej ale dodatkowo null też wchodzi w zbiór wyszukiwań.</li>
<li>  index_mege: użyty został klucz wielokolumnowy.</li>
<li>  unique_subquery: powiązany z podzapytaniem w którym używany jest klucz główny (primary key). Oznacza iż podzapytanie zwraca wynik do operatora &#8216;IN&#8217;.</li>
<li>  index_subquery: podobnie jak wyżej z tą różnicą iż klucz po którym następuje wyszukiwanie nie jest unikalny.</li>
<li>  range: działanie na zakresach wyników.</li>
<li>  index: podobnie jak zjawisko opisane poniżej, z tą różnicą iż wszystkie dane w tym wypadku pobierane są z klucza.</li>
<li>  ALL: występuje wtedy gdy w celu wyszukania wyniku należy odwiedzić wszystkie wiersze tabeli. Oczywiście najmniej optymalne i zazwyczaj tego wyniku będziecie się chcieli wystrzegać.</li>
</ul>
</li>
<p>Możliwe wartości i ich znaczenie:</p>
<li>	possible_keys: indeksy które mogą zostać wykorzystane w celu znalezienia wyniku.</li>
<li> key: klucz, który został wybrany do wyszukania wyniku. Pragnę tutaj nadmienić iż nie zawsze MySQL wybiera najlepszą możliwość. Ta informacja pozwala nam jednak dowiedzieć się z jakich innych indeksów możemy próbować korzystać.</li>
<li>key_len: długość klucza który został wybrany do wykorzystania w drodze wyszukiwania. Pozwala wywnioskować z ilu części klucza złożonego MySQL korzysta podczas tego zapytania.</li>
<li>8.	ref: mówi nam które kolumny lub stałe posłużyły do wybrania wierszy z tabeli.</li>
<li>rows: ważna informacja, mówi ile minimalnie wierzy musi zostać przeszukane aby znaleźć wynik.</li>
<li>extra: dodatkowe informacje. Tutaj może się pojawić:
<ul>
<li>distinct: informacja iż mysql pominie podobne wyniki;</li>
<li>not exists: mysql mógł przeprowadzić operację left join, ale nie był zdolny wyciągnąć informacji z drugiej tabeli.</li>
<li>range checked for each record (index map: #): mysql nie znalazł odpowiednich indeksów, możliwe jest jednak skorzystanie z innych indeksów. MySQL sprawdza zatem przy każdym wierszu czy może skorzystać z typu range lub index_merge w celu zoptymalizowania wyszukiwania.</li>
<li>using index: wyniki zostały wyszukane poprzez indeks, dane również zostały pobrane z indeksu. Nie było potrzeby wykonywać odczytu wszystkich informacji z wiersza.</li>
<li>using temporary: w celu znalezienia wyniku utworzona została tabela tymczasowa.</li>
<li>using where: informuje iż składnia WHERE została użyta to ograniczenia danych wysyłanych klientowi.</li>
<li>Using sort_union(&#8230;) , Using union(&#8230;) , Using intersect(&#8230;): informuje w jaki sposób wykorzystane zostały indeksy wiązane.</li>
<li>using index for group-by: informacja iż group by wykorzystał indeks. Niezwykle rzadki widok jednak niesamowicie piękny.</li>
</ul>
</li>
</ol>
<p>Cóż widać iż sam EXPLAIN może nam bardzo wiele powiedzieć. Prawdopodobnie wiele z rzeczy opisanych powyżej niewiele dla was znaczy, dlatego pozwalam sobie poniżej przedstawić kilka zapytań generujących specyficzne wartości w EXPLAIN&#8217;ie. Dla celów testowych pozwoliłem sobie stworzyć tabele, może nie mają dużego sensu ale pozwalają osiągnąć spodziewane wyniki. Bazę można pobrać stąd wykorzystać lokalnie w celu zabawy z mysql.</p>
<p><strong>SIMPLE + ALL.</strong></p>
<p><code>EXPLAIN SELECT * FROM items;</code></p>
<p>Co oznacza tego typu kod? Generalnie jest to proste zapytanie, brak joinow brak podzapytań. Niestety zażądaliśmy wszystkich rekordów więc serwer przejrzał każdy rekord (type: ALL). Jeżeli zdejmiecie klucz z tabeli i wyszukacie danych to podobnie jak tutaj zobaczycie ALL ponieważ konieczne będzie fizyczne odwiedzenie wierszy w celu znalezienia poprawnego wyniku.</p>
<p><strong>PRIMARY + DEPENDENT SUBQUERY.</strong></p>
<p><code>mysql&gt; SELECT * FROM groups WHERE group_id &lt; SOME (SELECT item_id FROM items) \G<br />
*************************** 1. row ***************************<br />
group_id: 1<br />
group_name: Jabłka<br />
gtype: food<br />
*************************** 2. row ***************************<br />
group_id: 2<br />
group_name: Gruszki<br />
gtype: food<br />
*************************** 3. row ***************************<br />
group_id: 3<br />
group_name: Banany<br />
gtype: food<br />
*************************** 4. row ***************************<br />
group_id: 1<br />
group_name: telefony sznurowe<br />
gtype: telco<br />
*************************** 5. row ***************************<br />
group_id: 2<br />
group_name: Telefony komórkowe<br />
gtype: telco<br />
5 rows in set (0,02 sec)</code></p>
<p>Z wyniku odczytać można iż najpierw wykonywało się zapytanie pobierające dane z groups, ponieważ od jego wyniku zależą wyniki głównego zapytania (DEPENDENT QUERY). Jak widać MySQL nie skorzystał z kluczy (bo ich nie ma) i musiał przejrzeć wszystkie rekordy istniejące w obu tabelach (type: ALL, wartości w kolumnie rows są zgodne z ilością rekordów w obu tabelach).<br />
Podrasujmy nieco tabele dodając im klucze. Zacznijmy od groups, dodam tam na razie klucz unikalny:</p>
<p><code>mysql&gt; ALTER TABLE groups ADD UNIQUE `group` (group_id, gtype);<br />
Query OK, 5 rows affected (0,03 sec)<br />
Records: 5 Duplicates: 0 Warnings: 0</code></p>
<p>mysql&gt; EXPLAIN SELECT * FROM items WHERE group_id IN (SELECT group_id FROM groups WHERE gtype = &#8216;food&#8217;)\G<br />
*************************** 1. row ***************************<br />
id: 1<br />
select_type: PRIMARY<br />
table: items<br />
type: ALL<br />
possible_keys: NULL<br />
key: NULL<br />
key_len: NULL<br />
ref: NULL<br />
rows: 7<br />
Extra: Using where<br />
*************************** 2. row ***************************<br />
id: 2<br />
select_type: DEPENDENT SUBQUERY<br />
table: groups<br />
type: unique_subquery<br />
possible_keys: group<br />
key: group<br />
key_len: 5<br />
ref: func,const<br />
rows: 1<br />
Extra: Using index; Using where<br />
2 rows in set (0,00 sec)<br />
Jak widać po dodaniu klucza MySQL przewiduje mniejszą ilość rekordów do odwiedzenia.<br />
Jak pisałem w poprzednim rozdziale zależność DEPENDENT SUBQUERY bywa niebezpieczna. Wielokrotnie spotkałem się z sytuacją w której serwer MySQL zapętlał się przy takim zapytaniu. Jest to wada serwera, ma zostać ponoć rozwiązana w wersji 5.1, serwer znajduje nieistniejącą zależność pomiędzy zapytaniem wewnętrznym a zewnętrznym i wykonuje podzapytanie dla każdego wiersza zapytania głównego.<br />
Przy adekwatnie zachowującym się zapytaniu typu LEFT JOIN problemów nie zaobserwowałem.<br />
Zapytania wykonywały się w czasie ~0.02sec.</p>
<p>W każdym przypadku zauważenia iż MySQL traktuje zapytanie jako DEPENDEND SUBQUERY zalecam użycie komendy EXPLAIN EXTENDED:</p>
<p>mysql&gt; EXPLAIN EXTENDED SELECT * FROM items WHERE group_id IN (SELECT group_id FROM groups WHERE gtype = &#8216;food&#8217;);</p>
<p>Wynik zapytania jest podobny, ale ważna jest inna rzecz która ludzie zazwyczaj pomijają:<br />
<code></code></p>
<p><code>2 rows in set, 1 warning (0,00 sec)<br />
Ja widać MySQL zgłasza ostrzeżenie i warto spojrzeć co się w nim kryje:<br />
mysql&gt; SHOW warnings\G<br />
*************************** 1. row ***************************<br />
Level: Note<br />
Code: 1003<br />
Message: select `bochen`.`items`.`item_id` AS `item_id`,`bochen`.`items`.`group_id` AS `group_id`,`bochen`.`items`.`name` AS `name`,`bochen`.`items`.`price` AS `price` from `bochen`.`items` where &lt;in_optimizer&gt;(`bochen`.`items`.`group_id`,&lt;exists&gt;(((`bochen`.`items`.`group_id`) in groups on group where (`bochen`.`groups`.`gtype` = _latin2'food'))))<br />
1 row in set (0,00 sec)<br />
</code></p>
<p>Jeżeli przedstawione zapytanie jest zgodne z Waszymi założeniami to można korzystać z podzapytania, jeżeli macie wątpliwości zastanówcie się nad zmianą zapytania na JOIN. Warto jednak zapamiętać: Przy DEPENDEND QUERY przygotuj się na problemy.</p>
<p><strong> UNION + UNION RESULT.</strong></p>
<p>Rozpatrzmy takie oto zapytanie:<br />
<code>mysql&gt; explain select * from items where item_id = 3 OR price=15\G<br />
*************************** 1. row ***************************<br />
id: 1<br />
select_type: SIMPLE<br />
table: items<br />
type: ALL<br />
possible_keys: item_id<br />
key: NULL<br />
key_len: NULL<br />
ref: NULL<br />
rows: 7<br />
Extra: Using where<br />
1 row in set (0,00 sec)<br />
</code></p>
<p>Jak widać jest możliwość skorzystania z klucza item_id, a jednak serwer nie korzysta z niego. Dlaczego? Bo i tak nic by mu to nie dało.<br />
Aby sprawdzić które artykuły w bazie mają cenę 15 musi i tak przeprowadzić Full Row Scan. Jak go zatem zmusić do wykorzystania indeksu i przy okazji zoptymalizować zapytanie? Można skorzystać z UNII:</p>
<p><code>mysql&gt; explain select * from items where item_id = 3 union select * from items where price=15\G<br />
*************************** 1. row ***************************<br />
id: 1<br />
select_type: PRIMARY<br />
table: items<br />
type: ref<br />
possible_keys: item_id<br />
key: item_id<br />
key_len: 4<br />
ref: const<br />
rows: 2<br />
Extra:<br />
*************************** 2. row ***************************<br />
id: 2<br />
select_type: UNION<br />
table: items<br />
type: ALL<br />
possible_keys: NULL<br />
key: NULL<br />
key_len: NULL<br />
ref: NULL<br />
rows: 7<br />
Extra: Using where<br />
*************************** 3. row ***************************<br />
id: NULL<br />
select_type: UNION RESULT<br />
table: <union1,2><br />
type: ALL<br />
possible_keys: NULL<br />
key: NULL<br />
key_len: NULL<br />
ref: NULL<br />
rows: NULL<br />
Extra:</union1,2></code></p>
<p>Jak widać podczas pierwszego zapytania mysql skorzystał z indeksu i odwiedził tylko dwa rekordy, niestety na potrzeby drugiego musiał odwiedzić wszystkie. Dodanie indeksu na pole &#8216;price&#8217; tym razem załatwia sprawę:</p>
<p><code>mysql&gt; explain select * from items where item_id = 3 union select * from items where price=15\G<br />
*************************** 1. row ***************************<br />
id: 1<br />
select_type: PRIMARY<br />
table: items<br />
type: ref<br />
possible_keys: item_id<br />
key: item_id<br />
key_len: 4<br />
ref: const<br />
rows: 2<br />
Extra:<br />
*************************** 2. row ***************************<br />
id: 2<br />
select_type: UNION<br />
table: items<br />
type: ref<br />
possible_keys: price<br />
key: price<br />
key_len: 4<br />
ref: const<br />
rows: 2<br />
Extra: Using where<br />
*************************** 3. row ***************************<br />
id: NULL<br />
select_type: UNION RESULT<br />
table: <union1,2><br />
type: ALL<br />
possible_keys: NULL<br />
key: NULL<br />
key_len: NULL<br />
ref: NULL<br />
rows: NULL<br />
Extra:<br />
3 rows in set (0,00 sec)</union1,2></code></p>
<p>Dla dociekliwych. EXPLAIN EXTENDED wyświetla ostrzeżenie jednak do proponowanego zapytania nie mam zastrzeżeń.<br />
Mimo wszystko widać że po wprowadzeniu indeksu oraz wykorzystaniu polecenia UNION mysql nie musi odwiedzać wszystkich rekordów.<br />
DEPENDENT UNION.<br />
Przedstawię zupełnie abstrakcyjny przykład:</p>
<p><code>mysql&gt; explain extended SELECT groups.group_id FROM groups WHERE groups.group_id IN (select group_id from items where item_id = 3 union select group_id from items where price=15)\G<br />
*************************** 1. row ***************************<br />
id: 1<br />
select_type: PRIMARY<br />
table: groups<br />
type: index<br />
possible_keys: NULL<br />
key: group<br />
key_len: 5<br />
ref: NULL<br />
rows: 5<br />
Extra: Using where; Using index<br />
*************************** 2. row ***************************<br />
id: 2<br />
select_type: DEPENDENT SUBQUERY<br />
table: items<br />
type: eq_ref<br />
possible_keys: item_id<br />
key: item_id<br />
key_len: 8<br />
ref: const,func<br />
rows: 1<br />
Extra: Using where; Using index<br />
*************************** 3. row ***************************<br />
id: 3<br />
select_type: DEPENDENT UNION<br />
table: items<br />
type: ref<br />
possible_keys: price<br />
key: price<br />
key_len: 4<br />
ref: const<br />
rows: 2<br />
Extra: Using where<br />
*************************** 4. row ***************************<br />
id: NULL<br />
select_type: UNION RESULT<br />
table: <union2,3><br />
type: ALL<br />
possible_keys: NULL<br />
key: NULL<br />
key_len: NULL<br />
ref: NULL<br />
rows: NULL<br />
Extra:<br />
4 rows in set, 1 warning (0,00 sec)<br />
</union2,3></code></p>
<p>Cóż przy czymś takim uważałbym tak samo jak przy DEPENDEND SUBQUERY. Osobiście nie spotkałem się wśród rzeszy zapytań które przerabiałem z czymś takim ani z problemami przez nie wygenerowanymi, jednakże samo założenie ZALEŻNOŚCI pomiędzy zewnętrznym a wewnętrznym zapytaniem jest czynnikiem niepokojącym.</p>
<p><strong>SUBQUERY.</strong></p>
<p><code>mysql&gt; explain select group_id from groups where group_id &gt; ALL (SELECT items.group_id FROM items) AND gtype='food'\G<br />
*************************** 1. row ***************************<br />
id: 1<br />
select_type: PRIMARY<br />
table: groups<br />
type: index<br />
possible_keys: NULL<br />
key: group<br />
key_len: 5<br />
ref: NULL<br />
rows: 5<br />
Extra: Using where; Using index<br />
*************************** 2. row ***************************<br />
id: 2<br />
select_type: SUBQUERY<br />
table: items<br />
type: index<br />
possible_keys: NULL<br />
key: item_id<br />
key_len: 8<br />
ref: NULL<br />
rows: 7<br />
Extra: Using index<br />
2 rows in set (0,00 sec)<br />
</code><br />
W sumie nie ma czego tłumaczyć, najpierw wykonuje się zapytanie wewnętrzne i pobiera identyfikatory group_id, potem zapytanie zewnętrzne sprawdza które identyfikatory w tabeli groups są większe aniżeli te istniejące w items. Explain extended nie czaruje i pokazuje wszystko zgodnie z założeniami.</p>
<p><strong> DERIVED.</strong><br />
<code></code></p>
<p><code>mysql&gt; EXPLAIN EXTENDED SELECT item_id, group_id, items FROM (SELECT * FROM items WHERE item_id &lt; 2) AS `a`\G SHOW WARNINGS\G<br />
*************************** 1. row ***************************<br />
id: 1<br />
select_type: PRIMARY<br />
table: <derived2><br />
type: ALL<br />
possible_keys: NULL<br />
key: NULL<br />
key_len: NULL<br />
ref: NULL<br />
rows: 2<br />
Extra:<br />
*************************** 2. row ***************************<br />
id: 2<br />
select_type: DERIVED<br />
table: items<br />
type: range<br />
possible_keys: item_id<br />
key: item_id<br />
key_len: 4<br />
ref: NULL<br />
rows: 3<br />
Extra: Using where<br />
2 rows in set, 1 warning (0,01 sec)</derived2></code></p>
<p>Wyciągnięcie danych z &#8216;tabeli&#8217; która jest wynikiem zapytania, czyli (przykładowo) obcięcie niepotrzebnych kolumn.<br />
Tyle tematem rożnych typów zapytań i niebezpieczeństw z nimi związanych, kolejnym krokiem jest zapoznanie się z kosztem powiązanym z kolumna `type` otrzymana w wyniku polecenia EXPLAIN.</p>
]]></content:encoded>
			<wfw:commentRss>http://0day.pl/index.php/archives/12/feed</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Jak działa MySQL. MyISAM &#8211; Optymalizacje I</title>
		<link>http://0day.pl/index.php/archives/11</link>
		<comments>http://0day.pl/index.php/archives/11#comments</comments>
		<pubDate>Wed, 21 Feb 2007 23:48:51 +0000</pubDate>
		<dc:creator>bochen</dc:creator>
				<category><![CDATA[mysql]]></category>
		<category><![CDATA[Ogólne]]></category>
		<category><![CDATA[optymalizacje]]></category>
		<category><![CDATA[programowanie]]></category>

		<guid isPermaLink="false">http://0day.pl/index.php/archives/11</guid>
		<description><![CDATA[Kolejnego posta czas zacząć. Tym razem 0day padł na MySQL i działanie MyISAM. Nie będę się tutaj rozpisywał o podstawach MySQL mam zamiar jedynie opisać ja działa MyISAM (i to dosyć ogólnikowo) oraz jak optymalizować zapytania i strukturę bazy tak aby wszystko dobrze działało. Oczywiście mógłbym pisać tylko o optymalizacji i nie wspominać o budowie, [...]]]></description>
			<content:encoded><![CDATA[<p>Kolejnego posta czas zacząć. Tym razem 0day padł na MySQL i działanie MyISAM.<br />
Nie będę się tutaj rozpisywał o podstawach MySQL mam zamiar jedynie opisać ja działa MyISAM (i to dosyć ogólnikowo) oraz jak optymalizować zapytania i strukturę bazy tak aby wszystko dobrze działało.</p>
<p>Oczywiście mógłbym pisać tylko o optymalizacji i nie wspominać o budowie, jednak w takim wypadku jest to podanie ryby zamiast wędki.<br />
Nie da się tutaj wszystkich możliwych ścieżek opisać, rozumiejąc jednak zasady działania można w prosty sposób optymalizować wszelkie inne zapytania. Nie są to tylko regułki w stylu: będzie szybciej jeżeli użyjesz tego czy tamtego&#8230;</p>
<p>Na początek chciałbym przestrzec że nie widzę siebie jako megaeksperta od wszystkiego. Lubię dłubać i szukać. Optymalizacja i uczenie się jak co działa, to moje małe hobby.</p>
<p>No dobrze to może od początku. MySQL ma wiele silników zarządzających danymi. Na dzień dzisiejszy najczęściej używanymi są MyISAM, InnoDB, Cluster. W kolejnych artykułach postaram się umieścić opisy dla kolejnych silników, łącznie z silnikami które są jeszcze w fazie wytwarzania. Samo rozbicie na kilka osobnych artykułów podyktowane jest ich objętością. Nie sposób w kilku sensownych słowach zawrzeć szereg przydatnych informacji dotyczących tak rozbudowanych mechanizmów.</p>
<p>Wracając do MyISAM&#8217;a jest to stosunkowo najprostszy i szybki silnik (jeżeli chodzi o pobieranie danych) z omawianej bazy danych.<!--[if gte vml 1]><v:shapetype  id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t"  path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">  <v:stroke joinstyle="miter"/>  <v:formulas>   <v:f eqn="if lineDrawn pixelLineWidth 0"/>   <v:f eqn="sum @0 1 0"/>   <v:f eqn="sum 0 0 @1"/>   <v:f eqn="prod @2 1 2"/>   <v:f eqn="prod @3 21600 pixelWidth"/>   <v:f eqn="prod @3 21600 pixelHeight"/>   <v:f eqn="sum @0 0 1"/>   <v:f eqn="prod @6 1 2"/>   <v:f eqn="prod @7 21600 pixelWidth"/>   <v:f eqn="sum @8 21600 0"/>   <v:f eqn="prod @7 21600 pixelHeight"/>   <v:f eqn="sum @10 21600 0"/>  </v:formulas>  <v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>  <o:lock v:ext="edit" aspectratio="t"/> </v:shapetype><v:shape id="_x0000_i1025" type="#_x0000_t75" alt="More..."  style='width:600pt;height:7.5pt'>  <v:imagedata xsrc="file:///C:\DOCUME~1\bochen\USTAWI~1\Temp\msohtml1\01\clip_image001.gif"      o:href="http://0day.pl/wp-includes/js/tinymce/themes/advanced/images/spacer.gif"   /> </v:shape><![endif]--><!--[if !vml]--><span id="more-11"></span><!--[endif]--><br />
Kolejne rekordy doklejane są zawsze na końcu pliku, można powiedzieć że struktura wewnętrzna bazy mocno przypomina listę wiązaną.<br />
Powodem jest tego podobieństwa jest to, iż aby dostać się do następnego rekordu należy odczytać informację gdzie takowy się znajduje. Poniżej przedstawiam rysunek przedstawiający strukturę MyISAM w baaardzo dużym uproszczeniu.</p>
<p align="center"><img src="http://0day.pl/gfx/MyISAM-struct.png" title="Struktura bazy MyISAM" alt="Struktura bazy MyISAM" align="middle" /><br />
Struktura danych w MyISAM</p>
<p align="left">
<p>Długość rekordów w bazie może się zmieniać, dlatego w nagłówku każdego z rekordu znajdują się informacje, które umożliwiają wyliczenie położenia kolejnego rekordu. Skutkiem takiej budowy dodawanie danych jest niezwykle szybkie (uogólniając). Nie jest konieczne przechodzenie przez szereg rekordów aby znaleźć miejsce dla nowego rekordu. Jest jeszcze jedna cecha tego silnika. Zmodyfikowane obiekty mogą wymagać przeniesienia ich w nowe miejsce (oczywiście na koniec pliku) tym samym w środku pliku pozostawiana jest informacja iż rekord jest usunięty. Tym samym każdy usunięty rekord cały czas fizycznie istnieje w strukturze tabeli.</p>
<p>Proponuję wykonać test. Stwórzmy sobie najpierw nic nie znaczącą tabelę items w której występują pola item_id, name oraz color.</p>
<p><code>CREATE TABLE items (item_id INTEGER, name VARCHAR(255), color VARCHAR(255));</code></p>
<p align="left">Dodajmy rekordy testowe:</p>
<p><code> INSERT INTO items SET item_id=1, name='aaaaaa', color='red';<br />
INSERT INTO items SET item_id=2, name='bbbbbb', color='red';<br />
INSERT INTO items SET item_id=3, name='cccccc', color='red';<br />
INSERT INTO items SET item_id=4, name='dddddd', color='red';<br />
INSERT INTO items SET item_id=5, name='eeeeee', color='red';<br />
</code></p>
<p align="left">Sprawdzając zajętość tabeli otrzymamy: 100b, czyli po 20b na rekord. Zajętość tabeli można sprawdzić dwojako:<br />
1) wykonując komendę: SHOW TABLE STATUS;<br />
2) Sprawdzając wielkość pliku items.MYD w odpowiednim folderze powiązanym z danymi serwera MySQL.</p>
<p>Wykonajmy zatem kolejne całkiem zwyczajne zapytanie:</p>
<p><code>DELETE FROM items WHERE item_id=2;</code></p>
<p align="left">Po sprawdzeniu zajętości okazuje się iż wielkość tabeli nie zmieniła się, Oczywiście SHOW table status wskazuje że w tabeli znajduje się teraz wolna przestrzeń (20B). Cóż oznacza to tylko tyle że rekordy pozostają w tabeli nawet pomimo usunięcia. Cóż można przypuszczać świetna optymalizacja nic się nie dzieje, szybka operacja. Co jednak z zajętym miejscem?<br />
Nasuwa się kolejne pytanie, co dzieje się z rekordem kiedy modyfikacja wykracza poza przestrzeń przeznaczona dla niego. Cóż przekonajmy się. Niedowiarkom proponuję zajrzeć jakimkolwiek edytorem to pliku items.MYD, okaże się iż ciąg &#8216;aaaaaa&#8217; jest na początku pliku zgodnie z przewidywaniami. Zmodyfikujmy ten własnie rekord rekord:</p>
<p><code>UPDATE items SET name='111111222222333333444444555555666666777777888888999999' WHERE item_id = 1;</code></p>
<p>Po ponownym obejrzeniu pliku okazuje się że rekord został przeniesiony na koniec pliku. Wielkość pliku to 164 bajty, wskazywana ilość wolnego miejsca w tabeli to dalej 20B. Błąd? Nie MyISAM aby nie kopiować niepotrzebnie rekordów umożliwia utworzenie wskazania na dane fragmentując rekord (teraz już wiadomo czemu rysunek przedstawiony wyżej był bardzo uproszczony <img src='http://0day.pl/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' />  ).</p>
<p align="center"> <img src="http://0day.pl/gfx/fragmentacja.png" title="Struktura danych przed i po modyfikacjach" alt="Struktura danych przed i po modyfikacjach" border="1" height="261" width="719" /><br />
Na rysunku widać skasowany rekord (zaznaczony wartościami 0xFF) oraz rekord, dla którego dane przeniesione zostały na koniec pliku.</p>
<p>Reasumując powyższy fragment opowieści: wstawianie, modyfikacja oraz usuwanie danych z tabel MyISAM owych <strong>może być </strong>niezwykle szybkie.</p>
<p><strong>Chciałbym w tym miejscu dodać jeszcze jedną uwagę. Skutki budowy tabeli oraz algorytmów modyfikacji rekordów powodują, że w środowisku wielowątkowym/wieloprosesowym, dostęp do zapisu powinien mieć tylko jeden wątek/proces. Co oznacza iż trzeba zablokować całą tabelę na czas modyfikacji. W przeciwnym wypadku dwa osobne procesy mogą pomieszać swoje dane i powstanie niespójność.</strong></p>
<p>Co zatem z wyszukiwaniem? Cóż znając budowę list od razu możemy stwierdzić, że aby dostać się do ostatniego rekordu musimy odwiedzić wszystkie poprzedniki. Jeżeli często będziemy się odwoływać do losowych rekordów a baza będzie duża, cóż nie będziemy zadowoleni.</p>
<p>Odejdźmy na chwilę od głównego tematu i zastanówmy się czym jest optymalizacja. W ogólnym słowa znaczeniu jest to <strong>skracanie ścieżek poszukiwań</strong>. Nie jest to wszystko ale na razie się na tym zatrzymamy.</p>
<p>Pierwsza rzeczą jaka się ciśnie na palce po tym co zrobiliśmy wcześniej jest posprzątanie nieużytków, czyli ominięcie rekordów usuniętych a tym samym zmniejszenie ilości kroków które trzeba przebyć aby dostać się do wyniku.<br />
W tym celu można wywołać komendę:<br />
<code>OPTIMIZE TABLE;</code></p>
<p>Po wykonaniu jej zaobserwujemy ze zwolniło się niemalże 40b. To by się mniej-więcej zgadzało. Oczywiście aby teraz dobrać się do ostatniego rekordu potrzebujemy jeden skok mniej.</p>
<p>Pomimo takiego zabiegu każdy programista powinien stwierdzić że lista nie bardzo przydaje się do wyszukiwania obiektów. I ma rację.<br />
Kolejnym krokiem przy skracaniu ścieżek przeszukiwań jest zmiana struktury. Przykładowo jeżeli często będziemy wyszukiwać po kolumnie item_id przydało by się ułożyć rekordy w taki sposób aby nie musieć odwiedzać wszystkich by znaleźć ten właściwy. Cóż sama zmiana rekordów również nie byłaby na rękę, gdyby tak się stało to co należałoby zrobić gdyby konieczne byłoby skrócenie ścieżek również dla innej kolumny?<br />
Rozwiązaniem tutaj są indeksy. Dla struktury MyISAM dostępny jest tylko jeden typ indeksu (i dobrze, będzie mniej pisania). Indeks ma strukturę <a href="http://en.wikipedia.org/wiki/B-drzewo" title="B+drzewo" target="_blank">B+drzewa</a>. Nie będę się tutaj rozpisywał jak to dokładnie wygląda zainteresowanych odsyłam do książek o algorytmach i strukturach danych lub do wikipedii. Powiem w skrócie iż <a href="http://en.wikipedia.org/wiki/B-drzewo" target="_blank" title="B+drzewo">B+drzewo</a> jest drzewem n-arnym co oznacza, że na jednym liściu znajduje się wiele kluczy i każdy węzeł może mieć n-potomków. Każdy klucz w indeksie wskazuje na właściwy powiązany rekord w pliku *.MYD. Indeksy przechowywane są w pliku *.MYI.<br />
Po utworzeniu indeksu wyszukiwanie elementów może się skrócić o rzędy wielkości, np z 1000000 porównań można zejść nawet do 3.<br />
Ładne porównanie prawda?</p>
<p>Cóż jednak nie wszystko złoto co się świeci. Nie ma rozwiązań doskonałych i wszystko ma swoje ograniczenia.<br />
O ile wyszukiwanie danych w takiej strukturze jest niezwykle szybkie, o tyle modyfikacja struktury oraz budowa drzewa jest dosyć skomplikowana i często wiąże się z wielokrotnym kopiowaniem danych w pamięci. Jeżeli więc częściej będą wykonywane modyfikacje klucza aniżeli wyszukiwania optymalizacja zda się na nic. Nawet przyniesie szkody w postaci większych opóźnień czasowych.</p>
<p>Czy zatem jesteśmy straceni, skazani na żółwie tempo? Oczywiście że nie. Przede wszystkim trzeba wiedzieć do czego potrzebna nam struktura danych. Zazwyczaj znacznie częściej wyszukujemy dane aniżeli je modyfikujemy i właśnie w takim przypadku należy używać struktury MyISAM + indeksy. Jeżeli natomiast częściej wykonywane są modyfikacje rekordów bądź jest to stosunek 1:1 to radziłbym używać innych struktur ale o tym w kolejnych artykułach.</p>
<p>Wracając do tematu. Sam indeks to nie wszystko. Jeżeli nieodpowiednio będzie się tworzyć zapytania to może się okazać że baza wcale ich nie wykorzysta i będą bardziej jak kula u nogi. Zatem jak pisać zapytania?<br />
Główna odpowiedz to: pisz prosto i czytelnie. Jeżeli ty drogi czytelniku będziesz miał problem z zawiłym zapytaniem, to zapewniam Ciebie ze MySQL również.</p>
<p>Kilka prostych zasad na początek:<br />
0) Dobry benchmark nie jest zły. Testuj to co masz zamiar zrobić zanim wypuścisz do użytkowników.<br />
Same zasady nic nie dają, wszystko zależy od wielu czynników o których można napisać obszerną książkę a nie artykuł. Trzeba starać się zrozumieć co się dzieje, jak pracuje MySQL. Tylko w takim wypadku można prawidlowo optymalizować.</p>
<p>1) Unikaj w zapytaniach alternatyw: (SELECT * FROM t1 WHERE c1 = 1 OR c2 = 2);<br />
W wielu przypadkach MySQL nie potrafi poprawnie wykorzystać indeksów. Jeżeli jest konieczne wykorzystanie alternatywy upewnij się że obie kolumny są zawarte w jednym indeksie. Jeżeli to nie przyniosło rezultatu sprawdź czy wykonanie osobno zapytań jest szybsze. Jeżeli tak to można próbować połączyć zapytanie za pomocą unii:</p>
<p><code>SELECT  * FROM t1 WHERE c1 = 1 UNION SELECT * FROM t1 WHERE c2 = 2 ORDER BY c_order;</code></p>
<p>W takim wypadku robione są osobno dwa zapytania, potem łączone. Wspólny wynik podlega dyrektywom ORDER, GROUP BY, HAVING&#8230;</p>
<p>Jeżeli OR dotyczy tej samej kolumny proponuję użyć skladni IN.</p>
<p>2) Z jakiegoś powodu użycie ABS w składni WHERE powoduje 10 krotny spadek wydajności (w mysql &gt;= 4.1).<br />
Zamieniaj zatem ABS na:</p>
<p><code>SELECT * FROM t1 WHERE c1 IN (-1, 1);</code></p>
<p>3) Pamiętaj, że jeżeli zapytanie wymaga obliczania go dla każdego wiersza to mysql przeprowadzi full rowscan, czyli nie skorzysta z indeksu. Bardzo prostym przykładem takiego zapytania może być:</p>
<p><code>SELECT * FROM items WHERE 1 &lt; item_id &lt; 3;</code></p>
<p>Dla wielu programistów którzy mają możliwość stosowania takiej pisowni w swoich językach (np python), może to być normalny zapis.<br />
Niestety dla MySQL nie jest tym samym czym mogło by się wydawać. Zapytanie to zostanie rozważone w taki sposób:</p>
<blockquote><p>DLA Każdego wiersza:</p>
<blockquote><p><code> X = 1 &lt; item_id; // Tutaj może być prawda lub fałsz (1, 0)</code><br />
<code> wynik = X &lt; 3;</code></p>
</blockquote>
</blockquote>
<p>Oczywiście zapytanie wykona się niby prawidłowo (o ile ostatnim rekordem będzie item_id == 3), jest to jednak przypadek który pozostawiam do rozpatrzenia czytelnikom <img src='http://0day.pl/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .<br />
Faktem jest, że MySQL odwiedzi wszystkie rekordy nawet jeżeli item_id będzie unikalnym kluczem głównym (Primary Key).</p>
<p>Jeżeli rozpiszemy to zapytanie prawidłowo:<br />
<code>    SELECT * FROM items WHERE 1 &lt; item_id AND item_id &lt; 3;</code></p>
<p>To w procesie wyszukiwania wykorzystany zostanie indeks prawidłowo.</p>
<p>4) Ostrożnie z podzapytaniami.<br />
Źle zaprojektowane podzapytania mogą podobnie jak w powyższym przypadku być wykonywane dla każdego przeszukiwanego rekordu. Mogą również tworzyć olbrzymie produkty kartezjańskie. Zapewne nie jest to coś co chcielibyśmy osiągnąć.<br />
W dokumentacji MySQL doszukać się można kilku optymalizacji dt. podzapytań:</p>
<p>- ograniczaj maksymalnie ilość wyników otrzymywanych z podzapytania w składni IN(SELECT &#8230;);<br />
- nie wykonuj podzapytania dla każdego wiersza:<br />
to będzie wykonane raz:</p>
<blockquote><p><code>SELECT (SELECT column1 + 5 FROM t1) FROM t2;</code></p>
</blockquote>
<p>to n razy: <code></code></p>
<blockquote><p><code>SELECT (SELECT column1 FROM t1) + 5 FROM t2;</code></p>
</blockquote>
<p>- jeżeli podzapytanie zwraca zawsze jeden wiersz używaj znaku równości a nie zakresu:</p>
<blockquote><p><code>SELECT * FROM t1 WHERE t1.col_name = (SELECT a FROM t2 WHERE b = some_const);</code><br />
zamiast:<br />
<code>SELECT * FROM t1 WHERE t1.col_name IN (SELECT a FROM t2 WHERE b = some_const);</code></p>
</blockquote>
<p>5) Sprawdzaj czy adekwatny join nie działa szybciej. Zapytania wiązane joinami często lepiej są optymalizowane. Przykładowo:</p>
<blockquote><p><code>SELECT * FROM t1 WHERE id IN (SELECT id FROM t2);</code><br />
można zamienić na:<br />
<code>SELECT t1.* FROM t1 LEFT JOIN t2 ON t1.id=t2.id;</code></p>
</blockquote>
<p>Cóż ogólnie podstawy optymalizacji zostały tutaj wyłuszczone. Zapewniam jeszcze że to nie wszystko.<br />
Reszta w kolejnym artykule.</p>
<p>6) Unikaj tworzenia iloczynów kartezjańskich. Polecenie:</p>
<blockquote><p> <code>SELECT * FROM t1, t2 WHERE t1.col1 = t2.col1 AND t2.col2 &gt; 10</code></p>
</blockquote>
<p>powoduje że:</p>
<ul>
<li>Stworzony zostaje produkt kartezjański <code>t1 x t2</code>. Oznacza to iż w pamięci powstaje tabela która przechowuje rekordy w ilości: <code>count(t1) * count(t2)</code>.</li>
</ul>
<ul>
<li>Wynik jest wybierany z tabeli powstałej w wyniku stworzenia produktu kartezjańskiego.</li>
</ul>
<p>Jeżeli tabele t1 i t2 posiadają po 10 000 wierszy, to powstanie tabela o wielkości 100000000 wierszy. Warto jest używać w takich przypadkach instrukcji LEFT JOIN, który jeżeli jest odpowiednio skonstruowany może zmniejszyć ilość wyszukiwanych rekordów nawet 10 000 krotnie.</p>
]]></content:encoded>
			<wfw:commentRss>http://0day.pl/index.php/archives/11/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Strach w zarządzaniu</title>
		<link>http://0day.pl/index.php/archives/10</link>
		<comments>http://0day.pl/index.php/archives/10#comments</comments>
		<pubDate>Wed, 14 Feb 2007 21:59:38 +0000</pubDate>
		<dc:creator>bochen</dc:creator>
				<category><![CDATA[Ogólne]]></category>
		<category><![CDATA[Zarządzanie]]></category>

		<guid isPermaLink="false">http://0day.pl/?p=10</guid>
		<description><![CDATA[Może zacznę od innej strony. Od wielu lat trenuję trochę sztuk walki. Przez ten czas przyuważyłem kilka interesujących zachowań. Generalnie uważam (i nie tylko ja zresztą) iż trening jest małym obrazem ludzkiego życia. Jeżeli ktoś odpuszcza na treningach, odpuszcza wszystkiemu we własnym życiu, jeżeli zachowuję się agresywnie i przejmuje inicjatywę itd itd. Ogólnie 3 punkty [...]]]></description>
			<content:encoded><![CDATA[<p>Może zacznę od innej strony.</p>
<p>Od wielu lat trenuję trochę sztuk walki. Przez ten czas przyuważyłem kilka interesujących zachowań.<br />
Generalnie uważam (i nie tylko ja zresztą) iż trening jest małym obrazem ludzkiego życia.<br />
Jeżeli ktoś odpuszcza na treningach, odpuszcza wszystkiemu we własnym życiu,<br />
jeżeli zachowuję się agresywnie i przejmuje inicjatywę itd itd.<span id="more-10"></span></p>
<p>Ogólnie 3 punkty o których chciałbym napisać.</p>
<p>1. Strach paraliżuje.<br />
Sparując z ludźmi bądź oglądając sparingi innych zauważyłem ciekawą rzecz. Osoba, która jest nieobyta, zestresowana zostaje zawsze zepchnięta w kąt i przegrywa. Jak to się ma do zarządzania?<br />
Prosto jeżeli jesteś kierownikiem zespołu zadbaj o to aby ludzie w twoim zespole mieli przygotowane dobre środowisko testowe. Namawiaj i wymagaj do tworzenia testów. Oczywiście z testami też przesadzać nie można, w innym wypadku można się zamienić w psa goniącego własny ogon.<br />
Przygotowując testy należy skupić się na kosztach które może spowodować wystąpienie błędu. Im większy koszt związany z wystąpieniem błędu tym bardziej naciskaj na stworzenie odpowiednich testów.</p>
<p>Nie przepisuj kodu. Tworząc oprogramowanie dobrze jest zrobić je bardziej funkcjonalne, przykładowo można zbudować je w ten sposób iż normalnie uruchomione pokazuje co może się stać. Uruchomione w trybie pełnym wykonuje wszystkie czynności. Stosując w miarę możliwości tą zasadę masz testy i gotową aplikację bez przepisywania. Oczywiście mimo wszystko nie należy zapominać o środowisku testowym.</p>
<p>2. Kiedy nie wiem co się kryje w szafie, mnożą się potwory.<br />
Trochę dziecięcy punkt. W walce oznacza, że jeżeli człowiek nie ma pojęcia co się może wydarzyć to na pewno nie zareaguje odpowiednio. W zarządzaniu znaczy że jeżeli człowiek nie wie o czym mówi i czego wymaga to jego zespół zawiedzie na pewno. W tworzeniu projektu oznacza to, że jeżeli zespół nie zna systemu po porusza sie po omacku, to projekt będzie się ciągnął bez końca.</p>
<p>Zaradzić temu jest prosto. Interesuj się, wgryzaj się. Zachowuj się jak kilkuletnie dziecko, żyj z pytaniem: a dlaczego? Jeżeli zarazisz zespół chęcią zbierania wiedzy, sam będziesz ją rozszerzał i przede wszystkim wymagał znajomości systemu od grupy to z pewnością powstanie mniej kwiatków.</p>
<p>3. Wykorzystaj energię przeciwnika. Mieczowi pozwól ciąć a nie tnij za niego.<br />
Podoba mi się ten tytuł, może dlatego że lubię miecz <img src='http://0day.pl/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' /> .<br />
Ogólnie co może osoba zarządzająca grupą:<br />
a) Upierać się przy swoich rozwiązaniach i trwać przy tym jak osioł.<br />
b) Pozwolić na wszystko zespołowi.<br />
c) Starać się zrozumieć zespół, pojąć ich wiedzę i przekierować ich możliwości w stronę użyteczną.</p>
<p>Oczywiście punkt a i b nie są dobrym rozwiązaniem a mimo wszystko stosowane są najczęściej.<br />
W punkcie a za każdym razem jest walka zespół musi się zawsze dostosować do osoby kierującej. Nie jest to dobre, sam to przećwiczyłem (cóż, też czasem popełniam błędy).<br />
Punkt B jest prostą drogą do chaosu.</p>
<p>Punkt C jest złożony wymaga zrozumienia zespołu, jednak system otrzymany jest spójny bo kierowany jedną głową i czas wykonania jest krótszy bo zespół nie musi uczyć się wielu nowych rzeczy.</p>
<p>Zakończę jak Musashi Miyamoto. Należy się nad tym wszystkim mocno zastanowić.<br />
Nieważne czy jesteś osobą która zarządza czy osobą zarządzaną.</p>
]]></content:encoded>
			<wfw:commentRss>http://0day.pl/index.php/archives/10/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Booting process&#8230;</title>
		<link>http://0day.pl/index.php/archives/3</link>
		<comments>http://0day.pl/index.php/archives/3#comments</comments>
		<pubDate>Thu, 18 Jan 2007 12:25:59 +0000</pubDate>
		<dc:creator>bochen</dc:creator>
				<category><![CDATA[Ogólne]]></category>

		<guid isPermaLink="false">http://bochen.home.pl/0day/wordpress/?p=3</guid>
		<description><![CDATA[{ __asm MOV EAX, 0xfffffffff0; __asm JMP EAX; } 0day.pl BIOS. Hit F2 to enter&#8230; Cóż, zawsze przychodzi czas że ktoś musi zacząć pierwszy. Tym razem padło na mnie&#8230; Coprawda pierwszy nie jestem, ale ostatnim też chyba nie będę. Co się tutaj znajdzie, nie wiem. W zamiarze mam umieszczać tutaj użyteczne informacje ale kto wie [...]]]></description>
			<content:encoded><![CDATA[<p>{</p>
<blockquote><p>__asm MOV EAX, 0xfffffffff0;<br />
__asm JMP EAX;</p></blockquote>
<p>}</p>
<p>0day.pl BIOS.</p>
<p>Hit F2 to enter&#8230;</p>
<p>Cóż, zawsze przychodzi czas że ktoś musi zacząć pierwszy.<br />
Tym razem padło na mnie&#8230; Coprawda pierwszy nie jestem, ale ostatnim też chyba nie będę.</p>
<p>Co się tutaj znajdzie, nie wiem.<br />
W zamiarze mam umieszczać tutaj użyteczne informacje ale kto wie jak to będzie&#8230;</p>
]]></content:encoded>
			<wfw:commentRss>http://0day.pl/index.php/archives/3/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Page Caching using disk: basic (Requested URI is rejected)

Served from: 0day.pl @ 2012-05-20 17:31:07 -->
