<?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>code.openark.org &#187; INFORMATION_SCHEMA</title>
	<atom:link href="http://code.openark.org/blog/tag/information_schema/feed" rel="self" type="application/rss+xml" />
	<link>http://code.openark.org/blog</link>
	<description>Blog by Shlomi Noach</description>
	<lastBuildDate>Wed, 01 Feb 2012 08:19:12 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3</generator>
		<item>
		<title>INFORMATION_SCHEMA Optimizations: still crashing my servers</title>
		<link>http://code.openark.org/blog/mysql/information_schema-optimizations-still-crashing-my-servers</link>
		<comments>http://code.openark.org/blog/mysql/information_schema-optimizations-still-crashing-my-servers#comments</comments>
		<pubDate>Mon, 12 Dec 2011 07:35:19 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=4561</guid>
		<description><![CDATA[[Update: need to take more breaks: now NOT crashing my servers! See clarifications below] INFORMATION_SCHEMA Optimizations are meant to make your INFORMATION_SCHEMA queries lighter and safer. For example, if you're going to query the COLUMNS table for just the columns of a single table, then the following: SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='sakila' AND TABLE_NAME='rental' [...]]]></description>
			<content:encoded><![CDATA[<p><strong>[Update</strong>: need to take more breaks: now<strong> NOT</strong> crashing my servers! See clarifications below<strong>]</strong></p>
<p><a href="http://dev.mysql.com/doc/refman/5.1/en/information-schema-optimization.html">INFORMATION_SCHEMA Optimizations</a> are meant to make your <strong>INFORMATION_SCHEMA</strong> queries lighter and safer.</p>
<p>For example, if you're going to query the <strong>COLUMNS</strong> table for just the columns of a single table, then the following:</p>
<blockquote>
<pre>SELECT * FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA='sakila' AND TABLE_NAME='rental'</pre>
</blockquote>
<p>makes for an optimization: specifying a literal on <strong>TABLE_SCHEMA</strong> avoid scanning the directories of other schemata. Specifying a literal on <strong>TABLE_NAME</strong> avoids checking up on other tables. So it's a one-schema-one-table read operation, as opposed to <em>"first read every single column from all and any single schema and table, then return only those I'm interested in"</em>.</p>
<p>Here's the execution plan for the above query:</p>
<blockquote>
<pre>*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: COLUMNS
         type: ALL
possible_keys: NULL
          key: TABLE_SCHEMA,TABLE_NAME
      key_len: NULL
          ref: NULL
         rows: NULL
        Extra: Using where; Open_frm_only; Scanned 0 databases</pre>
</blockquote>
<p>What I tried to do is to read the entire <strong>COLUMNS</strong> table, one schema at a time, one table at a time. I'm good with this taking longer time.</p>
<p>I have a production system on which reads from <strong>COLUMNS</strong> <em>consistently crash the servers</em>. Well, one read at a time can't do harm, right?<span id="more-4561"></span></p>
<p><del>Unfortunately, as the title of this posts reveals, even sequential read of <strong>COLUMNS</strong> using <strong>INFORMATION_SCHEMA</strong> optimization does not help: a minute into the process and the client lost connection. The server crashed.</del></p>
<p><del>I was expecting that table locks would be released, buffers released etc. One at a time, there wouldn't be a congestion of locks, reads, table cache suffocation etc.</del></p>
<p><del>Was actually having high hopes for this to succeed. I have to find a way in which <strong>INFORMATION_SCHEMA</strong> tables are not dangerous.</del></p>
<p>A few hours later, and I have both conclusions and achievements.</p>
<p>There are indeed memory issues with querying from <strong>INFORMATION_SCHEMA</strong> tables. I've found that <strong>VARCHAR(64)</strong> columns can consume <strong>64K</strong> each: I'm reading from large tables of more than <strong>1,000</strong> columns each, while monitoring MySQL's memory consumption. By dividing the increase in memory by the number of rows resulting from a query I sent, and which was for one single columns, I got an almost exact <strong>64K</strong> value per row.</p>
<p>So a query on <strong>INFORMATION_SCHEMA</strong> consumes much more memory than it should. The good news is that this memory is released once the query terminates. So there is no leak into the session memory.</p>
<p>This is combined with a <em>mistake of mine</em> in the way I iterated the tables, such that the problem was amplified: I happened to query much more than I needed, and so got my query's memory bloated. That is to say, I used the <strong>INFORMATION_SCHEMA</strong> optimizations only partly right, and so got only part of the savings it could offer me.</p>
<p>With better pinpointing I'm now actually able to read from <strong>COLUMNS,</strong> without crashing my servers, <em>consistently</em>.</p>
<p>I will further look into the <strong>64K</strong> issue. That in itself still drains a lot of memory: on my <a href="http://code.openark.org/forge/mycheckpoint">mycheckpoint</a> schema tables a singe table read means &gt; <strong>64MB</strong> of query memory down the drain.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/information_schema-optimizations-still-crashing-my-servers/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>More MySQL foreach()</title>
		<link>http://code.openark.org/blog/mysql/more-mysql-foreach</link>
		<comments>http://code.openark.org/blog/mysql/more-mysql-foreach#comments</comments>
		<pubDate>Fri, 02 Dec 2011 13:55:32 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[common_schema]]></category>
		<category><![CDATA[Hack]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[scripts]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=4171</guid>
		<description><![CDATA[In my previous post I've shown several generic use cases for foreach(), a new scripting functionality introduced in common_schema. In this part I present DBA's handy syntax for schema and table operations and maintenance. Confession: while I love INFORMATION_SCHEMA's power, I just hate writing queries against it. It's just so much typing! Just getting the [...]]]></description>
			<content:encoded><![CDATA[<p>In my <a href="http://code.openark.org/blog/mysql/mysql-foreach">previous post</a> I've shown several generic use cases for <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/foreach.html"><em>foreach()</em></a>, a new scripting functionality introduced in <a href="http://code.google.com/p/common-schema/" rel="nofollow">common_schema</a>.</p>
<p>In this part I present DBA's handy syntax for schema and table operations and maintenance.</p>
<p>Confession: while I love <strong>INFORMATION_SCHEMA</strong>'s power, I just <em>hate</em> writing queries against it. It's just so much typing! Just getting the list of tables in a schema makes for this heavy duty query:</p>
<blockquote>
<pre>SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA='sakila' AND TABLE_TYPE='BASE TABLE';</pre>
</blockquote>
<p>When a join is involved this really becomes a nightmare. I think it's cumbersome, and as result, many do not remember the names and meaning of columns, making for <em>"oh, I need to read the manual all over again just to get that query right"</em>. Anyway, that's my opinion.</p>
<p>A <strong>SHOW TABLES</strong> statement is easier to type, but cannot be integrated into a <strong>SELECT</strong> query (though <a href="http://code.openark.org/blog/mysql/reading-results-of-show-statements-on-server-side">we have a partial solution</a> for that, too), and besides, when filtering out the views, the <strong>SHOW</strong> statement becomes almost as cumbersome as the one on <strong>INFORMATION_SCHEMA</strong>.</p>
<p>Which is why <em>foreach()</em> offers handy shortcuts to common iterations on schemata and tables, as follows:</p>
<h4>Use case: iterate all databases</h4>
<blockquote>
<pre>call foreach(<span style="color: #808000;">'schema'</span>, <span style="color: #003366;">'CREATE TABLE ${schema}.event(event_id INT, msg VARCHAR(128))'</span>);</pre>
</blockquote>
<p>In the above we execute a query on each database. Hmmm, maybe not such a good idea to perform this operation on all databases? Let's filter them:</p>
<h4>Use case: iterate databases by name match</h4>
<blockquote>
<pre>call foreach(<span style="color: #808000;">'schema like wordpress_%'</span>, <span style="color: #003366;">'ALTER TABLE ${schema}.wp_posts MODIFY COLUMN comment_author VARCHAR(96) NOT NULL'</span>);</pre>
</blockquote>
<p>The above will only iterate my WordPress databases (I have several of these), performing an <strong>ALTER</strong> on <strong>wp_posts</strong> for each of those databases.<span id="more-4171"></span></p>
<p>I don't have to quote the <em>like</em> expression, but I can, if I wish to.</p>
<p>I can also use a regular expression match:</p>
<blockquote>
<pre>call foreach(<span style="color: #808000;">'schema ~ /^wordpress_[0-9]+$/'</span>, <span style="color: #003366;">'ALTER TABLE ${schema}.wp_posts MODIFY COLUMN comment_author VARCHAR(96) NOT NULL'</span>);</pre>
</blockquote>
<h4>Use case: iterate tables in a specific schema</h4>
<p>Time to upgrade our <strong>sakila</strong> tables to InnoDB's compressed format. We use <strong>$()</strong>, a synonym for <em>foreach()</em>.</p>
<blockquote>
<pre>call $(<span style="color: #808000;">'table in sakila'</span>, <span style="color: #003366;">'ALTER TABLE ${schema}.${table} ENGINE=InnoDB ROW_FORMAT=COMPRESSED'</span>);</pre>
</blockquote>
<p>The above will iterate on tables in <strong>sakila</strong>. I say <em>tables</em>, since it will avoid iterating views (there is still no specific syntax for views iteration). This is done on purpose, as my experience shows there is very little in common between tables and views when it comes to maintenance and operations.</p>
<h4>Use case: iterate tables by name match</h4>
<p>Here's a interesting scenario: you wish to work on all tables matching some name. The naive approach would be to:</p>
<blockquote>
<pre>SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'wp_posts' AND TABLE_TYPE = 'BASE TABLE'</pre>
</blockquote>
<p><em><strong>Wait!</strong></em> Are you aware this may bring your server down? This query will open all databases at once, opening all <strong>.frm</strong> files (though thankfully not data files, since we only check for name and type).</p>
<p>Here's a better approach:</p>
<blockquote>
<pre>call foreach(<span style="color: #808000;">'table like wp_posts'</span>, <span style="color: #003366;">'ALTER TABLE ${schema}.${table} ENGINE=InnoDB'</span>);</pre>
</blockquote>
<p>(There's now FULLTEXT to InnoDB, so the above can make sense in the near future!)</p>
<p>The good part is that <em>foreach()</em> will look for matching tables <em>one database at a time</em>. It will iterate the list of database, then look for matching tables per database, thereby optimizing the query on <strong>INFORMATION_SCHEMA</strong>.</p>
<p>Here, too, I can use regular expressions:</p>
<blockquote>
<pre>call $(<span style="color: #808000;">'table ~ /^wp_.*$/'</span>, <span style="color: #003366;">'ALTER TABLE ${schema}.${table} ENGINE=InnoDB'</span>);</pre>
</blockquote>
<h4>Conclusion</h4>
<p>This is work in the making, but, as someone who maintains a few productions servers, I've already put it to work.</p>
<p>I'm hoping the syntax is easy to comprehend. I know that since I developed it it must be far more intuitive to myself than to others. I've tried to keep close on common syntax and concepts from various programming languages.</p>
<p>I would like to get as much feedback as possible. I have further ideas and thoughts on the direction <a href="http://code.google.com/p/common-schema/">common_schema</a> is taking, but wish take it in small steps. Your feedback is appreciated!</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/more-mysql-foreach/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Reading results of SHOW statements, on server side</title>
		<link>http://code.openark.org/blog/mysql/reading-results-of-show-statements-on-server-side</link>
		<comments>http://code.openark.org/blog/mysql/reading-results-of-show-statements-on-server-side#comments</comments>
		<pubDate>Fri, 25 Nov 2011 19:39:58 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[common_schema]]></category>
		<category><![CDATA[Hacks]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=4138</guid>
		<description><![CDATA[SHOW statements are show stoppers on server side. While clients can get a SHOW statement as a result set just as any normal SELECT, things are not as such on server side. On server side, that is, from within MySQL itself, one cannot: SELECT `Database` FROM (SHOW DATABASES); One cannot: DECLARE show_cursor CURSOR FOR SHOW [...]]]></description>
			<content:encoded><![CDATA[<p><strong>SHOW</strong> statements are show stoppers on server side. While clients can get a <a href="http://dev.mysql.com/doc/refman/5.1/en/show.html">SHOW statement</a> as a result set just as any normal <strong>SELECT</strong>, things are not as such on server side.</p>
<p>On server side, that is, from within MySQL itself, one <em>cannot</em>:</p>
<blockquote>
<pre>SELECT `Database` FROM (SHOW DATABASES);</pre>
</blockquote>
<p>One <em>cannot</em>:</p>
<blockquote>
<pre>DECLARE show_cursor CURSOR FOR SHOW TABLES;</pre>
</blockquote>
<p>One <em>cannot</em>:</p>
<blockquote>
<pre>SHOW TABLES INTO OUTFILE '/tmp/my_file.txt';</pre>
</blockquote>
<p>So it is impossible to get the results with a query; impossible to get the results from a stored routine; impossible to get the results by file reading...</p>
<h4>Bwahaha! A hack!</h4>
<p>For some <strong>SHOW</strong> statements, there is a way around this. I've been banging my head against the wall for weeks now on this. Now I have a partial solution: I'm able to read <strong>SHOW</strong> output for several <strong>SHOW</strong> statements. Namely, those <strong>SHOW</strong> statements <a href="http://dev.mysql.com/doc/refman/5.1/en/extended-show.html">which allow a LIKE or a WHERE</a> clause.</p>
<p>For example, most are familiar with the following syntax:</p>
<blockquote>
<pre>USE mysql;
SHOW TABLE STATUS LIKE 'user';</pre>
</blockquote>
<p>However not so many know that any <strong>SHOW</strong> statement which accepts <strong>LIKE</strong>, can also accept <strong>WHERE</strong>:<span id="more-4138"></span></p>
<blockquote>
<pre>SHOW TABLE STATUS WHERE Name='user'\G
*************************** 1. row ***************************
           Name: user
         Engine: MyISAM
        Version: 10
     Row_format: Dynamic
           Rows: 17
 Avg_row_length: 69
    Data_length: 1184
Max_data_length: 281474976710655
   Index_length: 2048
      Data_free: 0
 Auto_increment: NULL
    Create_time: 2010-10-03 08:23:48
    Update_time: 2011-07-30 19:31:00
     Check_time: NULL
      Collation: utf8_bin
       Checksum: NULL
 Create_options:
        Comment: Users and global privileges</pre>
</blockquote>
<p>It's not just about "<strong>Name</strong>". I can filter using any column I like:</p>
<blockquote>
<pre>SHOW TABLE STATUS WHERE Rows &gt; 1000;
SHOW TABLE STATUS WHERE Rows &gt; 1000 AND Index_length &gt; 65536;</pre>
</blockquote>
<p>etc.</p>
<p>If you've been to my talk on <a href="http://www.percona.com/live/london-2011/session/programmatic-queries-things-you-can-code-with-sql/">Programmatic Queries: things you can code with SQL</a>, you have a good guess as for where I'm taking this.</p>
<h4>Where there's WHERE, there's code</h4>
<p>I can write code within the <strong>WHERE</strong> clause. Specifically, I can work with user defined variables. Shall we cut to the point and provide with an example?</p>
<blockquote>
<pre>mysql&gt; SET @databases := '';

mysql&gt; SHOW DATABASES WHERE (@databases := CONCAT(@databases, `Database`, ',')) IS NULL;

mysql&gt; SELECT @databases;
+-------------------------------------------------------------------+
| @databases                                                        |
+-------------------------------------------------------------------+
| information_schema,common_schema,mycheckpoint,mysql,sakila,world, |
+-------------------------------------------------------------------+</pre>
</blockquote>
<p>Let's discuss the above. We:</p>
<ul>
<li>Set a user variables called <strong>@databases</strong> to an empty text</li>
<li>Iterate through the <strong>SHOW DATABASES</strong> rowset. The <strong>WHERE</strong> clause is always <em>false</em> (the expression is in fact <strong>NOT NULL</strong> for all rows), so rows are not printed out, and we get an empty result set (we're not really interested in a result set here, since there's no way to read it anyhow).</li>
<li>However we do take care to "remember" the value we visit, by concatenating the <strong>`Database`</strong> column value.</li>
<li>We end up with a delimited string of database names. You'll forgive the ending <strong>','</strong>. This is just a simple example, it is of no importance.</li>
</ul>
<h4>Further notes</h4>
<p>What can we do with the concatenated list of database names? Whatever we want to. We can parse it again, <strong>INSERT</strong> it <strong>INTO</strong> some table, save to file, iterate, what have you!</p>
<p>We can wrap the above in a stored routine. Alas, not with a stored function, since the <strong>SHOW</strong> command, although returns with an empty result set, does return with a result set, not allowed withing functions.<strong></strong></p>
<h4>Limitations</h4>
<ul>
<li>Sadly, <strong>SHOW SLAVE STATUS</strong>, <strong>SHOW MASTER LOGS</strong> etc., do not support <strong>LIKE</strong> or <strong>WHERE</strong> syntax. Bummer.</li>
<li>Stored functions, as just mentioned, cannot utilize this hack. Hey, I'm still working on this!</li>
</ul>
<h4>To what use?</h4>
<p>Originally I wanted to avoid the time &amp; locking it takes for <strong>INFORMATION_SCHEMA</strong> queries, such as on <strong>TABLES</strong>, <strong>COLUMNS</strong>, etc. Ironically, in a few days apart I've found <em>another</em> interesting solution (well, two, actually) to manage reads from <strong>INFORMATION_SCHEMA</strong> with less overhead than in normal use. I'll talk about that another time; am about to use this in <a href="http://code.google.com/p/common-schema/" rel="nofollow">common_schema</a>.</p>
<h4>Further notes</h4>
<p>I met <a href="http://rpbouman.blogspot.com/">Roland</a> in <a href="http://www.percona.com/live/london-2011/">London</a>, and he liked the solution. As <a href="http://www.mysqlperformanceblog.com/author/baron/">Baron</a> joined, Roland said: "Baron, do you know Shlomi devised a method to read the output of <strong>SHOW</strong> commands?"</p>
<p>And Baron said: "Without using files? Then a <strong>SHOW</strong> statement can have a <strong>WHERE</strong> clause, in which case you can use a variable", and went on looking for his wife.</p>
<p>And we remained speechless.</p>
<p>[UPDATE: I've manually changed timestamp of this post due to failure in its aggregation in planet.mysql, being a major source of incoming traffic to this site]</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/reading-results-of-show-statements-on-server-side/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>MySQL Global status difference using single query</title>
		<link>http://code.openark.org/blog/mysql/mysql-global-status-difference-using-single-query</link>
		<comments>http://code.openark.org/blog/mysql/mysql-global-status-difference-using-single-query#comments</comments>
		<pubDate>Fri, 12 Aug 2011 16:31:12 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Analysis]]></category>
		<category><![CDATA[common_schema]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3908</guid>
		<description><![CDATA[Have just read MySQL Global status difference using MySQL procedures / functions, by Andres Karlsson. Have commented, but realized I did not provide with a direct answer. In the comment, I suggested checking out a solution based on views, found in common_schema. But the solution in common_schema is split into two views, due to the [...]]]></description>
			<content:encoded><![CDATA[<p>Have just read <a href="http://karlssonondatabases.blogspot.com/2011/08/mysql-global-status-difference-using_12.html">MySQL Global status difference using MySQL procedures / functions</a>, by Andres Karlsson. Have commented, but realized I did not provide with a direct answer. In the comment, I suggested checking out <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/global_status_diff.html">a solution based on views</a>, found in <a rel="nofollow" href="http://code.google.com/p/common-schema/">common_schema</a>. But the solution in <em>common_schema</em> is split into two views, due to the fact views cannot handle derived tables subqueries.</p>
<p>Well, here's a single query to do that: it checks <strong>GLOBAL_STATUS</strong> twice, <strong>10</strong> seconds apart in the following sample. It uses <strong>SLEEP()</strong> to actually wait between the two reads. Yes, you can do that with a query.</p>
<p>The following query shows all <strong>GLOBAL_STATUS</strong> values that have changed during the sample period.<span id="more-3908"></span></p>
<blockquote>
<pre>SELECT STRAIGHT_JOIN
   LOWER(gs0.VARIABLE_NAME) AS variable_name,
   gs0.VARIABLE_VALUE AS variable_value_0,
   gs1.VARIABLE_VALUE AS variable_value_1,
   (gs1.VARIABLE_VALUE - gs0.VARIABLE_VALUE) AS variable_value_diff,
   (gs1.VARIABLE_VALUE - gs0.VARIABLE_VALUE) / 10 AS variable_value_psec,
   (gs1.VARIABLE_VALUE - gs0.VARIABLE_VALUE) * 60 / 10 AS
variable_value_pminute
FROM
   (
     SELECT
       VARIABLE_NAME,
       VARIABLE_VALUE
     FROM
       INFORMATION_SCHEMA.GLOBAL_STATUS
     UNION ALL
     SELECT
       '',
       SLEEP(10)
     FROM DUAL
   ) AS gs0
   JOIN INFORMATION_SCHEMA.GLOBAL_STATUS gs1 USING (VARIABLE_NAME)
WHERE
   gs1.VARIABLE_VALUE != gs0.VARIABLE_VALUE
;
+-----------------------------------+------------------+------------------+---------------------+---------------------+------------------------+
| variable_name                     | variable_value_0 | variable_value_1 | variable_value_diff | variable_value_psec | variable_value_pminute |
+-----------------------------------+------------------+------------------+---------------------+---------------------+------------------------+
| aborted_clients                   | 2210669          | 2210686          |                  17 |                 1.7 |                    102 |
| bytes_received                    | 53259933210      | 53260211104      |              277894 |             27789.4 |                1667364 |
| bytes_sent                        | 351130988015     | 351132884956     |             1896941 |            189694.1 |               11381646 |
| com_change_db                     | 3760546          | 3760584          |                  38 |                 3.8 |                    228 |
| com_delete                        | 6774784          | 6774801          |                  17 |                 1.7 |                    102 |
| com_insert                        | 52743750         | 52744012         |                 262 |                26.2 |                   1572 |
| com_insert_select                 | 13362650         | 13362740         |                  90 |                   9 |                    540 |
| com_select                        | 51722818         | 51723107         |                 289 |                28.9 |                   1734 |
| com_set_option                    | 108564134        | 108564754        |                 620 |                  62 |                   3720 |
| com_show_collations               | 3760530          | 3760568          |                  38 |                 3.8 |                    228 |
| com_show_processlist              | 366078           | 366082           |                   4 |                 0.4 |                     24 |
| com_show_status                   | 366047           | 366051           |                   4 |                 0.4 |                     24 |
| com_show_variables                | 3760535          | 3760573          |                  38 |                 3.8 |                    228 |
| com_update                        | 6271283          | 6271324          |                  41 |                 4.1 |                    246 |
| connections                       | 3781382          | 3781420          |                  38 |                 3.8 |                    228 |
| created_tmp_disk_tables           | 983223           | 983224           |                   1 |                 0.1 |                      6 |
| created_tmp_tables                | 9134044          | 9134126          |                  82 |                 8.2 |                    492 |
| handler_commit                    | 125798040        | 125798688        |                 648 |                64.8 |                   3888 |
| handler_delete                    | 6849562          | 6849578          |                  16 |                 1.6 |                     96 |
| handler_read_first                | 5332451          | 5332498          |                  47 |                 4.7 |                    282 |
| handler_read_key                  | 373910509        | 373912469        |                1960 |                 196 |                  11760 |
| handler_read_next                 | 850122025        | 850170403        |               48378 |              4837.8 |                 290268 |
| handler_read_rnd                  | 255104660        | 255105932        |                1272 |               127.2 |                   7632 |
| handler_read_rnd_next             | 992505444        | 992549948        |               44504 |              4450.4 |                 267024 |
| handler_update                    | 27930283         | 27930465         |                 182 |                18.2 |                   1092 |
| handler_write                     | 2051582925       | 2051602416       |               19491 |              1949.1 |                 116946 |
| innodb_buffer_pool_pages_data     | 77232            | 77243            |                  11 |                 1.1 |                     66 |
| innodb_buffer_pool_pages_dirty    | 626              | 645              |                  19 |                 1.9 |                    114 |
| innodb_buffer_pool_pages_flushed  | 38429812         | 38430032         |                 220 |                  22 |                   1320 |
| innodb_buffer_pool_pages_misc     | 4294922063       | 4294922052       |                 -11 |                -1.1 |                    -66 |
| innodb_buffer_pool_read_requests  | 1933796064       | 1933871603       |               75539 |              7553.9 |                 453234 |
| innodb_buffer_pool_reads          | 11360212         | 11360214         |                   2 |                 0.2 |                     12 |
| innodb_buffer_pool_write_requests | 1074109722       | 1074115394       |                5672 |               567.2 |                  34032 |
| innodb_data_fsyncs                | 5583880          | 5583905          |                  25 |                 2.5 |                    150 |
| innodb_data_read                  | 3339489280       | 3339501568       |               12288 |              1228.8 |                  73728 |
| innodb_data_reads                 | 11796492         | 11796494         |                   2 |                 0.2 |                     12 |
| innodb_data_writes                | 105587582        | 105588145        |                 563 |                56.3 |                   3378 |
| innodb_data_written               | 3721600000       | 3727315968       |             5715968 |            571596.8 |               34295808 |
| innodb_dblwr_pages_written        | 38429812         | 38430032         |                 220 |                  22 |                   1320 |
| innodb_dblwr_writes               | 596503           | 596506           |                   3 |                 0.3 |                     18 |
| innodb_log_write_requests         | 380978894        | 380981368        |                2474 |               247.4 |                  14844 |
| innodb_log_writes                 | 74407604         | 74407990         |                 386 |                38.6 |                   2316 |
| innodb_os_log_fsyncs              | 2310799          | 2310807          |                   8 |                 0.8 |                     48 |
| innodb_os_log_written             | 2905292800       | 2906502656       |             1209856 |            120985.6 |                7259136 |
| innodb_pages_created              | 1341584          | 1341593          |                   9 |                 0.9 |                     54 |
| innodb_pages_read                 | 13117652         | 13117654         |                   2 |                 0.2 |                     12 |
| innodb_pages_written              | 38429812         | 38430032         |                 220 |                  22 |                   1320 |
| innodb_rows_deleted               | 6849552          | 6849568          |                  16 |                 1.6 |                     96 |
| innodb_rows_inserted              | 43787980         | 43788207         |                 227 |                22.7 |                   1362 |
| innodb_rows_read                  | 4289845136       | 4289919560       |               74424 |              7442.4 |                 446544 |
| innodb_rows_updated               | 24119627         | 24119809         |                 182 |                18.2 |                   1092 |
| key_read_requests                 | 41262330         | 41262338         |                   8 |                 0.8 |                     48 |
| open_files                        | 7                | 5                |                  -2 |                -0.2 |                    -12 |
| opened_files                      | 4212920          | 4212924          |                   4 |                 0.4 |                     24 |
| questions                         | 253158874        | 253160331        |                1457 |               145.7 |                   8742 |
| select_full_join                  | 546              | 547              |                   1 |                 0.1 |                      6 |
| select_range                      | 721945           | 721947           |                   2 |                 0.2 |                     12 |
| select_scan                       | 12828865         | 12828989         |                 124 |                12.4 |                    744 |
| sort_range                        | 170971           | 170973           |                   2 |                 0.2 |                     12 |
| sort_rows                         | 255175383        | 255176655        |                1272 |               127.2 |                   7632 |
| sort_scan                         | 534078           | 534080           |                   2 |                 0.2 |                     12 |
| table_locks_immediate             | 142673687        | 142674454        |                 767 |                76.7 |                   4602 |
| threads_cached                    | 7                | 8                |                   1 |                 0.1 |                      6 |
| threads_connected                 | 5                | 10               |                   5 |                 0.5 |                     30 |
| threads_created                   | 840486           | 840509           |                  23 |                 2.3 |                    138 |
+-----------------------------------+------------------+------------------+---------------------+---------------------+------------------------+</pre>
</blockquote>
<p>Some values don't make sense to do difference on (e.g. <strong>threads_connected</strong>), since they present with momentary status and are not incrementing as others (e.g. <strong>threads_created</strong>).</p>
<p>Happy SQLing!</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/mysql-global-status-difference-using-single-query/feed</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Announcing common_schema: common views &amp; routines for MySQL</title>
		<link>http://code.openark.org/blog/mysql/announcing-common_schema-common-views-routines-for-mysql</link>
		<comments>http://code.openark.org/blog/mysql/announcing-common_schema-common-views-routines-for-mysql#comments</comments>
		<pubDate>Wed, 13 Jul 2011 04:25:24 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Analysis]]></category>
		<category><![CDATA[common_schema]]></category>
		<category><![CDATA[Data Types]]></category>
		<category><![CDATA[Development]]></category>
		<category><![CDATA[Indexing]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[InnoDB]]></category>
		<category><![CDATA[Monitoring]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Schema]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[SQL]]></category>
		<category><![CDATA[Stored routines]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3794</guid>
		<description><![CDATA[Today I have released common_schema, a utility schema for MySQL which includes many views and functions, and is aimed to be installed on any MySQL server. What does it do? There are views answering for all sorts of useful information: stuff related to schema analysis, data dimensions, monitoring, processes &#38; transactions, security, internals... There are [...]]]></description>
			<content:encoded><![CDATA[<p>Today I have released <a title="common_schema" href="http://code.openark.org/forge/common_schema">common_schema</a>, a utility schema for MySQL which includes many views and functions, and is aimed to be installed on any MySQL server.</p>
<h4>What does it do?</h4>
<p>There are views answering for all sorts of useful information: stuff related to schema analysis, data dimensions, monitoring, processes &amp; transactions, security, internals... There are basic functions answering for common needs.</p>
<p>Some of the views/routines simply formalize those queries we tend to write over and over again. Others take the place of external tools, answering complex questions via SQL and metadata. Still others help out with SQL generation.</p>
<p>Here are a few highlights:</p>
<ul>
<li>Did you know you can work out <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/global_status_diff_nonzero.html">simple monitoring</a> of your server with a <em>query</em>?  There's a view to do that for you.</li>
<li>How about showing just <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/processlist_top.html">the good parts of the processlist</a>?</li>
<li>Does your schema have <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/redundant_keys.html">redundant keys</a>?</li>
<li>Or InnoDB tables with <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/no_pk_innodb_tables.html">no PRIMARY KEY</a>?</li>
<li>Is AUTO_INCREMENT <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/auto_increment_columns.html">running out of space</a>?</li>
<li>Can I get the SQL statements to <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/sql_foreign_keys.html">generate my FOREIGN KEYs</a>? To drop them?</li>
<li>And can we finally get <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/sql_show_grants.html">SHOW GRANTS for all accounts</a>, and as an <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/sql_grants.html">SQL query</a>?</li>
<li>Ever needed a <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/general_functions.html#crc64">64 bit CRC function</a>?</li>
<li>And aren't you tired of writing the cumbersome SUBSTRING_INDEX(SUBSTRING_INDEX(str, ',', 3), ',', -1)? <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/string_functions.html#split_token">There's an alternative</a>.</li>
</ul>
<p>There's more. Take a look at the <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/introduction.html">common_schema documentation</a> for full listing. And it's evolving: I've got quite a few ideas already for future components.</p>
<p>Some of these views rely on heavyweight INFORMATION_SCHEMA tables. You should be aware of the impact and <a href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/risks.html">risks</a>.</p>
<h4>What do I need to install?</h4>
<p>There's no script or executable file. It's just a schema. The distribution in an SQL file which generates <em>common_schema</em>. Much like a dump file.</p>
<h4><span id="more-3794"></span>What are the system requirements?</h4>
<p>It's just between you and your MySQL. There are currently three distribution files, dedicated for different versions of MySQL (and allowing for increased functionality):</p>
<ul>
<li><strong>common_schema_mysql_51</strong>: fits all MySQL &gt;= 5.1 distributions</li>
<li><strong>common_schema_innodb_plugin</strong>: fits MySQL &gt;= 5.1, with InnoDB plugin + INFORMATION_SCHEMA tables enabled</li>
<li><strong>common_schema_percona_server</strong>: fits Percona Server &gt;= 5.1</li>
</ul>
<p>Refer to the <a rel="nofollow" href="http://common-schema.googlecode.com/svn/trunk/common_schema/doc/html/download.html">documentation</a> for more details.</p>
<h4>What are the terms of use?</h4>
<p><em>common_schema</em> is released under the <a href="http://www.opensource.org/licenses/bsd-license.php">BSD license</a>.</p>
<h4>Where can I download it?</h4>
<p>On the <a href="http://code.google.com/p/common-schema/">common_schema project page</a>. Enjoy it!</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/announcing-common_schema-common-views-routines-for-mysql/feed</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>ROUTINE_PRIVILEGES implementation</title>
		<link>http://code.openark.org/blog/mysql/routine_privileges-implementation</link>
		<comments>http://code.openark.org/blog/mysql/routine_privileges-implementation#comments</comments>
		<pubDate>Wed, 22 Jun 2011 12:14:45 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[Security]]></category>
		<category><![CDATA[Stored routines]]></category>
		<category><![CDATA[Views]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3764</guid>
		<description><![CDATA[Following up on MySQL security: inconsistencies, and on MySQL bug #61596, I was thinking it may take a long time till the non-existent ROUTINE_PRIVILEGES view is implemented. Here's my own implementation of the view. I've followed the somewhat strange conventions used in the *_PRIVILEGES tables in INFORMATION_SCHEMA, where the IS_GRANTABLE is a separate column, although [...]]]></description>
			<content:encoded><![CDATA[<p>Following up on <a title="Link to MySQL security: inconsistencies" rel="bookmark" href="http://code.openark.org/blog/mysql/mysql-security-inconsistencies">MySQL security: inconsistencies</a>, and on <a href="http://bugs.mysql.com/bug.php?id=61596">MySQL bug #61596</a>, I was thinking it may take a long time till the non-existent <strong>ROUTINE_PRIVILEGES</strong> view is implemented. Here's my own implementation of the view.</p>
<p>I've followed the somewhat strange conventions used in the <strong>*_PRIVILEGES</strong> tables in <strong>INFORMATION_SCHEMA</strong>, where the <strong>IS_GRANTABLE</strong> is a separate column, although in <em><del>2nd</del> 1st normal form</em>.</p>
<p>I present it here as a query, using session variables, rather than a view definition:<span id="more-3764"></span></p>
<blockquote>
<pre>SELECT STRAIGHT_JOIN
  CONCAT('\'', User, '\'@\'', Host, '\'') AS GRANTEE,
  NULL AS ROUTINE_CATALOG,
  Db AS ROUTINE_SCHEMA,
  Routine_name AS ROUTINE_NAME,
  Routine_type AS ROUTINE_TYPE,
  UPPER(SUBSTRING_INDEX(SUBSTRING_INDEX(Proc_priv, ',', n+1), ',', -1)) AS PRIVILEGE_TYPE,
  IF(grantable_procs_priv.User IS NULL, 'NO', 'YES') AS IS_GRANTABLE
FROM
  mysql.procs_priv
  CROSS JOIN (SELECT @counter := -1) select_init
  CROSS JOIN (
    SELECT
      @counter := @counter+1 AS n
    FROM
      INFORMATION_SCHEMA.COLLATIONS
    LIMIT 5
  ) numbers
  LEFT JOIN (
      SELECT
        DISTINCT User, Host, Db, Routine_name
      FROM
        mysql.procs_priv
      WHERE
         find_in_set('Grant', Proc_priv) &gt; 0
    ) grantable_procs_priv USING (User, Host, Db, Routine_name)
WHERE
  numbers.n BETWEEN 0 AND CHAR_LENGTH(Proc_priv) - CHAR_LENGTH(REPLACE(Proc_priv, ',', ''))
  AND UPPER(SUBSTRING_INDEX(SUBSTRING_INDEX(Proc_priv, ',', n+1), ',', -1)) != 'GRANT'
ORDER BY
  GRANTEE, ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, n
;</pre>
</blockquote>
<p>It takes <strong>2</strong> views and a table to make this a VIEW rather than a query.</p>
<p>First teaser: the view which represents this query, along with many other interesting diagnostic views, is to take part in a new open source project I'm working on.</p>
<h4>[UPDATE]</h4>
<p>Guess I was in a rush to produce the query. Here's a shorter, cleaner one:</p>
<blockquote>
<pre>SELECT
  CONCAT('\'', User, '\'@\'', Host, '\'') AS GRANTEE,
  NULL AS ROUTINE_CATALOG,
  Db AS ROUTINE_SCHEMA,
  Routine_name AS ROUTINE_NAME,
  Routine_type AS ROUTINE_TYPE,
  UPPER(SUBSTRING_INDEX(SUBSTRING_INDEX(Proc_priv, ',', n+1), ',', -1)) AS PRIVILEGE_TYPE,
  IF(find_in_set('Grant', Proc_priv) &gt; 0, 'YES', 'NO') AS IS_GRANTABLE
FROM
  mysql.procs_priv
  CROSS JOIN (
    SELECT
      @counter := @counter+1 AS n
    FROM
      INFORMATION_SCHEMA.COLLATIONS, (SELECT @counter := -1) select_init
    LIMIT 5
  ) numbers
WHERE
  numbers.n BETWEEN 0 AND CHAR_LENGTH(Proc_priv) - CHAR_LENGTH(REPLACE(Proc_priv, ',', ''))
  AND UPPER(SUBSTRING_INDEX(SUBSTRING_INDEX(Proc_priv, ',', n+1), ',', -1)) != 'GRANT'
ORDER BY
  GRANTEE, ROUTINE_SCHEMA, ROUTINE_NAME, ROUTINE_TYPE, n
;</pre>
</blockquote>
<p>Example output:</p>
<blockquote>
<pre>+--------------------------+-----------------+----------------+----------------------------+--------------+----------------+--------------+
| GRANTEE                  | ROUTINE_CATALOG | ROUTINE_SCHEMA | ROUTINE_NAME               | ROUTINE_TYPE | PRIVILEGE_TYPE | IS_GRANTABLE |
+--------------------------+-----------------+----------------+----------------------------+--------------+----------------+--------------+
| 'other_user'@'localhost' |            NULL | sakila         | film_in_stock              | PROCEDURE    | EXECUTE        | NO           |
| 'other_user'@'localhost' |            NULL | sakila         | film_in_stock              | PROCEDURE    | ALTER ROUTINE  | NO           |
| 'other_user'@'localhost' |            NULL | sakila         | get_customer_balance       | FUNCTION     | EXECUTE        | NO           |
| 'other_user'@'localhost' |            NULL | sakila         | get_customer_balance       | FUNCTION     | ALTER ROUTINE  | NO           |
| 'other_user'@'localhost' |            NULL | sakila         | inventory_held_by_customer | FUNCTION     | EXECUTE        | NO           |
| 'other_user'@'localhost' |            NULL | sakila         | inventory_held_by_customer | FUNCTION     | ALTER ROUTINE  | NO           |
| 'shlomi'@'127.0.0.1'     |            NULL | sakila         | film_in_stock              | PROCEDURE    | EXECUTE        | YES          |
| 'shlomi'@'127.0.0.1'     |            NULL | sakila         | get_customer_balance       | FUNCTION     | EXECUTE        | NO           |
| 'shlomi'@'127.0.0.1'     |            NULL | sakila         | get_customer_balance       | FUNCTION     | ALTER ROUTINE  | NO           |
| 'world_user'@'localhost' |            NULL | sakila         | get_customer_balance       | FUNCTION     | EXECUTE        | YES          |
| 'world_user'@'localhost' |            NULL | sakila         | get_customer_balance       | FUNCTION     | ALTER ROUTINE  | YES          |
+--------------------------+-----------------+----------------+----------------------------+--------------+----------------+--------------+</pre>
</blockquote>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/routine_privileges-implementation/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>MySQL security: inconsistencies</title>
		<link>http://code.openark.org/blog/mysql/mysql-security-inconsistencies</link>
		<comments>http://code.openark.org/blog/mysql/mysql-security-inconsistencies#comments</comments>
		<pubDate>Wed, 22 Jun 2011 06:39:01 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[Security]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3667</guid>
		<description><![CDATA[Doing some work with MySQL security, I've noticed a few inconsistencies. They're mostly not-too-terrible for daily work, except they get in my way right now. The ALL PRIVILEGES inconsistency The preferred way of assigning account privileges in MySQL is by way of using GRANT. With GRANT, one assigns one or more privileges to an account, [...]]]></description>
			<content:encoded><![CDATA[<p>Doing some work with MySQL security, I've noticed a few inconsistencies. They're mostly not-too-terrible for daily work, except they get in my way right now.</p>
<h4>The ALL PRIVILEGES inconsistency</h4>
<p>The preferred way of assigning account privileges in MySQL is by way of using <a href="http://dev.mysql.com/doc/refman/5.1/en/grant.html">GRANT</a>.</p>
<p>With <strong>GRANT</strong>, one assigns one or more privileges to an account, such as <strong>SELECT</strong>, <strong>UPDATE</strong>, <strong>ALTER</strong>, <strong>SUPER</strong> ,etc. Sometimes it makes sense for an account to have complete control over a domain. For example, the <strong>root</strong> account is typically assigned with all privileges. Or, some user may require all possible privileges on a certain schema.</p>
<p>Instead of listing the entire set of privileges, the <strong>ALL PRIVILEGES</strong> meta-privilege can be used. There is a fine issue to notice here; typically this is not a problem, but I see it as a flaw. Assume the following account:</p>
<blockquote>
<pre>root@mysql-5.1.51&gt; GRANT <strong>ALL PRIVILEGES</strong> ON world.* TO 'world_user'@'localhost';

root@mysql-5.1.51&gt; SHOW GRANTS FOR 'world_user'@'localhost';
+---------------------------------------------------------------+
| Grants for world_user@localhost                               |
+---------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'world_user'@'localhost'                |
| GRANT <strong>ALL PRIVILEGES</strong> ON `world`.* TO 'world_user'@'localhost' |
+---------------------------------------------------------------</pre>
</blockquote>
<p>This makes sense. We granted <strong>ALL PRIVILEGES</strong> and we see that the account is granted with <strong>ALL PRIVILEGES</strong>.</p>
<p>Now notice the following:<span id="more-3667"></span></p>
<blockquote>
<pre>root@mysql-5.1.51&gt; GRANT ALTER, ALTER ROUTINE, CREATE, CREATE ROUTINE, CREATE TEMPORARY TABLES, CREATE VIEW, DELETE, DROP, EVENT, EXECUTE, INDEX, INSERT, LOCK TABLES, REFERENCES, SELECT, SHOW VIEW, TRIGGER, UPDATE ON `world`.* TO 'other_user'@'localhost';

root@mysql-5.1.51&gt; SHOW GRANTS FOR 'other_user'@'localhost';
+---------------------------------------------------------------+
| Grants for other_user@localhost                               |
+---------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'other_user'@'localhost'                |
| GRANT <strong>ALL PRIVILEGES</strong> ON `world`.* TO 'other_user'@'localhost' |
+---------------------------------------------------------------+</pre>
</blockquote>
<p>I didn't <em>ask</em> for <strong>ALL PRIVILEGES</strong>. I explicitly listed what I thought should be an account's privileges. It just so happens that these make for the entire set of privileges available on the schema domain.</p>
<p>You might think this is a nice feature, an ease out MySQL provides with. I do not see it this way.</p>
<p>My preferred way of upgrading MySQL version involves exporting and importing of the GRANTs. That is, I do not dump and load the <strong>mysql</strong> system tables, but rather export all the <strong>SHOW GRANTS FOR ...</strong> (e.g. with mk-show-grants), then execute these on the new version. This process was extremely useful on upgrades from <strong>5.0</strong> to <strong>5.1</strong>, where some <strong>mysql</strong> system tables were modified.</p>
<p>Now, consider the case where some new MySQL version introduced a new set of privileges. My <strong>'other_user'@'localhost'</strong> was not created with that set of privileges, nor did I intend it to have them. However, when exporting with <strong>SHOW GRANTS</strong>, the account is said to have <strong>ALL PRIVILEGES</strong>. When executed on the new version, the account will have privileges which I <em>never assigned it</em>.</p>
<p>Typically, this is not an issue. I mean, how many times do I assign an account with the entire set of privileges, yet do not intend it to have all privileges? Nevertheless, this makes for an inconsistency. It is unclear, by way of definition, which privileges are assigned to a user, without knowing the context of the version and the set of privileges per version. It makes for an inconsistency when moving between versions. And right now I'm working on some code which doesn't like these inconsistencies.</p>
<h4>The WITH GRANT OPTION inconsistency</h4>
<p>An account can be granted with the <strong>WITH GRANT OPTION</strong> privilege, which means the account's user can assign her privileges to other accounts. The inconsistency I found is that the <strong>GRANT</strong> mechanism is fuzzy with regard to <strong>GRANT OPTION</strong>, and falsely presents us with the wrong impression.</p>
<p>Let's begin with the bottom line: the <strong>WITH GRANT OPTION</strong> can only be set globally for an account-domain combination. Consider:</p>
<blockquote>
<pre>root@mysql-5.1.51&gt; GRANT INSERT, DELETE, UPDATE ON world.City TO 'gromit'@'localhost';
Query OK, 0 rows affected (0.00 sec)

root@mysql-5.1.51&gt; GRANT SELECT ON world.City TO 'gromit'@'localhost' WITH GRANT OPTION;
Query OK, 0 rows affected (0.00 sec)

root@mysql-5.1.51&gt; SHOW GRANTS FOR 'gromit'@'localhost';
+--------------------------------------------------------------------------------------------------+
| Grants for gromit@localhost                                                                      |
+--------------------------------------------------------------------------------------------------+
| GRANT USAGE ON *.* TO 'gromit'@'localhost'                                                       |
| GRANT <strong>SELECT, INSERT, UPDATE, DELETE</strong> ON `world`.`City` TO 'gromit'@'localhost' <strong>WITH GRANT OPTION</strong> |
+--------------------------------------------------------------------------------------------------+
</pre>
</blockquote>
<p>The syntax of first two queries leads us to believe that we're only providing the <strong>WITH GRANT OPTION</strong> for the <strong>SELECT</strong> privilege. But that is not so: the <strong>WITH GRANT OPTION</strong> is assigned for all privileges on <strong>world.City</strong> to <strong>'gromit'@'localhost'</strong>.</p>
<p>The syntax would be more correct if we were to write something like:</p>
<blockquote>
<pre>GRANT <strong>GRANT_OPTION</strong> ON world.* TO 'gromit'@'localhost';</pre>
</blockquote>
<p>That would make it clear that this privilege does not depend on other privileges set on the specified domain.</p>
<h4>The USAGE inconsistency</h4>
<p>You can <strong>GRANT</strong> the <strong>USAGE</strong> privilege, but you may never <strong>REVOKE</strong> it. To revoke <strong>USAGE</strong> means to <strong>DROP USER</strong>.</p>
<h4>The missing ROUTINES_PRIVILEGES inconsistency</h4>
<p><strong>INFORMATION_SCHEMA</strong> provides with four privileges tables: <strong>USER_PRIVILEGES</strong>, <strong>SCHEMA_PRIVILEGES</strong>, <strong>TABLE_PRIVILEGES</strong>, <strong>COLUMN_PRIVILEGES</strong>, which map well to <strong>mysql</strong>'s <strong>user</strong>, <strong>db</strong>, <strong>tables_priv</strong> and <strong>columns_priv</strong> tables, respectively.</p>
<p>Ahem, which <strong>INFORMATION_SCHEMA</strong> table maps to <strong>mysql.procs_priv</strong>?</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/mysql-security-inconsistencies/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Checking for AUTO_INCREMENT capacity with single query</title>
		<link>http://code.openark.org/blog/mysql/checking-for-auto_increment-capacity-with-single-query</link>
		<comments>http://code.openark.org/blog/mysql/checking-for-auto_increment-capacity-with-single-query#comments</comments>
		<pubDate>Tue, 05 Apr 2011 05:36:56 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Analysis]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[openark kit]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3421</guid>
		<description><![CDATA[Darn! This means oak-show-limits becomes redundant. Am I not supposed to speak about it on my coming presentation? Bad timing! You have AUTO_INCREMENT columns. How far are you pushing the limits? Are you going to run out of AUTO_INCREMENT values soon? Perhaps you wonder whether you should ALTER from INT to BIGINT? The answer is [...]]]></description>
			<content:encoded><![CDATA[<p><em>Darn!</em> This means <a href="http://openarkkit.googlecode.com/svn/trunk/openarkkit/doc/html/oak-show-limits.html">oak-show-limits</a> becomes redundant. Am I not supposed to speak about it on my <a href="http://en.oreilly.com/mysql2011/public/schedule/detail/17155">coming presentation</a>? Bad timing!</p>
<p>You have <strong>AUTO_INCREMENT</strong> columns. How far are you pushing the limits? Are you going to run out of <strong>AUTO_INCREMENT</strong> values soon? Perhaps you wonder whether you should <strong>ALTER</strong> from <strong>INT</strong> to <strong>BIGINT</strong>?</p>
<p>The answer is all there in <strong>INFORMATION_SCHEMA</strong>. The <strong>TABLES</strong> table shows the current <strong>AUTO_INCREMENT</strong> value per table, and the <strong>COLUMNS</strong> table tells us all about a column's data type.</p>
<p>It takes some ugly code to deduce the maximum value per column type, what with signed/unsigned and data type, but then its very simple. Here is the query:<span id="more-3421"></span></p>
<blockquote>
<pre>SELECT
  TABLE_SCHEMA,
  TABLE_NAME,
  COLUMN_NAME,
  DATA_TYPE,
  COLUMN_TYPE,
  IF(
    LOCATE('unsigned', COLUMN_TYPE) &gt; 0,
    1,
    0
  ) AS IS_UNSIGNED,
  (
    CASE DATA_TYPE
      WHEN 'tinyint' THEN 255
      WHEN 'smallint' THEN 65535
      WHEN 'mediumint' THEN 16777215
      WHEN 'int' THEN 4294967295
      WHEN 'bigint' THEN 18446744073709551615
    END &gt;&gt; IF(LOCATE('unsigned', COLUMN_TYPE) &gt; 0, 0, 1)
  ) AS MAX_VALUE,
  AUTO_INCREMENT,
  AUTO_INCREMENT / (
    CASE DATA_TYPE
      WHEN 'tinyint' THEN 255
      WHEN 'smallint' THEN 65535
      WHEN 'mediumint' THEN 16777215
      WHEN 'int' THEN 4294967295
      WHEN 'bigint' THEN 18446744073709551615
    END &gt;&gt; IF(LOCATE('unsigned', COLUMN_TYPE) &gt; 0, 0, 1)
  ) AS AUTO_INCREMENT_RATIO
FROM
  INFORMATION_SCHEMA.COLUMNS
  INNER JOIN INFORMATION_SCHEMA.TABLES USING (TABLE_SCHEMA, TABLE_NAME)
WHERE
  TABLE_SCHEMA NOT IN ('mysql', 'INFORMATION_SCHEMA', 'performance_schema')
  AND EXTRA='auto_increment'
;
</pre>
</blockquote>
<p>There's one row in the result set for each <strong>AUTO_INCREMENT</strong> column. since at most one <strong>AUTO_INCREMENT</strong> column can exist for any given table, each row also identifies a unique table. Resulting columns are mostly self-explanatory, but here's some details on some of the columns:</p>
<ul>
<li><strong>IS_UNSIGNED</strong>: <strong>1</strong> when the column is <strong>UNSIGNED</strong>, <strong>0</strong> otherwise.</li>
<li><strong>MAX_VALUE</strong>: maximum value that can be contained within column.</li>
<li><strong>AUTO_INCREMENT</strong>: current <strong>AUTO_INCREMENT</strong> value for table.</li>
<li><strong>AUTO_INCREMENT_RATIO</strong>: value in the range <strong>[0..1]</strong>, where <strong>1</strong> means "100% full".</li>
</ul>
<p>A sample output:</p>
<blockquote>
<pre>+--------------+------------+--------------+-----------+-----------------------+-------------+------------+----------------+----------------------+
| TABLE_SCHEMA | TABLE_NAME | COLUMN_NAME  | DATA_TYPE | COLUMN_TYPE           | IS_UNSIGNED | MAX_VALUE  | AUTO_INCREMENT | AUTO_INCREMENT_RATIO |
+--------------+------------+--------------+-----------+-----------------------+-------------+------------+----------------+----------------------+
| sakila       | actor      | actor_id     | smallint  | smallint(5) unsigned  |           1 |      65535 |            201 |               0.0031 |
| sakila       | address    | address_id   | smallint  | smallint(5) unsigned  |           1 |      65535 |            606 |               0.0092 |
| sakila       | category   | category_id  | tinyint   | tinyint(3) unsigned   |           1 |        255 |             17 |               0.0667 |
| sakila       | city       | city_id      | smallint  | smallint(5) unsigned  |           1 |      65535 |            601 |               0.0092 |
| sakila       | country    | country_id   | smallint  | smallint(5) unsigned  |           1 |      65535 |            110 |               0.0017 |
| sakila       | customer   | customer_id  | smallint  | smallint(5) unsigned  |           1 |      65535 |            600 |               0.0092 |
| sakila       | film       | film_id      | smallint  | smallint(5) unsigned  |           1 |      65535 |           1001 |               0.0153 |
| sakila       | inventory  | inventory_id | mediumint | mediumint(8) unsigned |           1 |   16777215 |           4582 |               0.0003 |
| sakila       | language   | language_id  | tinyint   | tinyint(3) unsigned   |           1 |        255 |              7 |               0.0275 |
| sakila       | payment    | payment_id   | smallint  | smallint(5) unsigned  |           1 |      65535 |          16050 |               0.2449 |
| sakila       | rental     | rental_id    | int       | int(11)               |           0 | 2147483647 |          16050 |               0.0000 |
| sakila       | staff      | staff_id     | tinyint   | tinyint(3) unsigned   |           1 |        255 |              3 |               0.0118 |
| sakila       | store      | store_id     | tinyint   | tinyint(3) unsigned   |           1 |        255 |              3 |               0.0118 |
+--------------+------------+--------------+-----------+-----------------------+-------------+------------+----------------+----------------------+
</pre>
</blockquote>
<h4>Bonus: free advice on increasing your AUTO_INCREMENT capacity</h4>
<p>Make it <strong>UNSIGNED</strong>. No, really. Check your definitions now.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/checking-for-auto_increment-capacity-with-single-query/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>Thoughts and ideas for Online Schema Change</title>
		<link>http://code.openark.org/blog/mysql/thoughts-and-ideas-for-online-schema-change</link>
		<comments>http://code.openark.org/blog/mysql/thoughts-and-ideas-for-online-schema-change#comments</comments>
		<pubDate>Thu, 07 Oct 2010 08:29:10 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[Development]]></category>
		<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Indexing]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[InnoDB]]></category>
		<category><![CDATA[Open Source]]></category>
		<category><![CDATA[openark kit]]></category>
		<category><![CDATA[Opinions]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[Schema]]></category>
		<category><![CDATA[scripts]]></category>
		<category><![CDATA[Triggers]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=3005</guid>
		<description><![CDATA[Here's a few thoughts on current status and further possibilities for Facebook's Online Schema Change (OSC) tool. I've had these thoughts for months now, pondering over improving oak-online-alter-table but haven't got around to implement them nor even write them down. Better late than never. The tool has some limitations. Some cannot be lifted, some could. [...]]]></description>
			<content:encoded><![CDATA[<p>Here's a few thoughts on current status and further possibilities for Facebook's <a href="http://www.facebook.com/note.php?note_id=430801045932">Online Schema Change</a> (OSC) tool. I've had these thoughts for months now, pondering over improving <a href="../../forge/openark-kit/oak-online-alter-table">oak-online-alter-table</a> but haven't got around to implement them nor even write them down. Better late than never.</p>
<p>The tool has some limitations. Some cannot be lifted, some could. Quoting from the <a href="http://www.facebook.com/notes/mysql-at-facebook/online-schema-change-for-mysql/430801045932">announcement</a> and looking at the code, I add a few comments. I conclude with a general opinion on the tool's abilities.</p>
<h4>"The original table must have PK. Otherwise an error is returned."</h4>
<p>This restriction could be lifted: it's enough that the table has a UNIQUE KEY. My original <em>oak-online-alter-table</em> handled that particular case. As far as I see from their code, the Facebook code would work just as well with any unique key.</p>
<p>However, this restriction is of no real interest. As we're mostly interested in InnoDB tables, and since any InnoDB table <em>should have</em> a PRIMARY KEY, we shouldn't care too much.</p>
<h4>"No foreign keys should exist. Otherwise an error is returned."</h4>
<p>Tricky stuff. With <em>oak-online-alter-table</em>, changes to the original table were immediately reflected in the <em>ghost</em> table. With InnoDB tables, that meant same transaction. And although I never got to update the text and code, there shouldn't be a reason for not using child-side foreign keys (the child-side is the table on which the FK constraint is defined).</p>
<p>The Facebook patch works differently: it captures changes and writes them to a <strong>delta</strong> table,  to be later (asynchronously) analyzed and make for a <em>replay</em> of actions on the <em>ghost</em> table.<span id="more-3005"></span></p>
<p>So in the Facebook code, some cases will lead to undesired behavior. Consider two tables, <strong>country</strong> and <strong>city</strong>, with city holding a RESTRICT/NO ACTION foreign key on <strong>country</strong>'s id. Now consider the scenario:</p>
<ol>
<li>Rows from <strong>city</strong> are DELETEd, where the country Id is Spain's.
<ul>
<li><strong>city</strong>'s ghost table is still unaffected, Spain's cities are still there.</li>
<li>A change is written to the delta table to mark these rows for deletion.</li>
</ul>
</li>
<li>A DELETE is issued on <strong>country</strong>'s Spain record.
<ul>
<li>The DELETE should work, from the user's perspective</li>
<li>But it will fail: city's ghost table has not received the changes yet. There's still matching rows. The NO ACTION constraint will fail the DELETE statement.</li>
</ul>
</li>
</ol>
<p>Now, this does not lead to corruption, just to seemingly unreasonable behavior on the database part. This behavior is probably undesired. NO ACTION constraint won't do.</p>
<p>However, with CASCADE or SET NULL options, there is less of an issue: operations on the parent table (e.g. <strong>country</strong>) cannot fail. We must make sure operations on the ghost table make it consistent with the original table (e.g. <strong>city</strong>).</p>
<p>Consider the following scenario:</p>
<ol>
<li>A new country is created, called "Sleepyland". An INSERT is made to <strong>country</strong>.
<ul>
<li>Both <strong>city</strong> and <strong>city</strong>'s ghost are immediately aware of it.</li>
</ul>
</li>
<li>A new town is created and INSERTed to <strong>city</strong>. The town is called "Naphaven".
<ul>
<li>The change takes time to propagate to <strong>city</strong>'s ghost table.</li>
</ul>
</li>
<li>Meanwhile, we realized we made a mistake. We've been had. There's no such city nor country.
<ol>
<li>We DELETE "Naphaven" from <strong>city</strong>.</li>
<li>We DELETE "Sleepyland" from <strong>country</strong>.</li>
</ol>
<ul>
<li>Note that <strong>city</strong>'s ghost table still hasn't caught up with the changes.</li>
</ul>
</li>
<li>Eventually, the INSERT statement for "Naphaven" reaches <strong>city</strong>'s ghost table.
<ul>
<li>What should happen now? The INSERT cannot succeed.</li>
<li>Will this fail the entire process?</li>
</ul>
</li>
</ol>
<p>Looking at the PHP code, I see that changes written on the <strong>delta</strong> table are blindly replayed on the ghost table.</p>
<p>Since the process is asynchronous, this should not be the case. We can solve the above if we use INSERT IGNORE instead of INSERT. The statement will fail without failing anything else. The row cannot exist, and that's because the original row does not exist anymore.</p>
<p>Unlike a replication corruption, this does not lead to accumulation mistakes. The <strong>replay</strong> is static, somewhat like in <em>binary log format</em>. Changes are <em>just written</em>, regardless of existing data.</p>
<p>I have given this considerable thought, and I can't say I've covered all the possible scenario. However I believe that with proper use of INSERT IGNORE and REPLACE INTO (two statements I heavily relied on with <em>oak-online-alter-table</em>), correctness can be achieved.</p>
<p>There's the small pain of re-generating the foreign key definition on the "ghost" table (<strong>CREATE TABLE LIKE ...</strong> does not copy FK definitions). And since foreign key names are unique, a new name must be picked up. Not pretty, but perfectly doable.</p>
<h4>"No AFTER_{INSERT/UPDATE/DELETE} triggers must exist."</h4>
<p>It would be nicer if MySQL had an ALTER TRIGGER statement. There isn't such statement. If there were such an atomic statement, then we would be able to rewrite the trigger, so as to add our own code to the <em>end of the trigger's code</em>. Yuck. Would be even nicer if we were <a href="http://code.openark.org/blog/mysql/triggers-use-case-compilation-part-ii">allowed to have multiple triggers</a> of same event.</p>
<p>So, we are left with DROP and CREATE triggers. Alas, this makes for a short period where the trigger does not exist. Bad. The easy solution would be to LOCK WRITE the table, but apparently you can't DROP the trigger (*) when the table is locked. Sigh.</p>
<p>(*) Happened to me, apparently to Facebook too; With latest 5.1 (5.1.51) version this actually works. With 5.0 it didn't use to; this needs more checking.</p>
<h4>Use of INFORMATION_SCHEMA</h4>
<p>As with oak-online-alter-table, the OSC checks for triggers, indexes, column by searching on the INFORMATION_SCHEMA tables. This makes for nice SQL for getting the exact listing and types of PRIMARY KEY columns, whether or not AFTER triggers exist, and so on.</p>
<p>I've always considered this to be the weak part of <a href="http://code.openark.org/forge/openark-kit">openark-kit</a>, that it relies on INFORMATION_SCHEMA so much. It's easier, it's cleaner, it's even <em>more correct</em> to work that way -- but it just puts too much locks. I think Baron Schwartz (and now Daniel Nichter) did amazing work on analyzing table schemata by parsing the SHOW CREATE TABLE and other SHOW commands regex-wise with <a href="http://www.maatkit.org/">Maatkit</a>. It's a crazy work! Had I written <em>openark-kit</em> in Perl, I would have just import their code. But I'm too <span style="text-decoration: line-through;">lazy</span> busy to do the conversion from Perl to Python, and rewrite that code, what with all the debugging.</p>
<p>OSC is written in PHP. Again, much conversion work. I think performance-wise this is an important step to make.</p>
<h4>A word for the critics</h4>
<p>Finally, a word for the critics. I've read some Facebook/MySQL bashing comments and wish to relate.</p>
<p>In his <a href="http://www.theregister.co.uk/2010/09/21/facebook_online_schema_change_for_mysql/">interview to The Register</a>, Mark Callaghan gave the example that "Open Schema Change lets the company update indexes without user downtime, according to Callaghan".</p>
<p>PostgreSQL was mentioned for being able to add index with only read locks taken, or being able to do the work with no locks using CREATE INDEX CONCURRENTLY. I wish MySQL had that feature! Yes, MySQL has a lot to improve upon, and the latest PostgreSQL 9.0 brings valuable new features. (Did I make it clear I have no intention of bashing PostgreSQL? If not, please re-read this paragraph until convinced).</p>
<p>Bashing related to the notion of MySQL being so poor that Facebook used an even poorer mechanism to work out the ALTER TABLE.</p>
<p>Well, allow me to add a few words: the CREATE INDEX is by far not the only thing you can achieve with OSC (although it may be Facebook's major concern). You should be able to:</p>
<ul>
<li>Add columns</li>
<li>Drop columns</li>
<li>Convert character sets</li>
<li>Modify column types</li>
<li>Add partitioning</li>
<li>Reorganize partitioning</li>
<li>Compress the table</li>
<li>Otherwise changing table format</li>
<li>Heck, you could even modify the storage engine! (To other transactional engine)</li>
</ul>
<p>These are giant steps. How easy would it be to write these down into the database? It only takes a few weeks time to work out a working solution with reasonable limitations, just using the resources the MySQL server provides you with. The <a href="http://www.facebook.com/MySQLatFacebook">MySQL@Facebook team</a> should be given credit for that.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/thoughts-and-ideas-for-online-schema-change/feed</wfw:commentRss>
		<slash:comments>8</slash:comments>
		</item>
		<item>
		<title>Verifying GROUP_CONCAT limit without using variables</title>
		<link>http://code.openark.org/blog/mysql/verifying-group_concat-limit-without-using-variables</link>
		<comments>http://code.openark.org/blog/mysql/verifying-group_concat-limit-without-using-variables#comments</comments>
		<pubDate>Thu, 10 Jun 2010 07:16:14 +0000</pubDate>
		<dc:creator>shlomi</dc:creator>
				<category><![CDATA[MySQL]]></category>
		<category><![CDATA[Configuration]]></category>
		<category><![CDATA[INFORMATION_SCHEMA]]></category>
		<category><![CDATA[SQL]]></category>

		<guid isPermaLink="false">http://code.openark.org/blog/?p=2534</guid>
		<description><![CDATA[I have a case where I must know if group_concat_max_len is at its default value (1024), which means there are some operation I cannot work out. I've ranted on this here. Normally, I would simply: However, I am using views, where session variables are not allowed. Using a stored function can do the trick, but [...]]]></description>
			<content:encoded><![CDATA[<p>I have a case where I must know if <strong>group_concat_max_len</strong> is at its default value (<strong>1024</strong>), which means there are some operation I cannot work out. I've ranted on this <a href="http://code.openark.org/blog/mysql/those-oversized-undersized-variables-defaults">here</a>.</p>
<p>Normally, I would simply:</p>
<blockquote><pre class="brush: sql; title: ; notranslate">
SELECT @@group_concat_max_len
</pre>
</blockquote>
<p>However, I am using views, where session variables are not allowed. Using a stored function can <a href="http://code.openark.org/blog/mysql/views-better-performance-with-condition-pushdown">do the trick</a>, but I wanted to avoid stored routines. So here's a very simple test case: is the current <strong>group_concat_max_len</strong> long enough or not? I'll present the long version and the short version.</p>
<h4>The long version</h4>
<blockquote><pre class="brush: sql; title: ; notranslate">
SELECT
  CHAR_LENGTH(
    GROUP_CONCAT(
      COLLATION_NAME SEPARATOR ''
    )
  )
FROM
  INFORMATION_SCHEMA.COLLATIONS;
</pre>
</blockquote>
<p>If the result is <strong>1024</strong>, we are in a bad shape. I happen to know that the total length of collation names is above <strong>1800</strong>, and so it is trimmed down. Another variance of the above query would be:<span id="more-2534"></span></p>
<blockquote><pre class="brush: sql; title: ; notranslate">
SELECT
  CHAR_LENGTH(
    GROUP_CONCAT(
      COLLATION_NAME SEPARATOR ''
    )
  ) = SUM(CHAR_LENGTH(COLLATION_NAME))
    AS group_concat_max_len_is_long_enough
FROM
  INFORMATION_SCHEMA.COLLATIONS;

+-------------------------------------+
| group_concat_max_len_is_long_enough |
+-------------------------------------+
|                                   0 |
+-------------------------------------+
</pre>
</blockquote>
<p>The <strong>COLLATIONS</strong>, <strong>CHARACTER_SETS</strong> or <strong>COLLATION_CHARACTER_SET_APPLICABILITY</strong> tables provide with known to exist variables (assuming you did not compile MySQL with particular charsets). It's possible to <strong>CONCAT</strong>, <strong>UNION</strong> or <strong>JOIN</strong> columns and tables to detect longer than <strong>1800</strong> characters in <strong>group_concat_max_len</strong>. I admit this is becoming ugly, so let's move on.</p>
<h4>The short version</h4>
<p>Don't want to rely on existing tables? Not sure what values to expect? Look at this:</p>
<blockquote><pre class="brush: sql; title: ; notranslate">
SELECT CHAR_LENGTH(GROUP_CONCAT(REPEAT('0', 1025))) FROM DUAL
</pre>
</blockquote>
<p><strong>GROUP_CONCAT</strong> doesn't really care about the number of rows. In the above example, I'm using a single row (retrieved from the <strong>DUAL</strong> virtual table), making sure it is long enough. Type in any number in place of <strong>1025</strong>, and you have a metric for your <strong>group_concat_max_len</strong>.</p>
<blockquote><pre class="brush: sql; title: ; notranslate">
SELECT
  CHAR_LENGTH(GROUP_CONCAT(REPEAT('0', 32768))) &gt;= 32768 As group_concat_max_len_is_long_enough
FROM
  DUAL;
+-------------------------------------+
| group_concat_max_len_is_long_enough |
+-------------------------------------+
|                                   0 |
+-------------------------------------+
</pre>
</blockquote>
<p>The above makes a computation with <strong>REPEAT</strong>. One can replace this with a big constant.</p>
]]></content:encoded>
			<wfw:commentRss>http://code.openark.org/blog/mysql/verifying-group_concat-limit-without-using-variables/feed</wfw:commentRss>
		<slash:comments>10</slash:comments>
		</item>
	</channel>
</rss>

