<?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>hudson@work &#187; mysql</title>
	<atom:link href="http://hudson.su/tag/mysql/feed/" rel="self" type="application/rss+xml" />
	<link>http://hudson.su</link>
	<description>статьи о web-разработке, менеджменте IT проектов и контроле качества</description>
	<lastBuildDate>Fri, 20 Jan 2012 13:15:39 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	
		<item>
		<title>Random MySQL date</title>
		<link>http://hudson.su/2011/08/31/random-mysql-date/</link>
		<comments>http://hudson.su/2011/08/31/random-mysql-date/#comments</comments>
		<pubDate>Wed, 31 Aug 2011 07:00:44 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[mysql]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1953</guid>
		<description><![CDATA[В этой статье рассмотрим несколько вариантов получения случайной даты в MySQL. Для чего это нужно? Ну к примеру для генерации фикстур и прочих тестовых данных, чтобы были похожи на реальные. Самый простой вариант &#8211; совершенно случайная дата: mysql&#62; SELECT FROM_UNIXTIME&#40;RAND&#40;&#41; * 2147483647&#41; AS `rand`; +---------------------+ &#124; rand &#124; +---------------------+ &#124; 1998-04-01 21:42:48 &#124; +---------------------+ 1 [...]]]></description>
			<content:encoded><![CDATA[<p>В этой статье рассмотрим несколько вариантов получения случайной даты в MySQL. Для чего это нужно? Ну к примеру для генерации фикстур и прочих тестовых данных, чтобы были похожи на реальные.</p>
<p><span id="more-1953"></span><br />
Самый простой вариант &#8211; совершенно случайная дата:</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;">mysql<span style="color: #66cc66;">&gt;</span> <span style="color: #993333; font-weight: bold;">SELECT</span> FROM_UNIXTIME<span style="color: #66cc66;">&#40;</span>RAND<span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">*</span> <span style="color: #cc66cc;">2147483647</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> <span style="color: #ff0000;">`rand`</span>;
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #66cc66;">|</span> rand                <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #66cc66;">|</span> <span style="color: #cc66cc;">1998</span><span style="color: #66cc66;">-</span>04<span style="color: #66cc66;">-</span>01 <span style="color: #cc66cc;">21</span>:<span style="color: #cc66cc;">42</span>:<span style="color: #cc66cc;">48</span> <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #cc66cc;">1</span> <span style="color: #993333; font-weight: bold;">ROW</span> <span style="color: #993333; font-weight: bold;">IN</span> <span style="color: #993333; font-weight: bold;">SET</span> <span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.00</span> sec<span style="color: #66cc66;">&#41;</span></pre></div></div>

<p>Попробуем ещё раз?</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;">mysql<span style="color: #66cc66;">&gt;</span> <span style="color: #993333; font-weight: bold;">SELECT</span> FROM_UNIXTIME<span style="color: #66cc66;">&#40;</span>RAND<span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">*</span> <span style="color: #cc66cc;">2147483647</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> <span style="color: #ff0000;">`rand`</span>;
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #66cc66;">|</span> rand                <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #66cc66;">|</span> <span style="color: #cc66cc;">2028</span><span style="color: #66cc66;">-</span>03<span style="color: #66cc66;">-</span><span style="color: #cc66cc;">21</span> <span style="color: #cc66cc;">22</span>:<span style="color: #cc66cc;">44</span>:<span style="color: #cc66cc;">43</span> <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #cc66cc;">1</span> <span style="color: #993333; font-weight: bold;">ROW</span> <span style="color: #993333; font-weight: bold;">IN</span> <span style="color: #993333; font-weight: bold;">SET</span> <span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.00</span> sec<span style="color: #66cc66;">&#41;</span></pre></div></div>

<p>Окей, зачем умножать на 2147483647? (ещё не догадались?! <img src='http://hudson.su/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />  Ну тут всё просто, как 2 копейки: 2147483647 это самый большой SIGNED INT32 и по совместительству <del>писарчук</del> самый последний распоследний таймштамп. Проверяется это так (ежели не верите):</p>
<p>Так ещё работает:</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;">mysql<span style="color: #66cc66;">&gt;</span> <span style="color: #993333; font-weight: bold;">SELECT</span> FROM_UNIXTIME<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">2147483647</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> <span style="color: #ff0000;">`rand`</span>;
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #66cc66;">|</span> rand                <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #66cc66;">|</span> <span style="color: #cc66cc;">2038</span><span style="color: #66cc66;">-</span>01<span style="color: #66cc66;">-</span><span style="color: #cc66cc;">18</span> <span style="color: #cc66cc;">19</span>:<span style="color: #cc66cc;">14</span>:07 <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #cc66cc;">1</span> <span style="color: #993333; font-weight: bold;">ROW</span> <span style="color: #993333; font-weight: bold;">IN</span> <span style="color: #993333; font-weight: bold;">SET</span> <span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.00</span> sec<span style="color: #66cc66;">&#41;</span></pre></div></div>

<p>А если добавим хоть секундочку (214748364<strong>8</strong>) &#8211; уже работать не будет:</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;">mysql<span style="color: #66cc66;">&gt;</span> <span style="color: #993333; font-weight: bold;">SELECT</span> FROM_UNIXTIME<span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">2147483648</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> <span style="color: #ff0000;">`rand`</span>;
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">------+</span>
<span style="color: #66cc66;">|</span> rand <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">------+</span>
<span style="color: #66cc66;">|</span> <span style="color: #993333; font-weight: bold;">NULL</span> <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">------+</span>
<span style="color: #cc66cc;">1</span> <span style="color: #993333; font-weight: bold;">ROW</span> <span style="color: #993333; font-weight: bold;">IN</span> <span style="color: #993333; font-weight: bold;">SET</span> <span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.01</span> sec<span style="color: #66cc66;">&#41;</span></pre></div></div>

<p>Таким образом, функция <code>RAND()</code> даёт нам случайное значение в диапазоне от 0 до 1 и умножая его на максимально возможный таймштамп мы получим случайную дату в диапазоне от 1969 до 2038 гг. Но что же нам делать, если требуется дата в некотором строго определённом периоде?</p>
<h3>Случайная дата 2011го года</h3>
<p>Получается вот так:</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;">mysql<span style="color: #66cc66;">&gt;</span> <span style="color: #993333; font-weight: bold;">SELECT</span> FROM_UNIXTIME<span style="color: #66cc66;">&#40;</span>RAND<span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">*</span> <span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">1325275200</span> <span style="color: #66cc66;">-</span> <span style="color: #cc66cc;">1293825600</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">+</span> <span style="color: #cc66cc;">1293825600</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> <span style="color: #ff0000;">`the_date`</span>;
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #66cc66;">|</span> the_date            <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #66cc66;">|</span> <span style="color: #cc66cc;">2011</span><span style="color: #66cc66;">-</span>04<span style="color: #66cc66;">-</span><span style="color: #cc66cc;">27</span> 05:01:04 <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #cc66cc;">1</span> <span style="color: #993333; font-weight: bold;">ROW</span> <span style="color: #993333; font-weight: bold;">IN</span> <span style="color: #993333; font-weight: bold;">SET</span> <span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.00</span> sec<span style="color: #66cc66;">&#41;</span></pre></div></div>

<p>Так круто, что хочется попробовать ещё раз <img src='http://hudson.su/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;">mysql<span style="color: #66cc66;">&gt;</span> <span style="color: #993333; font-weight: bold;">SELECT</span> FROM_UNIXTIME<span style="color: #66cc66;">&#40;</span>RAND<span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">*</span> <span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">1325275200</span> <span style="color: #66cc66;">-</span> <span style="color: #cc66cc;">1293825600</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">+</span> <span style="color: #cc66cc;">1293825600</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> <span style="color: #ff0000;">`the_date`</span>;
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #66cc66;">|</span> the_date            <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #66cc66;">|</span> <span style="color: #cc66cc;">2011</span><span style="color: #66cc66;">-</span>08<span style="color: #66cc66;">-</span><span style="color: #cc66cc;">15</span> <span style="color: #cc66cc;">12</span>:<span style="color: #cc66cc;">54</span>:<span style="color: #cc66cc;">51</span> <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">---------------------+</span>
<span style="color: #cc66cc;">1</span> <span style="color: #993333; font-weight: bold;">ROW</span> <span style="color: #993333; font-weight: bold;">IN</span> <span style="color: #993333; font-weight: bold;">SET</span> <span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.01</span> sec<span style="color: #66cc66;">&#41;</span></pre></div></div>

<h3>А чо энто было то?</h3>
<p>Когда нам нужно зафиксировать диапазон, между <code>start</code> и <code>end</code> датами, и мы имеем случайное значение от 0 до 1 &#8211; требуется вычесть из конечного таймштампа начальный <code>end - start</code>, что даст искомый интервал в секундах и &#8220;двинуть&#8221; интервал в начало периода. Т.о. наша супер-формула выглядит вот так: </p>
<p><code>(end - start) * RAND + start</code></p>
<p>Ну и для того чтобы высвободить вам побольше времени для Angry Birds (или чего-то подобного), а также не морщить лоб, пытаясь в уме переводить DATETIME в TIMESTAMP &#8211; мы воспользуемся функцией UNIX_TIMESTAMP:</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;">mysql<span style="color: #66cc66;">&gt;</span> <span style="color: #993333; font-weight: bold;">SELECT</span>
    <span style="color: #66cc66;">-&gt;</span> UNIX_TIMESTAMP<span style="color: #66cc66;">&#40;</span><span style="color: #ff0000;">'2011-01-01'</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> <span style="color: #993333; font-weight: bold;">START</span><span style="color: #66cc66;">,</span>
    <span style="color: #66cc66;">-&gt;</span> UNIX_TIMESTAMP<span style="color: #66cc66;">&#40;</span><span style="color: #ff0000;">'2011-12-31'</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> <span style="color: #993333; font-weight: bold;">END</span>;
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">------------+------------+</span>
<span style="color: #66cc66;">|</span> <span style="color: #993333; font-weight: bold;">START</span>      <span style="color: #66cc66;">|</span> <span style="color: #993333; font-weight: bold;">END</span>        <span style="color: #66cc66;">|</span>
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">------------+------------+</span>
<span style="color: #66cc66;">|</span> <span style="color: #cc66cc;">1293825600</span> <span style="color: #66cc66;">|</span> <span style="color: #cc66cc;">1325275200</span> <span style="color: #66cc66;">|</span> 	
<span style="color: #66cc66;">+</span><span style="color: #808080; font-style: italic;">------------+------------+</span>
<span style="color: #cc66cc;">1</span> <span style="color: #993333; font-weight: bold;">ROW</span> <span style="color: #993333; font-weight: bold;">IN</span> <span style="color: #993333; font-weight: bold;">SET</span> <span style="color: #66cc66;">&#40;</span><span style="color: #cc66cc;">0.00</span> sec<span style="color: #66cc66;">&#41;</span></pre></div></div>

<blockquote><p>Оригинал для перевода взял тут: <a href="http://www.phpied.com/random-mysql-date/" target="_blank">http://www.phpied.com/random-mysql-date/</a></p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2011/08/31/random-mysql-date/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>MySQL &#8211; анализ сложных запросов</title>
		<link>http://hudson.su/2011/03/24/mysql-analyzing-complex-queries/</link>
		<comments>http://hudson.su/2011/03/24/mysql-analyzing-complex-queries/#comments</comments>
		<pubDate>Thu, 24 Mar 2011 07:59:56 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[hints]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[optimization]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1795</guid>
		<description><![CDATA[Перевод статьи про анализ сложных MySQL запросов: &#160; Для данной статьи возьмем запрос из статьи про ORDER BY RAND, так он достаточно интересен для рассмотрения различных аспектов запросов. Он содержит: Подзапросы; Объединения; Особые случаи; JOIN&#8217;ы Сортировку ORDER BY + LIMIT Если вы хотите понять как запрос был создан, посмотрите оригинальную статью ORDER BY RAND. Примечание [...]]]></description>
			<content:encoded><![CDATA[<div>
<p>Перевод статьи про анализ сложных MySQL запросов:</p>
<p><span id="more-1795"></span>&nbsp;</p>
<p>Для данной статьи возьмем запрос из статьи про <a href="http://hudson.su/?p=1600" target="_blank">ORDER BY RAND</a>, так он достаточно интересен для рассмотрения различных аспектов запросов. Он содержит:</p>
<ul>
<li>Подзапросы;</li>
<li>Объединения;</li>
<li>Особые случаи;</li>
<li>JOIN&#8217;ы</li>
<li>Сортировку ORDER BY + LIMIT</li>
</ul>
<p>Если вы хотите понять как запрос был создан, посмотрите оригинальную статью <a href="../?p=1600" target="_blank">ORDER BY RAND</a>.</p>
<blockquote><p>Примечание переводчика: вообще говоря в статье на которую ссылается автор, работа окончена до составления именно этого запроса. Возможно это дополнительный хинт для пытливых умов <img src='http://hudson.su/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p></blockquote>
<p>Рассмотрим результаты EXPLAIN запроса:</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;"><span style="color: #66cc66;">&gt;</span> <span style="color: #993333; font-weight: bold;">EXPLAIN</span> EXTENDED
<span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">SELECT</span> r1<span style="color: #66cc66;">.</span>name <span style="color: #993333; font-weight: bold;">FROM</span> random <span style="color: #993333; font-weight: bold;">AS</span> r1
   <span style="color: #993333; font-weight: bold;">JOIN</span> <span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">SELECT</span> <span style="color: #66cc66;">&#40;</span>RAND<span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">*</span>
                <span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">SELECT</span> <span style="color: #993333; font-weight: bold;">MAX</span><span style="color: #66cc66;">&#40;</span>id<span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">FROM</span> random<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> id
        <span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> r2
  <span style="color: #993333; font-weight: bold;">WHERE</span> r1<span style="color: #66cc66;">.</span>id <span style="color: #66cc66;">&gt;=</span> r2<span style="color: #66cc66;">.</span>id
  <span style="color: #993333; font-weight: bold;">ORDER</span> <span style="color: #993333; font-weight: bold;">BY</span> r1<span style="color: #66cc66;">.</span>id <span style="color: #993333; font-weight: bold;">ASC</span>
  <span style="color: #993333; font-weight: bold;">LIMIT</span> <span style="color: #cc66cc;">1</span><span style="color: #66cc66;">&#41;</span>
<span style="color: #993333; font-weight: bold;">UNION</span> <span style="color: #993333; font-weight: bold;">ALL</span>
<span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">SELECT</span> r1<span style="color: #66cc66;">.</span>name <span style="color: #993333; font-weight: bold;">FROM</span> random <span style="color: #993333; font-weight: bold;">AS</span> r1
   <span style="color: #993333; font-weight: bold;">JOIN</span> <span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">SELECT</span> <span style="color: #66cc66;">&#40;</span>RAND<span style="color: #66cc66;">&#40;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">*</span>
                <span style="color: #66cc66;">&#40;</span><span style="color: #993333; font-weight: bold;">SELECT</span> <span style="color: #993333; font-weight: bold;">MAX</span><span style="color: #66cc66;">&#40;</span>id<span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">FROM</span> random<span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> id
        <span style="color: #66cc66;">&#41;</span> <span style="color: #993333; font-weight: bold;">AS</span> r2
  <span style="color: #993333; font-weight: bold;">WHERE</span> r1<span style="color: #66cc66;">.</span>id <span style="color: #66cc66;">&gt;=</span> r2<span style="color: #66cc66;">.</span>id
  <span style="color: #993333; font-weight: bold;">ORDER</span> <span style="color: #993333; font-weight: bold;">BY</span> r1<span style="color: #66cc66;">.</span>id <span style="color: #993333; font-weight: bold;">ASC</span>
  <span style="color: #993333; font-weight: bold;">LIMIT</span> <span style="color: #cc66cc;">1</span><span style="color: #66cc66;">&#41;</span>;</pre></div></div>

<p>И собственно сами результаты:</p>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">+----+--------------+------------+--------+---------------+---------+---------+------+---------+----------+------------------------------+
| id | select_type  | table      | type   | possible_keys | key     | key_len | ref  | rows    | filtered | Extra                        |
+----+--------------+------------+--------+---------------+---------+---------+------+---------+----------+------------------------------+
|  1 | PRIMARY      | &lt;derived2&gt; | system | NULL          | NULL    | NULL    | NULL |       1 |   100.00 | Using filesort               |
|  1 | PRIMARY      | r1         | ALL    | PRIMARY       | NULL    | NULL    | NULL | 1000000 |   100.00 | Using where                  |
|  2 | DERIVED      | NULL       | NULL   | NULL          | NULL    | NULL    | NULL |    NULL |     NULL | No tables used               |
|  3 | SUBQUERY     | NULL       | NULL   | NULL          | NULL    | NULL    | NULL |    NULL |     NULL | Select tables optimized away |
|  4 | UNION        | &lt;derived5&gt; | system | NULL          | NULL    | NULL    | NULL |       1 |   100.00 |                              |
|  4 | UNION        | r1         | range  | PRIMARY       | PRIMARY | 4       | NULL |  153726 |   100.00 | Using where                  |
|  5 | DERIVED      | NULL       | NULL   | NULL          | NULL    | NULL    | NULL |    NULL |     NULL | No tables used               |
|  6 | SUBQUERY     | NULL       | NULL   | NULL          | NULL    | NULL    | NULL |    NULL |     NULL | Select tables optimized away |
| NULL | UNION RESULT | &lt;union1,4&gt; | ALL  | NULL          | NULL    | NULL    | NULL |    NULL |     NULL |                              |
+----+--------------+------------+--------+---------------+---------+---------+------+---------+----------+------------------------------+</pre></div></div>

<p>Запрос состоит из 2х SELECT&#8217;ов и UNION&#8217;а. Результат UNION получается за счет объединения id 1 и 4 (<code>union1,4</code>), что можно увидеть в последней строке.</p>
<p>Id 1 это JOIN между временной таблицей <code>derived2</code> и <code>random</code>. Поскольку id остается неизменным, запрос как правило является частью JOIN. deriverd2 это результат id 2 (<code>DERIVED</code>) с его подзапросом id3.</p>
<p>Мы использовали несколько трюков для того чтобы довести до оптимайзера идею, что ему не надо читать данные с диска при выполнении различных частей запроса:</p>
<ul>
<li><a href="http://dev.mysql.com/doc/refman/5.1/en/where-optimizations.html">MAX(id)</a> может быть оптимизирован в данном случае последним значением индекса. Это единичный поиск, который не требует ни сортировки, ни группировки&#8230; (id 3 and 6)</li>
<li>Мы вынесли рассчет <code>RAND() * MAX(id)</code> в подзапрос, чтобы быть уверенными, что оптимизация MAX() действительно имеет место (вычисляется один раз // прим. пер.) (id 2 and 5)</li>
<li>Мы использовали <a href="http://dev.mysql.com/doc/refman/5.0/en/limit-optimization.html">ORDER BY + LIMIT</a> чтобы оптимайзер выполнил <a href="http://dev.mysql.com/doc/refman/5.1/en/where-optimizations.html">INDEX READ</a> и остановился после обнаружения одной строки. (id 1 and 4)</li>
</ul>
<h2>стоимость выборки</h2>
<p>Мы можем доказать нашу теорию при помощи проверки <a href="http://dev.mysql.com/doc/refman/5.0/en/server-status-variables.html">SHOW STATUS</a>.</p>

<div class="wp_syntax"><div class="code"><pre class="sql" style="font-family:monospace;"><span style="color: #66cc66;">&gt;</span> <span style="color: #993333; font-weight: bold;">FLUSH</span> <span style="color: #993333; font-weight: bold;">STATUS</span>;
<span style="color: #66cc66;">&gt;</span> <span style="color: #993333; font-weight: bold;">SELECT</span> <span style="color: #66cc66;">...</span>;
<span style="color: #66cc66;">&gt;</span> <span style="color: #993333; font-weight: bold;">SHOW</span> <span style="color: #993333; font-weight: bold;">SESSION</span> <span style="color: #993333; font-weight: bold;">STATUS</span>;</pre></div></div>

<p>Команда <code>FLUSH STATUS</code> сбрасывает значения счетчиков для текущей сессии, команда <code>SHOW SESSION STATUS</code> отображает счетчики:</p>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">| Select_full_join                  | 0         |
| Select_full_range_join            | 0         |
| Select_range                      | 2         |
| Select_range_check                | 0         |
| Select_scan                       | 1         |</pre></div></div>

<p>Мы имеем 2 <code>range scans</code> (ORDER BY + LIMIT), по одному для каждой части нашего UNION и 1 <code>table scan</code> для чтения результатов UNION и отправки их на клиент.</p>
<p>Для того чтобы доказать что мы не используем дорогую сортировку &#8211; проверим счетчики <code>Sort</code>-*:</p>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">| Sort_merge_passes                 | 0         |
| Sort_range                        | 0         |
| Sort_rows                         | 0         |
| Sort_scan                         | 0         |</pre></div></div>

<p>Таблица для тестов содержит 1 миллион строк, но мы читаем всего несколько штук из них:</p>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">## наши временные таблицы:
| Created_tmp_tables                | 3         | 2 вторичные таблицы + UNION RESULT
## тут мы пишем только во временные таблицы в памяти
| Handler_write                     | 4         | (1 строка + 1 строка) для вторичных таблиц + 2 строки для результата UNION
## MAX(id) выполняет Index-lookup для _last_ id
| Handler_read_first                | 2         |
## ... и мы ищем одну строку для random-id внутри JOIN
| Handler_read_key                  | 2         |
| Handler_read_next                 | 0         |
| Handler_read_prev                 | 0         |
| Handler_read_rnd                  | 0         |
## все чтения из временных таблиц не индексированы
| Handler_read_rnd_next             | 5         |
## виртуальная стоимость запроса ... &quot;foobars&quot;
| Last_query_cost                   | 10.499000 |</pre></div></div>

<h2>а что насчет &#8220;старого доброго&#8221; ORDER BY RAND?</h2>
<p>Ради прикола посмотрим на классический запрос еще раз:</p>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">&gt; FLUSH STATUS;
&gt; SELECT name FROM random ORDER BY RAND() LIMIT 1;
&gt; SHOW SESSION STATUS;
&gt; EXPLAIN SELECT name FROM random ORDER BY RAND() LIMIT 1;
+----+-------------+--------+------+---------------+------+---------+------+---------+---------------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows    | Extra                           |
+----+-------------+--------+------+---------------+------+---------+------+---------+---------------------------------+
|  1 | SIMPLE      | random | ALL  | NULL          | NULL | NULL    | NULL | 1000000 | Using temporary; Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+---------+---------------------------------+</pre></div></div>

<p>В общем дело было так: ALL rows &#8211; все строки &#8211; сортируются во временной таблице и озвращается лишь одна!</p>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">## цена запроса &quot;несколько&quot; выше :)
| Last_query_cost                   | 210744.186500 |
## у нас используется только одна временная таблица, с намного бОльшим числом строк
| Created_tmp_tables                | 1             |
| Handler_write                     | 1349207       |
## Выполняется один ГИГАНТСКИЙ table-scan для заполнения временной таблицы
| Select_scan                       | 1             |
| Handler_read_rnd_next             | 2349209       |
## и тяжелейшая сортировка
| Sort_merge_passes                 | 19            |
| Sort_range                        | 0             |
| Sort_rows                         | 2             |
| Sort_scan                         | 1             |</pre></div></div>

</div>
<blockquote><p>Источник: <a href="http://jan.kneschke.de/projects/mysql/analyzing-complex-queries/" target="_blank">http://jan.kneschke.de/projects/mysql/analyzing-complex-queries/</a></p>
<p>Перевод как водится мой. Также хочу отметить что не смотря на небольшой объем текста перевод выдался тяжелым с точки зрения понимания. Если видите что я где-то накосячил или не прав &#8211; отпишите в комметариях или в личку. 10nx )</p>
<p>Да, лично для меня статья оказалось полезна в плане использования счетчиков и интерпретации их значений. Надеюсь и вам тоже.
</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2011/03/24/mysql-analyzing-complex-queries/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Скрипт для бэкапа mysql баз</title>
		<link>http://hudson.su/2011/03/03/mysql-backup-script/</link>
		<comments>http://hudson.su/2011/03/03/mysql-backup-script/#comments</comments>
		<pubDate>Thu, 03 Mar 2011 16:14:43 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[backup]]></category>
		<category><![CDATA[bash]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[mysql]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1821</guid>
		<description><![CDATA[Написал для себя простенький скрипт (linux bash) для бэкапа всех баз на одном сервере. Его отличительные особенности: Наличие списка исключений (т.е. бэкапим все кроме&#8230;) Получение списка всех БД из MySQL (не надо добавлять вновь созданные базы к бекапу) Создание директории под бэкап вида &#8220;&#8230;/YYYY/mm/dd/HH-MM/&#8221; Бэкап каждой базы в отдельный файл вида &#8220;YYYY-mm-dd.HH-MM.databasename.backup.sql&#8221; (mysqldump бэкапит все [...]]]></description>
			<content:encoded><![CDATA[<p>Написал для себя простенький скрипт (linux bash) для бэкапа всех баз на одном сервере. Его отличительные особенности:</p>
<ul>
<li>Наличие списка исключений (т.е. бэкапим все кроме&#8230;)</li>
<li>Получение списка всех БД из MySQL (не надо добавлять вновь созданные базы к бекапу)</li>
<li>Создание директории под бэкап вида &#8220;&#8230;/YYYY/mm/dd/HH-MM/&#8221;</li>
<li>Бэкап каждой базы в отдельный файл вида &#8220;YYYY-mm-dd.HH-MM.databasename.backup.sql&#8221; (mysqldump бэкапит все в один файл)</li>
<li>Архивирование бэкапа в тарбол</li>
<li>Зачистка .sql</li>
</ul>
<p>Собственно к написанию скрипта меня сподвигло именно то что mysqldump бэкапит все что ему сказано в один файл (если требуется восстановить одну базу, то попробуй ее выцарапай из общего дампа&#8230;), а создавать отдельную строку для бэкапа всякой новой БД геморно (об этом как минимум надо вспомнить!).</p>
<p>В общем если интересно &#8211; прошу под кат:</p>
<p><span id="more-1821"></span><br />
Собственно сам скрипт</p>

<div class="wp_syntax"><div class="code"><pre class="bash" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">#!/bin/bash</span>
&nbsp;
<span style="color: #666666; font-style: italic;">##</span>
<span style="color: #666666; font-style: italic;"># MySQL backup utility</span>
<span style="color: #666666; font-style: italic;"># @author dmitry.bykadorov@gmail.com</span>
<span style="color: #666666; font-style: italic;">##</span>
&nbsp;
<span style="color: #666666; font-style: italic;"># current date</span>
<span style="color: #007800;">DATE</span>=<span style="color: #000000; font-weight: bold;">`</span><span style="color: #c20cb9; font-weight: bold;">date</span> +<span style="color: #000000; font-weight: bold;">%</span>Y-<span style="color: #000000; font-weight: bold;">%</span>M-<span style="color: #000000; font-weight: bold;">%</span>d<span style="color: #000000; font-weight: bold;">`</span>
&nbsp;
<span style="color: #666666; font-style: italic;"># y/m/d/h/m separately</span>
<span style="color: #007800;">YEAR</span>=<span style="color: #000000; font-weight: bold;">`</span><span style="color: #c20cb9; font-weight: bold;">date</span> +<span style="color: #000000; font-weight: bold;">%</span>Y<span style="color: #000000; font-weight: bold;">`</span>
<span style="color: #007800;">MONTH</span>=<span style="color: #000000; font-weight: bold;">`</span><span style="color: #c20cb9; font-weight: bold;">date</span> +<span style="color: #000000; font-weight: bold;">%</span>m<span style="color: #000000; font-weight: bold;">`</span>
<span style="color: #007800;">DAY</span>=<span style="color: #000000; font-weight: bold;">`</span><span style="color: #c20cb9; font-weight: bold;">date</span> +<span style="color: #000000; font-weight: bold;">%</span>d<span style="color: #000000; font-weight: bold;">`</span>
<span style="color: #007800;">HOURS</span>=<span style="color: #000000; font-weight: bold;">`</span><span style="color: #c20cb9; font-weight: bold;">date</span> +<span style="color: #000000; font-weight: bold;">%</span>H<span style="color: #000000; font-weight: bold;">`</span>
<span style="color: #007800;">MINUTES</span>=<span style="color: #000000; font-weight: bold;">`</span><span style="color: #c20cb9; font-weight: bold;">date</span> +<span style="color: #000000; font-weight: bold;">%</span>M<span style="color: #000000; font-weight: bold;">`</span>
&nbsp;
<span style="color: #666666; font-style: italic;"># database credentials</span>
<span style="color: #007800;">DBUSER</span>=<span style="color: #ff0000;">&quot;&quot;</span>
<span style="color: #007800;">DBPASS</span>=<span style="color: #ff0000;">&quot;&quot;</span>
<span style="color: #007800;">DBHOST</span>=<span style="color: #ff0000;">&quot;&quot;</span>
&nbsp;
<span style="color: #666666; font-style: italic;"># create list of databases</span>
mysql <span style="color: #660033;">-h</span> <span style="color: #007800;">$DBHOST</span> <span style="color: #660033;">-u</span> <span style="color: #007800;">$DBUSER</span> -p<span style="color: #007800;">$DBPASS</span> <span style="color: #660033;">-e</span> <span style="color: #ff0000;">&quot;show databases;&quot;</span> <span style="color: #000000; font-weight: bold;">&gt;</span> <span style="color: #000000; font-weight: bold;">/</span>tmp<span style="color: #000000; font-weight: bold;">/</span>databases.list
&nbsp;
<span style="color: #666666; font-style: italic;"># create backup dir (e.g. ../2011/01/01/04-00)</span>
<span style="color: #007800;">BACKUP_DIR</span>=<span style="color: #000000; font-weight: bold;">/</span>home<span style="color: #000000; font-weight: bold;">/</span>backups<span style="color: #000000; font-weight: bold;">/</span>mysql<span style="color: #000000; font-weight: bold;">/</span><span style="color: #007800;">$YEAR</span><span style="color: #000000; font-weight: bold;">/</span><span style="color: #007800;">$MONTH</span><span style="color: #000000; font-weight: bold;">/</span><span style="color: #007800;">$DAY</span><span style="color: #000000; font-weight: bold;">/</span><span style="color: #007800;">$HOURS</span>-<span style="color: #007800;">$MINUTES</span>
<span style="color: #c20cb9; font-weight: bold;">mkdir</span> <span style="color: #660033;">--parents</span> <span style="color: #660033;">--verbose</span> <span style="color: #007800;">$BACKUP_DIR</span>
&nbsp;
<span style="color: #666666; font-style: italic;"># excludes list (Database is a part of SHOW DATABASES output)</span>
<span style="color: #007800;">EXCLUDES</span>=<span style="color: #7a0874; font-weight: bold;">&#40;</span> <span style="color: #ff0000;">'Database'</span> <span style="color: #ff0000;">'information_schema'</span> <span style="color: #7a0874; font-weight: bold;">&#41;</span>
<span style="color: #007800;">NUM_EXCLUDES</span>=<span style="color: #800000;">${#EXCLUDES[@]}</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">for</span> database <span style="color: #000000; font-weight: bold;">in</span> <span style="color: #000000; font-weight: bold;">`</span><span style="color: #c20cb9; font-weight: bold;">cat</span> <span style="color: #000000; font-weight: bold;">/</span>tmp<span style="color: #000000; font-weight: bold;">/</span>databases.list<span style="color: #000000; font-weight: bold;">`</span>
<span style="color: #000000; font-weight: bold;">do</span>
  <span style="color: #007800;">skip</span>=<span style="color: #000000;">0</span>
&nbsp;
  <span style="color: #7a0874; font-weight: bold;">let</span> <span style="color: #007800;">count</span>=<span style="color: #000000;">0</span>
  <span style="color: #000000; font-weight: bold;">while</span> <span style="color: #7a0874; font-weight: bold;">&#91;</span> <span style="color: #007800;">$count</span> <span style="color: #660033;">-lt</span> <span style="color: #007800;">$NUM_EXCLUDES</span> <span style="color: #7a0874; font-weight: bold;">&#93;</span> ; <span style="color: #000000; font-weight: bold;">do</span>
    <span style="color: #666666; font-style: italic;"># check if this name in excludes list</span>
    <span style="color: #000000; font-weight: bold;">if</span> <span style="color: #7a0874; font-weight: bold;">&#91;</span> <span style="color: #ff0000;">&quot;<span style="color: #007800;">$database</span>&quot;</span> = <span style="color: #800000;">${EXCLUDES[$count]}</span> <span style="color: #7a0874; font-weight: bold;">&#93;</span> ; <span style="color: #000000; font-weight: bold;">then</span>
      <span style="color: #7a0874; font-weight: bold;">let</span> <span style="color: #007800;">skip</span>=<span style="color: #000000;">1</span>
    <span style="color: #000000; font-weight: bold;">fi</span>
    <span style="color: #7a0874; font-weight: bold;">let</span> <span style="color: #007800;">count</span>=<span style="color: #007800;">$count</span>+<span style="color: #000000;">1</span>
  <span style="color: #000000; font-weight: bold;">done</span>
&nbsp;
  <span style="color: #000000; font-weight: bold;">if</span> <span style="color: #7a0874; font-weight: bold;">&#91;</span> <span style="color: #007800;">$skip</span> <span style="color: #660033;">-eq</span> <span style="color: #000000;">0</span> <span style="color: #7a0874; font-weight: bold;">&#93;</span> ; <span style="color: #000000; font-weight: bold;">then</span>
    <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">&quot;++ <span style="color: #007800;">$database</span>&quot;</span>
    <span style="color: #666666; font-style: italic;"># now we can backup current database</span>
    <span style="color: #7a0874; font-weight: bold;">cd</span> <span style="color: #007800;">$BACKUP_DIR</span>
    <span style="color: #007800;">backup_name</span>=<span style="color: #ff0000;">&quot;<span style="color: #007800;">$YEAR</span>-<span style="color: #007800;">$MONTH</span>-<span style="color: #007800;">$DAY</span>.<span style="color: #007800;">$HOURS</span>-<span style="color: #007800;">$MINUTES</span>.<span style="color: #007800;">$database</span>.backup.sql&quot;</span>
    <span style="color: #007800;">backup_tarball_name</span>=<span style="color: #ff0000;">&quot;<span style="color: #007800;">$backup_name</span>.tar.gz&quot;</span>
    <span style="color: #000000; font-weight: bold;">`/</span>usr<span style="color: #000000; font-weight: bold;">/</span>bin<span style="color: #000000; font-weight: bold;">/</span>mysqldump <span style="color: #660033;">-h</span> <span style="color: #ff0000;">&quot;<span style="color: #007800;">$DBHOST</span>&quot;</span> <span style="color: #660033;">--databases</span> <span style="color: #ff0000;">&quot;<span style="color: #007800;">$database</span>&quot;</span> <span style="color: #660033;">-u</span> <span style="color: #ff0000;">&quot;<span style="color: #007800;">$DBUSER</span>&quot;</span> <span style="color: #660033;">--password</span>=<span style="color: #ff0000;">&quot;<span style="color: #007800;">$DBPASS</span>&quot;</span> <span style="color: #000000; font-weight: bold;">&gt;</span> <span style="color: #ff0000;">&quot;<span style="color: #007800;">$backup_name</span>&quot;</span><span style="color: #000000; font-weight: bold;">`</span>
    <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">&quot;   backup <span style="color: #007800;">$backup_name</span>&quot;</span>
    <span style="color: #000000; font-weight: bold;">`/</span>bin<span style="color: #000000; font-weight: bold;">/</span><span style="color: #c20cb9; font-weight: bold;">tar</span> <span style="color: #660033;">-zcf</span> <span style="color: #ff0000;">&quot;<span style="color: #007800;">$backup_tarball_name</span>&quot;</span> <span style="color: #ff0000;">&quot;<span style="color: #007800;">$backup_name</span>&quot;</span><span style="color: #000000; font-weight: bold;">`</span>
    <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">&quot;   compress <span style="color: #007800;">$backup_tarball_name</span>&quot;</span>
    <span style="color: #000000; font-weight: bold;">`/</span>bin<span style="color: #000000; font-weight: bold;">/</span><span style="color: #c20cb9; font-weight: bold;">rm</span> <span style="color: #ff0000;">&quot;<span style="color: #007800;">$backup_name</span>&quot;</span><span style="color: #000000; font-weight: bold;">`</span>
    <span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">&quot;   cleanup <span style="color: #007800;">$backup_name</span>&quot;</span>
  <span style="color: #000000; font-weight: bold;">fi</span>
<span style="color: #000000; font-weight: bold;">done</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">`/</span>bin<span style="color: #000000; font-weight: bold;">/</span><span style="color: #c20cb9; font-weight: bold;">rm</span> <span style="color: #000000; font-weight: bold;">/</span>tmp<span style="color: #000000; font-weight: bold;">/</span>databases.list<span style="color: #000000; font-weight: bold;">`</span>
&nbsp;
<span style="color: #7a0874; font-weight: bold;">echo</span> <span style="color: #ff0000;">&quot;done!&quot;</span></pre></div></div>

<p>Можете его свободно:</p>
<ul>
<li>использовать (никаких гарантий я вам конечно же не даю &#8211; на свой страх и риск )))</li>
<li>модифицировать (например добавить выгрузку бэкапа на другой сервер или на Amazon S3 )))</li>
<li>критиковать ))</li>
</ul>
<p>P.S. Если таки будете использовать &#8211; не забудьте подставить ваши mysql credentials ))</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2011/03/03/mysql-backup-script/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>MySQL: не работает QCache</title>
		<link>http://hudson.su/2010/10/25/mysql-qcache/</link>
		<comments>http://hudson.su/2010/10/25/mysql-qcache/#comments</comments>
		<pubDate>Mon, 25 Oct 2010 08:09:56 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[mysql]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1651</guid>
		<description><![CDATA[Интересная проблема существует в mysql 5.1.*: Query Cache не работает если имя базы данных (схемы) включает в себя дефис. Например: `super-db`. Интересно? Тогда вам под кат: Немного подробностей можно найти тут: http://bugs.mysql.com/bug.php?id=55556 А решение в общем-то имеется весьма топорное &#8211; переименовать базу. Тут у нас есть два варианта: - dump/restore &#8211; т.е. сделать дамп старой [...]]]></description>
			<content:encoded><![CDATA[<p>Интересная проблема существует в mysql 5.1.*: Query Cache не работает если имя базы данных (схемы) включает в себя дефис. Например: `super-db`.</p>
<p>Интересно? Тогда вам под кат:</p>
<p><span id="more-1651"></span></p>
<p>Немного подробностей можно найти тут: <a href="http://bugs.mysql.com/bug.php?id=55556" target="_blank">http://bugs.mysql.com/bug.php?id=55556</a></p>
<p>А решение в общем-то имеется весьма топорное &#8211; переименовать базу. Тут у нас есть два варианта:</p>
<p>- dump/restore &#8211; т.е. сделать дамп старой базы, создать новую и влить туда дамп.</p>
<p>Однако, если вы озаботились кешированием, то база у вас явно не на 1000 строк и такое решение займет прилично времени.</p>
<p>Можно &#8220;скопировать&#8221; mysql базу более простым способом: заменой схем.</p>
<p>Алгоритм  в принципе тоже не сложен:</p>
<p>- <code>CREATE DATABASE `newdb`;</code> // конечно же теперь без дефисов! )</p>
<p>- для каждой таблицы в старой БД выполнить <code>ALTER TABLE `old-db`.`tbl_name` RENAME TO `newdb`.`tbl_name`;</code> (фактически подменяем схему и все).</p>
<p>- ну и <code>DROP DATABASE `old-db`;</code></p>
<p>Замечание: при переименовании таблиц надо быть аккуратнее с триггерами. Я бы предложил перед переименованием делать DROP на триггер и пересоздавать его после переименования в новой схеме &#8211; иначе рискуете нарваться на ошибку.</p>
<p>Как-то так на этот раз&#8230; )</p>
<p>p.s. Спасибо <a href="http://kevit.info" target="_blank">Сергею Караткевичу</a> за &#8220;наводку&#8221; на данный баг )</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/10/25/mysql-qcache/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL оптимизация: ORDER BY RAND()</title>
		<link>http://hudson.su/2010/09/16/mysql-optimizaciya-order-by-rand/</link>
		<comments>http://hudson.su/2010/09/16/mysql-optimizaciya-order-by-rand/#comments</comments>
		<pubDate>Thu, 16 Sep 2010 09:13:03 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[optimization]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1600</guid>
		<description><![CDATA[Лирическое вступление: Довольно часто у нас возникает потребность выборки случайных данных из mysql базы данных. Как правило времени нет и используется самая простая конструкция вида SELECT [что-то] FROM [где-то] WHERE [то и сё] ORDER BY RAND(). Эта конструкция работает на ура. Но вот прототип выезжает на продуктовые сервера и такой милый сердцу запрос вдруг начинает [...]]]></description>
			<content:encoded><![CDATA[<p>Лирическое вступление:</p>
<p>Довольно часто у нас возникает потребность выборки случайных данных из mysql базы данных. Как правило времени нет и используется самая простая конструкция вида SELECT [что-то] FROM [где-то] WHERE [то и сё] ORDER BY RAND(). Эта конструкция работает на ура. Но вот прототип выезжает на продуктовые сервера и такой милый сердцу запрос вдруг начинает выпадать в топы медленных логов. Ниже будут рассмотрены несколько возможностей для оптимизации этого запроса по нарастанию их эффективности:</p>
<p><span id="more-1600"></span>В первых примерах мы полагаем что ID стартует с номера 1 и в ID нет разрывов между 1 и максимальным ID.</p>
<h2>#1. Передать всю работу в приложение</h2>
<p>Мы можем тупо слить всю работу по определению случайного номера в приложение.</p>
<pre>SELECT MAX(id) FROM random;
## генерируем случайный ID в приложении
SELECT name FROM random WHERE id = id_из_приложения</pre>
<p>Так как <code>MAX(id) == COUNT(id)</code> нам всеголишь нужно сгенерировать случайное число между 1 и MAX(id), передать его в запрос к БД и получить свою случайную строку.</p>
<p>Первый SELECT у нас фактически NO-OP и он оптимизирован по самое &#8220;не балуйся&#8221;. Второй запрос это eq_ref по константе и он тоже очень быстр.</p>
<h2>#2. Делаем всю работу на стороне базы данных</h2>
<p>Но настолько ли необходимо делать случайные выборки через приложение? Может стоит вынести &#8220;грязную&#8221; работу на сторону базы данных (<em>прим. пер.: на самом деле первый способ это практически сферический конь в вакууме &#8211; большинство реальных задач будет выходить за рамки его применимости</em>)</p>
<pre># генерируем случайный ID
&gt; SELECT RAND() * MAX(id) FROM random;
+------------------+
| RAND() * MAX(id) |
+------------------+
|  689.37582507297 |
+------------------+</pre>
<p>упс, это число типа double, а нам нужен int</p>
<pre>&gt; SELECT CEIL(RAND() * MAX(id)) FROM random;
+-------------------------+
| CEIL(RAND() * MAX(id))  |
+-------------------------+
|                1000000  |
+-------------------------+
</pre>
<p>уже лучше, но что насчет скорости?</p>
<pre>&gt; EXPLAIN
SELECT CEIL(RAND() * MAX(id)) FROM random;
+----+-------------+-------+-------+---------+-------------+
| id | select_type | table | type  |   rows  | Extra       |
+----+-------------+-------+-------+---------+-------------+
|  1 | SIMPLE      |random | index | 1000000 | Using index |
+----+-------------+-------+-------+---------+-------------+
</pre>
<p>index scan? похоже мы потеряли оптимизацию MAX()</p>
<pre>&gt; EXPLAIN
SELECT CEIL(RAND() * (SELECT MAX(id) FROM random));
+----+-------------+-------+------+------+------------------------------+
| id | select_type | table | type | rows | Extra                        |
+----+-------------+-------+------+------+------------------------------+
|  1 | PRIMARY     | NULL  | NULL | NULL | No tables used               |
|  2 | SUBQUERY    | NULL  | NULL | NULL | Select tables optimized away |
+----+-------------+-------+------+------+------------------------------+
</pre>
<p>Ура! Простой подзапрос возвращает нашу потерянную производительность!</p>
<p>Окей, теперь мы знаем как сгенерировать случайный ID, теперь надо получить и соответствующую ему строку:</p>
<pre>&gt; EXPLAIN
SELECT name
FROM random
WHERE id = (SELECT CEIL(RAND() *
(SELECT MAX(id)
FROM random));
+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows    | Extra                        |
+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+
|  1 | PRIMARY     | random | ALL  | NULL          | NULL | NULL    | NULL | 1000000 | Using where                  |
|  3 | SUBQUERY    | NULL   | NULL | NULL          | NULL | NULL    | NULL |    NULL | Select tables optimized away |
+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+
&gt; show warnings;
+-------+------+------------------------------------------+
| Level | Code | Message                                  |
+-------+------+------------------------------------------+
| Note  | 1249 | Select 2 was reduced during optimization |
+-------+------+------------------------------------------+</pre>
<p>Нет нет нет! Не идите этим путем! Это самый очевидный, но также самый неверный способ! Почему? А вот почему: SELECT в условии WHERE будет выполняться <strong>для каждой</strong> строки! А это число может составлять от 0 до 4091 строки, в зависимости от того насколько вы будете удачливы.</p>
<p>Нам нужен такой способ выборки, при котором мы будем уверены что случайный номер генерируется только однажды:</p>
<pre>SELECT name
FROM random
JOIN
(
  SELECT CEIL(RAND() * (SELECT MAX(id) FROM random)) AS id
) AS r2
USING (id);
+----+-------------+------------+--------+------+------------------------------+
| id | select_type | table      | type   | rows | Extra                        |
+----+-------------+------------+--------+------+------------------------------+
|  1 | PRIMARY     |            | system |    1 |                              |
|  1 | PRIMARY     | random     | const  |    1 |                              |
|  2 | DERIVED     | NULL       | NULL   | NULL | No tables used               |
|  3 | SUBQUERY    | NULL       | NULL   | NULL | Select tables optimized away |
+----+-------------+------------+--------+------+------------------------------+</pre>
<p>Внутренний SELECT генерирует константу в TEMPORARY таблицу и JOIN выбирает одну строку. Великолепно! Нет сортировок, нет вмешательства приложения. Все части запроса оптимизированы.</p>
<h2>#3. Добавляем &#8220;дыры&#8221; в primary key</h2>
<p>Для того чтобы сделать наше предыдущее решение более универсальным, нам нужно учесть возможность &#8220;дыр&#8221; в ID (как если бы вы удалили некоторые строки).</p>
<pre>SELECT name
FROM random AS r1 JOIN
(
 SELECT (RAND() * (SELECT MAX(id) FROM random)) AS id
)
AS r2
WHERE r1.id &gt;= r2.id
ORDER BY r1.id ASC
LIMIT 1;
+----+-------------+------------+--------+------+------------------------------+
| id | select_type | table      | type   | rows | Extra                        |
+----+-------------+------------+--------+------+------------------------------+
|  1 | PRIMARY     |            | system |    1 |                              |
|  1 | PRIMARY     | r1         | range  |  689 | Using where                  |
|  2 | DERIVED     | NULL       | NULL   | NULL | No tables used               |
|  3 | SUBQUERY    | NULL       | NULL   | NULL | Select tables optimized away |
+----+-------------+------------+--------+------+------------------------------+</pre>
<p>Теперь JOIN добавляет все ID который больше или равны нашему случайному значению и мы выбираем ближайшего соседа, если равенство не возможно. НО как только одна строка найдена мы останавливаемся (LIMIT 1). И мы читаем строки в соответствии с индексом (ORDER BY id ASC). Так как мы используем знак &#8220;&gt;=&#8221; вместо строгого равенства &#8220;=&#8221; мы можем избавиться от CEIL и получить тот же резудьтат при немного меньших затратах.</p>
<h2>#4. Равномерное распределение</h2>
<p>Поскольку распределение ID не равномерно, наша выборка на самом деле стала не совсем случайной (<em>прим. пер.: насколько я понимаю, чем больше &#8220;больших&#8221; дыр в ID тем менее равномерно распределение и тем &#8220;менее случайной&#8221; будет выборка</em>).</p>
<pre>&gt; select * from holes;
+----+----------------------------------+----------+
| id | name                             | accesses |
+----+----------------------------------+----------+
|  1 | d12b2551c6cb7d7a64e40221569a8571 |      107 |
|  2 | f82ad6f29c9a680d7873d1bef822e3e9 |       50 |
|  4 | 9da1ed7dbbdcc6ec90d6cb139521f14a |      132 |
|  8 | 677a196206d93cdf18c3744905b94f73 |      230 |
| 16 | b7556d8ed40587a33dc5c449ae0345aa |      481 |
+----+----------------------------------+----------+</pre>
<p>Функция RAND генерирует ID от 9 до 15, которые попадают в &#8220;дыру&#8221; перед 16 и как следствие, 16 выбирается намного чаще чем остальные.</p>
<p>Для этой проблемы не существует нормального решения, но если ваши данные более-менее постоянны, вы можете добавить таблицу для маппинга номера строки с ее ID:</p>
<pre>&gt; create table holes_map (
&gt;   row_id int not NULL primary key,
&gt;   random_id int not null
&gt; );
&gt; SET @id = 0;
&gt; INSERT INTO holes_map SELECT @id := @id + 1, id FROM holes;
&gt; select * from holes_map;
+--------+-----------+
| row_id | random_id |
+--------+-----------+
|      1 |         1 |
|      2 |         2 |
|      3 |         4 |
|      4 |         8 |
|      5 |        16 |
+--------+-----------+</pre>
<p>Идентификатор row_id теперь не содержит дыр и мы опять можем воспользоваться нашим запросом:</p>
<pre>SELECT name
FROM holes
JOIN (
  SELECT r1.random_id
  FROM holes_map AS r1
  JOIN (
    SELECT (RAND() * (SELECT MAX(row_id) FROM holes_map)
  ) AS row_id
) AS r2
WHERE r1.row_id &gt;= r2.row_id
ORDER BY r1.row_id ASC
LIMIT 1) as rows ON (id = random_id);</pre>
<p>После 1000 попыток опять имеем равномерное распределение:</p>
<pre>&gt; select * from holes;
+----+----------------------------------+----------+
| id | name                             | accesses |
+----+----------------------------------+----------+
|  1 | d12b2551c6cb7d7a64e40221569a8571 |      222 |
|  2 | f82ad6f29c9a680d7873d1bef822e3e9 |      187 |
|  4 | 9da1ed7dbbdcc6ec90d6cb139521f14a |      195 |
|  8 | 677a196206d93cdf18c3744905b94f73 |      207 |
| 16 | b7556d8ed40587a33dc5c449ae0345aa |      189 |
+----+----------------------------------+----------+</pre>
<h2>#5. Обслуживание таблицы Holes при помощи триггеров</h2>
<p>Давайте подготовим таблицы как описано ниже:</p>
<pre>DROP TABLE IF EXISTS r2;
CREATE TABLE r2 (
  id SERIAL,
  name VARCHAR(32) NOT NULL UNIQUE
);

DROP TABLE IF EXISTS r2_equi_dist;
CREATE TABLE r2_equi_dist (
  id SERIAL,
  r2_id bigint unsigned NOT NULL UNIQUE
);</pre>
<p>Когда мы что-то меняем в r2, мы хотим чтобы r2_equi_dist также изменялась.</p>
<pre>DELIMITER $$
DROP TRIGGER IF EXISTS tai_r2$$
CREATE TRIGGER tai_r2
AFTER INSERT ON r2 FOR EACH ROW
BEGIN
DECLARE m BIGINT UNSIGNED DEFAULT 1;

SELECT MAX(id) + 1 FROM r2_equi_dist INTO m;
SELECT IFNULL(m, 1) INTO m;
INSERT INTO r2_equi_dist (id, r2_id) VALUES (m, NEW.id);
END$$
DELIMITER ;

DELETE FROM r2;

INSERT INTO r2 VALUES ( NULL, MD5(RAND()) );
INSERT INTO r2 VALUES ( NULL, MD5(RAND()) );
INSERT INTO r2 VALUES ( NULL, MD5(RAND()) );
INSERT INTO r2 VALUES ( NULL, MD5(RAND()) );</pre>
<pre>SELECT * FROM r2;
+----+----------------------------------+
| id | name                             |
+----+----------------------------------+
|  1 | 8b4cf277a3343cdefbe19aa4dabc40e1 |
|  2 | a09a3959d68187ce48f4fe7e388926a9 |
|  3 | 4e1897cd6d326f8079108292376fa7d5 |
|  4 | 29a5e3ed838db497aa330878920ec01b |
+----+----------------------------------+
SELECT * FROM r2_equi_dist;
+----+-------+
| id | r2_id |
+----+-------+
|  1 |     1 |
|  2 |     2 |
|  3 |     3 |
|  4 |     4 |
+----+-------+</pre>
<p>INSERT весьма прост. При DELETE же мы хотим поддерживать equi-dist-id в состоянии &#8220;без дыр&#8221;:</p>
<pre>DELIMITER $$
DROP TRIGGER IF EXISTS tad_r2$$
CREATE TRIGGER tad_r2
AFTER DELETE ON r2 FOR EACH ROW
BEGIN
DELETE FROM r2_equi_dist WHERE r2_id = OLD.id;
UPDATE r2_equi_dist SET id = id - 1 WHERE r2_id &gt; OLD.id;
END$$
DELIMITER ;</pre>
<pre>DELETE FROM r2 WHERE id = 2;

SELECT * FROM r2;
+----+----------------------------------+
| id | name                             |
+----+----------------------------------+
|  1 | 8b4cf277a3343cdefbe19aa4dabc40e1 |
|  3 | 4e1897cd6d326f8079108292376fa7d5 |
|  4 | 29a5e3ed838db497aa330878920ec01b |
+----+----------------------------------+
SELECT * FROM r2_equi_dist;
+----+-------+
| id | r2_id |
+----+-------+
|  1 |     1 |
|  2 |     3 |
|  3 |     4 |
+----+-------+</pre>
<p>UPDATE также прост. Мы должны обслужить лишь Foreign Key constraint:</p>
<pre>DELIMITER $$
DROP TRIGGER IF EXISTS tau_r2$$
CREATE TRIGGER tau_r2
AFTER UPDATE ON r2 FOR EACH ROW
BEGIN
UPDATE r2_equi_dist SET r2_id = NEW.id WHERE r2_id = OLD.id;
END$$
DELIMITER ;</pre>
<pre>UPDATE r2 SET id = 25 WHERE id = 4;

SELECT * FROM r2;
+----+----------------------------------+
| id | name                             |
+----+----------------------------------+
|  1 | 8b4cf277a3343cdefbe19aa4dabc40e1 |
|  3 | 4e1897cd6d326f8079108292376fa7d5 |
| 25 | 29a5e3ed838db497aa330878920ec01b |
+----+----------------------------------+
SELECT * FROM r2_equi_dist;
+----+-------+
| id | r2_id |
+----+-------+
|  1 |     1 |
|  2 |     3 |
|  3 |    25 |
+----+-------+</pre>
<h2>#6. Несколько случайных строк за один раз</h2>
<p>Если вы хотите получить более одной случайной строки за раз вы можете:</p>
<ol>
<li>Выполнить запрос несколько раз</li>
<li>Написать хранимую процедуру, которая выполняет запрос и хранит результат во временной таблице</li>
<li>Выполнить UNION наконец</li>
</ol>
<p><strong>Хранимая процедура:</strong></p>
<p>Хранимая процедура позволяет вам использовать структуры, известные в любом популярном языке программирования:</p>
<ol>
<li>Циклы</li>
<li>Управляющие конструкции</li>
<li>Процедуры</li>
<li>&#8230;</li>
</ol>
<p>Для нашей задачи нам нужен только цикл LOOP:</p>
<pre>DELIMITER $$
DROP PROCEDURE IF EXISTS get_rands$$
CREATE PROCEDURE get_rands(IN cnt INT)
BEGIN
DROP TEMPORARY TABLE IF EXISTS rands;
CREATE TEMPORARY TABLE rands ( rand_id INT );

loop_me: LOOP
IF cnt &amp;lt; 1
  THEN LEAVE loop_me;
END IF;
INSERT INTO rands
  SELECT r1.id
  FROM random AS r1
  JOIN (
    SELECT (RAND() * (SELECT MAX(id) FROM random)) AS id
  ) AS r2
  WHERE r1.id &gt;= r2.id
ORDER BY r1.id ASC
LIMIT 1;

SET cnt = cnt - 1;
END LOOP loop_me;
END$$
DELIMITER ;</pre>
<pre>CALL get_rands(4);
SELECT * FROM rands;
+---------+
| rand_id |
+---------+
|  133716 |
|  702643 |
|  112066 |
|  452400 |
+---------+</pre>
<p>Оставляю в качестве заданий читателю следующие задачки:</p>
<ol>
<li>Динамически составлять запрос, генерируя название временной таблицы (спасибо 2 Evgeny Babin)</li>
<li>Используя UNIQUE index отлавливать нарушения UNIQUE key для удаления возможных дублей.</li>
</ol>
<h2><strong>#7. Быстродействие</strong></h2>
<p>Чтоже стало с быстродействием? У нас есть 3 различные запроса, решающие нашу проблему:</p>
<ol>
<li><code>Q1. ORDER BY RAND()</code></li>
<li><code>Q2. RAND() * MAX(ID)</code></li>
<li><code>Q3. RAND() * MAX(ID) + ORDER BY ID</code></li>
</ol>
<p>Q1 можно оценить как N * log2(N), Q2 и Q3 что-то около константы.</p>
<p>Чтобы получить реальные значения мы провели несколько тестов с числом строк от 100 до миллиона и выполнили каждый запрос 1000 раз.</p>
<pre>    100        1.000      10.000     100.000    1.000.000
Q1  0:00.718s  0:02.092s  0:18.684s  2:59.081s  58:20.000s
Q2  0:00.519s  0:00.607s  0:00.614s  0:00.628s   0:00.637s
Q3  0:00.570s  0:00.607s  0:00.614s  0:00.628s   0:00.637s</pre>
<p>Как вы можете видеть, простой ORDER BY RAND() оптимизирован для выполнения при количестве строк не более 100.</p>
<p>По мотивам <a href="http://jan.kneschke.de/projects/mysql/order-by-rand/" target="blank">http://jan.kneschke.de/projects/mysql/order-by-rand/</a></p>
<p>Coding with fun!</p>
<blockquote><p>P.S. Сам я на практике использовал пока лишь 3е решение ))</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/09/16/mysql-optimizaciya-order-by-rand/feed/</wfw:commentRss>
		<slash:comments>20</slash:comments>
		</item>
		<item>
		<title>i18n данные в mysql</title>
		<link>http://hudson.su/2010/05/12/i18n-dannye-v-mysql/</link>
		<comments>http://hudson.su/2010/05/12/i18n-dannye-v-mysql/#comments</comments>
		<pubDate>Wed, 12 May 2010 06:07:59 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[hints]]></category>
		<category><![CDATA[i18n]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1493</guid>
		<description><![CDATA[Полезный пост от Жени Бабина: http://discover-symfony.blogspot.com/2010/05/symfony-i18n-over-mysql.html. Рассказывается о том как XLIFF переводы (текст в шаблонах) хранить в MySQL. По образу и подобию можно воспользоваться gettext или SQLite. В принципе можно и свой адаптер написать. Спасибо, Женя )]]></description>
			<content:encoded><![CDATA[<p>Полезный пост от Жени Бабина: <a href="http://discover-symfony.blogspot.com/2010/05/symfony-i18n-over-mysql.html" target="_blank">http://discover-symfony.blogspot.com/2010/05/symfony-i18n-over-mysql.html</a>.</p>
<p>Рассказывается о том как XLIFF переводы (текст в шаблонах) хранить в MySQL. По образу и подобию можно воспользоваться gettext или SQLite. В принципе можно и свой адаптер написать.</p>
<p>Спасибо, Женя )</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/05/12/i18n-dannye-v-mysql/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Добавляем в форму sfGuardUser связь many-to-many</title>
		<link>http://hudson.su/2010/04/08/add-many-to-many-relation-in-sfguarduser-form/</link>
		<comments>http://hudson.su/2010/04/08/add-many-to-many-relation-in-sfguarduser-form/#comments</comments>
		<pubDate>Thu, 08 Apr 2010 04:46:38 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[hints]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[snippet]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[symfony forms]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1359</guid>
		<description><![CDATA[В данной заметке я расскажу об использованном мной способе добавления к sfGuardUser связи many-to-many. Начальные условия. Имеется symfony 1.4 проект с установленным sfDoctrineGuardPlugin. Цели. Требуется реализовать для пользователя список ежедневных задач (памятка). Решение. Схема. Создадим в БД следующие таблицы: # таблица ежедневных задач CREATE TABLE IF NOT EXISTS `daily_task` ( `id` int(11) NOT NULL AUTO_INCREMENT, [...]]]></description>
			<content:encoded><![CDATA[<p>В данной заметке я расскажу об использованном мной способе добавления к sfGuardUser связи many-to-many.<br />
<span id="more-1359"></span><br />
<h2>Начальные условия.</h2>
<p>Имеется symfony 1.4 проект с установленным sfDoctrineGuardPlugin.</p>
<h2>Цели.</h2>
<p>Требуется реализовать для пользователя список ежедневных задач (памятка).</p>
<h2>Решение.</h2>
<h3>Схема.</h3>
<p>Создадим в БД следующие таблицы:</p>
<pre># таблица ежедневных задач
CREATE TABLE IF NOT EXISTS `daily_task` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `daily_task_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;

# таблица связей many-to-many задач и sfGuardUser
CREATE TABLE IF NOT EXISTS `user_has_daily_task` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `daily_task_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id_idx` (`user_id`),
  KEY `daily_task_id_idx` (`daily_task_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;

# внешние ключи для связок
ALTER TABLE `user_has_daily_task`
  ADD CONSTRAINT `daily_task_id_idx` FOREIGN KEY (`daily_task_id`) REFERENCES `daily_task` (`id`),
  ADD CONSTRAINT `user_id_idx` FOREIGN KEY (`user_id`) REFERENCES `sf_guard_user` (`id`);</pre>
<p>Этой структуре соответствует следующая схема Doctrine:</p>
<pre># схема для таблицы daily_task
DailyTask:
  connection: doctrine
  tableName: daily_task
  columns:
    id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: true
      autoincrement: true
    daily_task_name:
      type: string(255)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
  relations:
    UserHasDailyTask:
      local: id
      foreign: daily_task_id
      type: many
      onDelete: NO ACTION
      onUpdate: NO ACTION
# схема для таблицы user_has_daily_task
UserHasDailyTask:
  connection: doctrine
  tableName: user_has_daily_task
  columns:
    id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: true
      autoincrement: true
    user_id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
    daily_task_id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
  relations:
    sfGuardUser:
      local: user_id
      foreign: id
      type: one
      onDelete: NO ACTION
      onUpdate: NO ACTION
    DailyTask:
      local: daily_task_id
      foreign: id
      type: one
      onDelete: NO ACTION
      onUpdate: NO ACTION</pre>
<p>Выполняем пересоздание классов Doctrine (модели, формы, фильтры) и очищаем кэш:</p>
<pre>$ ./symfony doctrine:build --all-classes
$ ./symfony cache:clear</pre>
<p>Подготовительную часть на этом можно считать завершенной.</p>
<h3>Админ-генератор sfGuardUser</h3>
<p>Теперь на уровне БД у нас есть привязка ежедневных задач к пользователю sfGuardUser. Для редактирования пользователя sfGuard предусматривает одноименный модуль sfGuardUser с админ-генератором. По-умолчанию этот модуль позволяет редактировать имя, пароль пользователя, а также группы и список прав доступа. Вот они то нас и интересуют. Почему? Потому что эти параметры также представляют из себя связки many-to-many, а значит, если мы хотим добавить ежедневные задачи в редактирование пользователя, то сделать это логично по образу и подобию.</p>
<h3>Модель sfGuardUser</h3>
<p>Для того чтобы sfGuardUser корректно понимал нашу новую связь, необходимо модифицировать сгенерированный класс его модели следующим образом:</p>
<pre># lib/model/doctrine/sfDoctrineGuardPlugin/sfGuardUser.class.php
class sfGuardUser extends PluginsfGuardUser
{
  public function setUp()
  {
    parent::setUp();
    $this-&gt;hasMany('DailyTask', array(
      'refClass'  =&gt; 'UserHasDailyTask',
      'local'     =&gt; 'user_id',
      'foreign'   =&gt; 'daily_task_id'
    ));
  }
}</pre>
<p>Отлично, теперь модель пользователя знает о новой связке ) Теперь можно заняться формой</p>
<h3>Форма sfGuardUserForm</h3>
<p>Теперь мы можем модифицировать форму sfGuardUserForm. Для начала добавим виджет для нашего нового поля (виджет назовем <code>daily_tasks_list</code>):</p>
<pre># lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends BasesfGuardUserAdminForm
{
  public function configure()
  {
    $this-&gt;widgetSchema['daily_tasks_list'] = new sfWidgetFormDoctrineChoice(array(
      'multiple'  =&gt; true,
      'model'     =&gt; 'DailyTask'
    ));

    $this-&gt;validatorSchema['daily_tasks_list'] = new sfValidatorDoctrineChoice(array(
      'multiple'  =&gt; true,
      'model'     =&gt; 'DailyTask',
      'required'  =&gt; false
    ));
}</pre>
<p>Тут желательно еще раз пересобрать все классы и очистить кэш.</p>
<p>Теперь при редактировании пользователя, вы уже сможете увидеть наш новый виджет. Но он еще не пригоден к использованию. Т.е. то что вы выберете, не будет сохранено.</p>
<h3>Сохранение новой связки</h3>
<blockquote><p><strong>Небольшое лирическое отступление, написанное по здравому размышлению</strong>. Если у вас виджет называется точно также как таблица + <code>_list</code> (в нашем случае это будет <code>daily_task_list</code>), то, скорее всего вы уже готовы использовать форму по полной. Я же назвал виджет не так как таблицу (в статье еще опущен префикс таблицы) и поимел последующий геморрой, который все-таки считаю нужным описать полностью.</p></blockquote>
<p>Вот тут нам пригодится аналогия с группами и правами доступа. Если покопошиться в исходниках плагина, выяснится, что для сохранения связей, реализовано два дополнительных метода &#8211; <code>savegroupsList </code>и <code>savepermissionsList</code>. Т.о. мы реализуем наш метод <code>savedailytasksList </code>и переопределяем метод <code>doSave()</code>:</p>
<pre># lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends BasesfGuardUserAdminForm
{
  //...
  /**
   * Сохранение списка ежедневных задач
   */
  public function savedailytasksList($con = null)
  {
    if (!$this-&gt;isValid())
    {
      throw $this-&gt;getErrorSchema();
    }
    if (!isset($this-&gt;widgetSchema['daily_tasks_list']))
    {
      // somebody has unset this widget
      return;
    }
    if (null === $con)
    {
      $con = $this-&gt;getConnection();
    }
    $existing = $this-&gt;object-&gt;DailyTask-&gt;getPrimaryKeys();
    $values = $this-&gt;getValue('daily_tasks_list');
    if (!is_array($values))
    {
      $values = array();
    }
    $unlink = array_diff($existing, $values);
    if (count($unlink))
    {
      $this-&gt;object-&gt;unlink('DailyTask', array_values($unlink));
    }
    $link = array_diff($values, $existing);
    if (count($link))
    {
      $this-&gt;object-&gt;link('DailyTask', array_values($link), true);
    }
  }

  /**
   * Сохранение формы
   */
  protected function doSave($con = null)
  {
    $this-&gt;savedailytasksList($con);
    parent::doSave($con);
  }
}</pre>
<p>Ура! Теперь мы можем сохранять наши задачи! )) Остался один маленький заключительный штришок &#8211; сохранить то мы сохраняем, но при повторном редактировании пользователя сохраненные связи не отображаются. Для этого переопределим метод определения значения по-умолчанию для виджета:</p>
<pre># lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends BasesfGuardUserAdminForm
{
  //...

  /**
   * Значение по-умолчанию для виджета ежедневных задач
   */
  public function updateDefaultsFromObject()
  {
    parent::updateDefaultsFromObject();
    if (isset($this-&gt;widgetSchema['daily_tasks_list']))
    {
      $this-&gt;setDefault('daily_tasks_list', $this-&gt;object-&gt;DailyTask-&gt;getPrimaryKeys());
    }
  }
}</pre>
<p>Ну вот, кажется и все! Пользуйтесь на здоровье )) И ставьте &#8220;плюсики&#8221;, если понравилось! ))</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/04/08/add-many-to-many-relation-in-sfguarduser-form/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Сниппет: инициализация character_set и collation для Doctrine в Symfony</title>
		<link>http://hudson.su/2010/03/22/character-set-and-collation-doctrine-symfony/</link>
		<comments>http://hudson.su/2010/03/22/character-set-and-collation-doctrine-symfony/#comments</comments>
		<pubDate>Mon, 22 Mar 2010 20:21:38 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[snippet]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[web разработка]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1243</guid>
		<description><![CDATA[Для того чтобы Doctrine создавал таблицы в базе данных с правильным набором символов и collation (например utf8 и utf8_general_ci, а не latin1, используемый mysql по умолчанию (например)) необходимо выполнить простую инициализацию: // config/ProjectConfiguration.class.php: public function configureDoctrine(Doctrine_Manager $manager) { $manager-&#62;setCharset( 'utf8' ); $manager-&#62;setCollate( 'utf8_unicode_ci' ); } Спасибо Андрэю Дзягелю из русскоговорящего symfony-коммьюнити )]]></description>
			<content:encoded><![CDATA[<p>Для того чтобы <strong>Doctrine </strong>создавал таблицы в базе данных с правильным набором символов и collation (например <strong>utf8 </strong>и <strong>utf8_general_ci</strong>, а не <strong>latin1</strong>, используемый mysql по умолчанию (например)) необходимо выполнить простую инициализацию:</p>
<pre>
// config/ProjectConfiguration.class.php:
public function configureDoctrine(Doctrine_Manager $manager)
{
  $manager-&gt;setCharset( 'utf8' );
  $manager-&gt;setCollate( 'utf8_unicode_ci' );
}
</pre>
<p>Спасибо <a href="http://develop7.info/" target="_blank">Андрэю Дзягелю</a> из русскоговорящего symfony-коммьюнити )</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/03/22/character-set-and-collation-doctrine-symfony/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>&#8220;CASE WHEN&#8230;&#8221; в Propel без потери hydrate</title>
		<link>http://hudson.su/2009/12/03/case-when-in-propel-without-hydration-lost/</link>
		<comments>http://hudson.su/2009/12/03/case-when-in-propel-without-hydration-lost/#comments</comments>
		<pubDate>Thu, 03 Dec 2009 11:38:21 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[hints]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[propel]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=520</guid>
		<description><![CDATA[Постановка задачи: требуется составить Criteria для Propel, который позволяет выполнить запрос с использованием CASE WHEN и при этом не потерять выбираемые колонки и гидрацию объектов. Собственно казалось бы, используем конструкцию $criteria-&#62;addAsColumn(&#8230;) и все. Но не тут то было, при использовании этого запроса теряются все колонки, выбираемые по-умолчанию Propel&#8217;ом. Итак, вот, пример запроса который нам нужно [...]]]></description>
			<content:encoded><![CDATA[<p>Постановка задачи: требуется составить Criteria для Propel, который позволяет выполнить запрос с использованием <strong>CASE WHEN</strong> и при этом не потерять выбираемые колонки и гидрацию объектов.</p>
<p><span id="more-520"></span></p>
<p>Собственно казалось бы, используем конструкцию <strong>$criteria-&gt;addAsColumn(&#8230;)</strong> и все. Но не тут то было, при использовании этого запроса теряются все колонки, выбираемые по-умолчанию Propel&#8217;ом.</p>
<p>Итак, вот, пример запроса который нам нужно выполнить:</p>
<pre>SELECT
    `table`.`id`,
    `table`.`field1`,
    `table`.`field2`,
    CASE
    WHEN
        `table`.`photo_count` &gt; 0
    THEN 1
    WHEN
        `table`.`photo_count` = 0
    THEN 0
    END as has_photo
FROM
    `table`
WHERE
    `table`.`status_id` = 2
AND
    `table`.`type_id` = 1
...
ORDER BY
    has_photo DESC,
    `table`.`created_at` DESC
</pre>
<p>Ключевые особенности &#8211; дополнительное &#8220;поле&#8221; photo_count принимает значение 0 или 1 и по нему осуществляется сортировка. При этом на выходе мы хотим получить Propel-объекты.</p>
<p>На самом деле решение очень простое:</p>
<pre>PropertyPeer::addSelectColumns( $criteria );
$criteria-&gt;addAsColumn(
    "has_photo" ,
    "CASE WHEN " . TablePeer::PHOTO_COUNT . " &gt; 0 THEN 1 " .
    "WHEN " . TablePeer::PHOTO_COUNT . " = 0 THEN 0 END"
);
$criteria-&gt;addDescendingOrderByColumn( "has_photo" );
</pre>
<p>Ключевой особенностью этого решения является первая строка. Просто, не так ли? Тем не менее чтобы найти решение проблемы потребовался не один час, куча нервов. Собственно ответ был найден в trac&#8217;е propel &#8211; <a href="http://propel.phpdb.org/trac/ticket/643">http://propel.phpdb.org/trac/ticket/643</a></p>
<p>с уважением, отдел по борьбе с propel )))</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2009/12/03/case-when-in-propel-without-hydration-lost/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Volume testing: быстрое наполнение тестовых таблиц в MySQL</title>
		<link>http://hudson.su/2009/11/11/volume-testing-quick-fillin-of-test-tables/</link>
		<comments>http://hudson.su/2009/11/11/volume-testing-quick-fillin-of-test-tables/#comments</comments>
		<pubDate>Wed, 11 Nov 2009 15:14:39 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[hints]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[volume testing]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=376</guid>
		<description><![CDATA[Представим, что вы создаете новое приложение и вам нужно протестировать его на большом объеме данных (volume testing). В этом случае вы можете взять уже готовые данные, или же подготовить их самостоятельно. Если у вас есть набор данных для тестов достаточного объема &#8211; это просто замечательно, но чаще всего данных нужного объема у вас не будет [...]]]></description>
			<content:encoded><![CDATA[<p>Представим, что вы создаете новое приложение и вам нужно протестировать его на большом объеме данных (<strong>volume testing</strong>). В этом случае вы можете взять уже готовые данные, или же подготовить их самостоятельно. Если у вас есть набор данных для тестов достаточного объема &#8211; это просто замечательно, но чаще всего данных нужного объема у вас не будет и вам будет нужен способ для быстрого их создания. Ниже будут перечислены три способа создания больших наборов данных простых типов (чисел, слов, дат).</p>
<p><span id="more-376"></span><strong>Числа</strong></p>
<p>Создавать большие наборы числовых данных совсем не сложно.</p>
<p>Возможно вы захотите написать цикл на вашем любимом языке программирования, или даже цикл в хранимой процедуре на SQL, но это займет намного больше времени, чем предлагаемый ниже подход, который позволит заполнить таблицу за несколько секунд:</p>
<pre>drop table if exists numbers;
create table numbers ( id int not null primary key);

delimiter $$

drop procedure if exists fill_numbers $$
create procedure fill_numbers()
deterministic
begin
  declare counter int default 1;
  insert into numbers values (1);
  while counter &lt; 1000000
  do
      insert into numbers (id)
          select id + counter
          from numbers;
      select count(*) into counter from numbers;
      select counter;
  end while;
end $$
delimiter ;

call fill_numbers();</pre>
<p>Данный способ намного более быстрый, нежели прямая вставка 1&#8217;000&#8217;000 строк. Мы вставляем в таблицу всего одну строку, и потом дублируем таблицу 20 раз, пока не получим 1&#8217;048&#8217;576 строк (2<sup>20</sup>). Эта операция занимает менее 8 секунд на ноутбуке автора, который намного менее мощный, нежели средний сервер (<em>на момент написания статьи &#8211; 2006г. // hudson</em>). Даже если вы не хотите использовать хранимую процедуру, вы можете вручную вставить 1 строку и выполнить 20 раз следующий запрос:</p>
<pre>insert into
    numbers (id)
select
    id + (select count(*) from numbers)
from
    numbers; 

select count(*) from numbers;</pre>
<p>Эта процедура не займет у вас больше 30 секунд.</p>
<p><strong>Слова</strong></p>
<p>Если вам нужно работать с большим объемом уникальных строковых данных, вы опять таки, можете написать программу на любом языке, однако это будет совсем не быстро (процесс вставки достаточно медленный). Наиболее быстрый способ &#8211; загрузить готовый список слов из файла. Все Unix системы содержат списки слов &#8211; от нескольких тысяч, до полумиллиона. Если вдруг у вас такого не оказалось, вы можете скачать его из множества доступных источников или собрать самостоятельно (для начала можете посмотреть здесь: <a href="ftp://ftp.cerias.purdue.edu/pub/dict/" target="_blank">ftp://ftp.cerias.purdue.edu/pub/dict/</a> или здесь <a href="ftp://ftp.ox.ac.uk/pub/wordlists/" target="_blank">ftp://ftp.ox.ac.uk/pub/wordlists/</a>).</p>
<p>И, наконец, покажем как имея на руках файл с примерно полумиллионом слов <code>/usr/share/dict/words</code>, вы сможете наполнить тестовую таблицу:</p>
<pre>drop table if exists words;
create table words (
  id int not null auto_increment primary key,
  t varchar(50) not null
);

load data local infile '/usr/share/dict/words'
  into table words (t);

Query OK, 518584 rows affected (4.94 sec)
Records: 518584  Deleted: 0  Skipped: 0  Warnings: 0

select count(*) from words;
+----------+
| count(*) |
+----------+
|   518584 |
+----------+
1 row in set (0.04 sec)</pre>
<p>Быстро, не правда ли? Но постойте-ка, у нас пока что есть только пол миллиона записей (ну&#8230; чуть больше). Так как нам нужны уникальные слова, мы можем попросить базу данных, изменить порядок букв в уже существующих (<em>reverse()</em>):</p>
<pre>insert into words (t) select reverse(t) from words;
Query OK, 518584 rows affected (3.98 sec)
Records: 518584  Duplicates: 0  Warnings: 0

select count(*) from words;
+----------+
| count(*) |
+----------+
|  1037168 |
+----------+</pre>
<p>Вот так вот! Но мы все еще можем сомневаться, уникальны ли эти слова, так как изменение порядка букв одного слова может превратить его в другое (<em>например mood &lt;&#8211;&gt; doom</em>). Т.о. чтобы считать нашу задачу завершенной, нужно добавить UNIQUE индекс с опцией IGNORE, что позволит исключить дубликаты:</p>
<pre>alter ignore table words add unique key (t);
Query OK, 1037168 rows affected (46.69 sec)
Records: 1037168  Duplicates: 5791  Warnings: 0

select count(*) from words;
+----------+
| count(*) |
+----------+
|  1031377 |
+----------+</pre>
<p>Вот так &#8211; миллион слов, не напрягаясь )</p>
<p><strong>Даты</strong></p>
<p>Наконец, давайте посмотрим, как можно быстро и просто создавать большие наборы дат. Фактически, миллион разных дат вам вряд ли понадобится, т.к. миллион дней это более 2700 лет. Т.о. только даты будут покрывать диапазон от 1000 до 10000 дней, вряд ли больше. Если же вам нужен миллион записей, то впору поговорить не о DATE а о DATETIME с интервалами в часы, минуты или даже секунды. Никто не запрещает вам использовать эту технику для создания сотни уникальных DATE, но при этом иметь в таблице что-то около миллиона записей. Итак, если мы хотим создать записи с минутным интервалом то нужно выполнить следующий код:</p>
<pre>drop table if exists dates;
create table dates (
  id int(11) not null auto_increment primary key,
  dt datetime not NULL
) engine=myisam;

delimiter $$

drop procedure if exists make_dates $$
CREATE PROCEDURE make_dates( max_recs int)
begin
  declare start_dt datetime;
  declare numrecs int default 1;
  set start_dt = date_format( now() - interval max_recs minute, '%Y-%m-%d %H:%i:00');

  insert into dates (dt) values (start_dt );

  while numrecs &lt; max_recs
  do
      insert into dates (dt)
          select dt + interval ( numrecs ) minute
          from dates;
      select count(*) into numrecs from dates;
      select numrecs;
  end while;
end $$

delimiter ;</pre>
<p>Выглядит знакомо, не правда ли? ) Неудивительно, ведь ту же технику мы использовали для заполнения таблицы с числами. Хотя в отличие от чисел, здесь мы использовали число записей в таблице для вычисления интервала в минутах между существующими записями и теми записями, которые будут добавлены в этой итерации. Но мы также дублируем таблицу 20 раз, чтобы получить 1&#8217;000&#8217;000 записей.</p>
<pre>call make_dates( 1000000 );
+---------+
| numrecs |
+---------+
|       2 |
+---------+
1 row in set (0.02 sec)

+---------+
| numrecs |
+---------+
|       4 |
+---------+
1 row in set (0.02 sec)

# ... 16 more counts

+---------+
| numrecs |
+---------+
|  524288 |
+---------+
1 row in set (5.99 sec)

+---------+
| numrecs |
+---------+
| 1048576 |
+---------+
1 row in set (10.18 sec)

select count(*), min(dt), max(dt) from dates;
+----------+---------------------+---------------------+
| count(*) | min(dt)             | max(dt)             |
+----------+---------------------+---------------------+
|  1048576 | 2004-07-07 13:57:00 | 2006-07-05 18:12:00 |
+----------+---------------------+---------------------+</pre>
<p>Данная процедура занимает немного больше времени, нежели для чисел, т.к. возникает оверхед за счет вычисления миллиона интервалов дат, но и в этом случае процедура заняла около 10 секунд, что вполне приемлемо для создания тестовой таблицы.</p>
<p><strong>Заключение</strong></p>
<p><strong> </strong><br />
Конечно же есть и другие техники для формирования больших тестовых наборов данных, но перечисленные три, позволяют делать это быстро и без применения сторонних инструментов.</p>
<p>p.s. как и обещал, это перевод статьи <a href="http://datacharmer.blogspot.com/2006/06/filling-test-tables-quickly.html" target="_blank">http://datacharmer.blogspot.com/2006/06/filling-test-tables-quickly.html</a></p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2009/11/11/volume-testing-quick-fillin-of-test-tables/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

