<?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>PlanetMysql.ru - информация о СУБД MySQL &#187; 5.6</title>
	<atom:link href="http://planetmysql.ru/category/5-6/feed/" rel="self" type="application/rss+xml" />
	<link>http://planetmysql.ru</link>
	<description>Блог о самой популярной СУБД MySQL</description>
	<lastBuildDate>Fri, 10 Feb 2012 20:01:53 +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>Better Controlling MySQL Memory Usage</title>
		<link>http://blog.wl0.org/2012/01/better-controlling-mysql-memory-usage/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=better-controlling-mysql-memory-usage</link>
		<comments>http://blog.wl0.org/2012/01/better-controlling-mysql-memory-usage/#comments</comments>
		<pubDate>Tue, 24 Jan 2012 23:32:53 +0000</pubDate>
		<dc:creator>Simon Mudd</dc:creator>
				<category><![CDATA[5.1]]></category>
		<category><![CDATA[5.5]]></category>
		<category><![CDATA[5.6]]></category>
		<category><![CDATA[Databases]]></category>
		<category><![CDATA[memory]]></category>
		<category><![CDATA[memory allocation]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[performance]]></category>
		<category><![CDATA[resource control]]></category>
		<category><![CDATA[swapping]]></category>

		<guid isPermaLink="false">http://blog.wl0.org/?p=512</guid>
		<description><![CDATA[MySQL, like a lot of other software, has many knobs you can tweak. Most of these knobs may affect behaviour, but more importantly most affect the memory usage of the server, so getting these settings right is very important.
Most of MySQL&#8217;s memory is really used just as a cache, in one form or another, information that otherwise is on disk. So ensuring you have as large a cache as possible is important. However, making these memory sizes too large will trigger the server to start swapping and possibly can cause it to crash or cause the kernel to kill the process when it runs out of memory.  So that&#8217;s something we want to avoid.
Certain settings affect memory allocation on a per connection/thread basis, being bounded by thread_cache_size and max_connections.  If you configure for the worst behaviour (max_connections) you may end up not actually using all the memory you have available, memory which normally could be used for other purposes.
Recently a server I managed was configured incorrectly with a large sort_buffer_size (4M to 256M) and larger read_buffer_size (4M to 20M).  The change in configuration on first glance looks quite innocent, and not noticing that these are per-connection settings this got rolled out. max_connections on this server was set to 1000, while normally there were ~40 connections of which only a few were active. The mysqld memory footprint on startup looked fine. In fact under normal usage it also worked fine. However spiky load suddenly changed this nice behaviour: as configured mysqld ramped up the thread count and hence memory usage, resulting in swapping and finally server death&#8230;
The fault of course was mine for not noticing, but this has been an issue with MySQL forever.  Most other RDBMSes manage memory slightly differently: you define how much memory you want to use (maximum), this is optinally locked into memory, and further requests for different buffers are taken from this global pool.  That is much safer, avoids unexpected swapping, or memory over-allocation, but does raise the question of what happens when a memory request can not be honoured.
Initially I would expect two different behaviours: either

kill a thread whose memory can not be allocated, or
wait a certain time to allocate that memory, after which it gets killed anyway.

Option (2) is probably saner, and possibly some sort of deadlock detection can kick if in all threads are waiting for memory, perhaps killing the younger thread, or thread which has done least work first. Possibly there are other better ways of doing this?
I can imagine that changing MySQL&#8217;s current behaviour to do something like this could be quite hard, especially as ideally the engines would also use the same memory management mechanisms, but I see this as being a good thing and would make MySQL more robust, especially under load, which is after all what counts.  Of course this will not happen in today&#8217;s 5.5 GA version, or tomorrow&#8217;s 5.6 version which is probably likely to appear some time this year. That&#8217;s a major change. It would be nice if Oracle look at this for 5.7 as a way of ensuring that when resource usage does come under pressure MySQL does not go heads up, but attempts to use the allocated resources as best as possible.
In the meantime what would help would be:

better documentation so we can see clearly how all mysql memory is allocated. There are several web pages commenting ways to calculate this, but certainly no definitive guide.
The InnoDB engine&#8217;s documentation talks about memory usage and most people think that the innodb_buffer_pool_size is the main setting. yet read further and there&#8217;s talk of an overhead of perhaps 1/8th. I have recently been playing with innodb_buffer_pool_instances settings &#62; 1 (using values in the range of 20-40) and am inclined to think that this increases that overhead somewhat more, yet there&#8217;s no documentation on this and whether my guess is right or not. Please InnoDB developers improve your documentation, if only to prove me wrong.
Ideally some tools to tell you if you server is possibly misconfigured. Coming from a Sybase environment I&#8217;d be tempted to suggest a stored procedure in the mysql database which can tell you total memory usage and how it is broken down as doing this with a single SELECT is going to be tricky.

Then once that is done consider adding some extra variable to enable total memory usage to be controlled. I made a feature request for this at http://bugs.mysql.com/?id=64108. If you think this feature might interest you please let Oracle know.]]></description>
			<content:encoded><![CDATA[<p>MySQL, like a lot of other software, has many knobs you can tweak. Most of these knobs may affect behaviour, but more importantly most affect the memory usage of the server, so getting these settings right is very important.</p>
<p>Most of MySQL&#8217;s memory is really used just as a cache, in one form or another, information that otherwise is on disk. So ensuring you have as large a cache as possible is important. However, making these memory sizes too large will trigger the server to start swapping and possibly can cause it to crash or cause the kernel to kill the process when it runs out of memory.  So that&#8217;s something we want to avoid.</p>
<p>Certain settings affect memory allocation on a per connection/thread basis, being bounded by thread_cache_size and max_connections.  If you configure for the worst behaviour (max_connections) you may end up not actually using all the memory you have available, memory which normally could be used for other purposes.</p>
<p>Recently a server I managed was configured incorrectly with a large sort_buffer_size (4M to 256M) and larger read_buffer_size (4M to 20M).  The change in configuration on first glance looks quite innocent, and not noticing that these are per-connection settings this got rolled out. max_connections on this server was set to 1000, while normally there were ~40 connections of which only a few were active. The mysqld memory footprint on startup looked fine. In fact under normal usage it also worked fine. However spiky load suddenly changed this <em>nice behaviour</em>: as configured mysqld ramped up the thread count and hence memory usage, resulting in swapping and finally server death&#8230;</p>
<p>The fault of course was mine for not noticing, but this has been an issue with MySQL forever.  Most other RDBMSes manage memory slightly differently: you define how much memory you want to use (maximum), this is optinally locked into memory, and further requests for different <em>buffers</em> are taken from this <em>global pool</em>.  That is much safer, avoids unexpected swapping, or memory over-allocation, but does raise the question of what happens when a memory request can not be honoured.</p>
<p>Initially I would expect two different behaviours: either</p>
<ol>
<li>kill a thread whose memory can not be allocated, or</li>
<li>wait a certain time to allocate that memory, after which it gets killed anyway.</li>
</ol>
<p>Option (2) is probably saner, and possibly some sort of deadlock detection can kick if in all threads are waiting for memory, perhaps killing the <em>younger thread</em>, or thread which has done least work first. Possibly there are other better ways of doing this?</p>
<p>I can imagine that changing MySQL&#8217;s current behaviour to do something like this could be quite hard, especially as ideally the engines would also use the same memory management mechanisms, but I see this as being a good thing and would make MySQL more robust, especially under load, which is after all what counts.  Of course this will not happen in today&#8217;s 5.5 GA version, or tomorrow&#8217;s 5.6 version which is probably likely to appear some time this year. That&#8217;s a major change. It would be nice if Oracle look at this for 5.7 as a way of ensuring that when resource usage does come under pressure MySQL does not go heads up, but attempts to use the allocated resources as best as possible.</p>
<p>In the meantime what would help would be:</p>
<ul>
<li>better documentation so we can see clearly how all mysql memory is allocated. There are several web pages commenting ways to calculate this, but certainly no definitive guide.</li>
<li>The InnoDB engine&#8217;s documentation talks about memory usage and most people think that the innodb_buffer_pool_size is the main setting. yet read further and there&#8217;s talk of an overhead of perhaps 1/8th. I have recently been playing with innodb_buffer_pool_instances settings &gt; 1 (using values in the range of 20-40) and am inclined to think that this increases that overhead somewhat more, yet there&#8217;s no documentation on this and whether my guess is right or not. Please InnoDB developers improve your documentation, if only to prove me wrong.</li>
<li>Ideally some tools to tell you if you server is possibly misconfigured. Coming from a Sybase environment I&#8217;d be tempted to suggest a stored procedure in the mysql database which can tell you total memory usage and how it is broken down as doing this with a single SELECT is going to be tricky.</li>
</ul>
<p>Then once that is done consider adding some extra variable to enable total memory usage to be controlled. I made a feature request for this at <a title="Provide a global maximum_memory setting to limit unexpected memory usage" href="http://bugs.mysql.com/?id=64108" >http://bugs.mysql.com/?id=64108</a>. If you think this feature might interest you please let Oracle know.</p><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=31791&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=31791&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://planetmysql.ru/2012/01/25/better-controlling-mysql-memory-usage/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL 5.6.4 Development Milestone Now Available!</title>
		<link>http://blogs.oracle.com/MySQL/entry/mysql_5_6_4_development?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=mysql-5-6-4-development-milestone-now-available</link>
		<comments>http://blogs.oracle.com/MySQL/entry/mysql_5_6_4_development#comments</comments>
		<pubDate>Tue, 20 Dec 2011 17:44:19 +0000</pubDate>
		<dc:creator>Rob Young</dc:creator>
				<category><![CDATA[5.6]]></category>
		<category><![CDATA[InnoDB]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[optimizer]]></category>
		<category><![CDATA[Replication]]></category>

		<guid isPermaLink="false">http://blogs.oracle.com/MySQL/entry/mysql_5_6_4_development</guid>
		<description><![CDATA[I am pleased to announce that the MySQL Database 5.6.4 development milestone release (&#34;DMR&#34;) is now available for download&#160;(select the Development Release tab). MySQL 5.6.4 includes all 5.5 production-ready features and provides an aggreation of all of the new features that have been released in earlier 5.6 DMRs.&#160; 5.6.4 adds many bug fixes and more new &#34;early and often&#34; enhancements that are development and system QA complete and ready for Community evaluation and feedback.&#160; You can get the complete rundown of all the new 5.6.4 specific features here.For those following the progression of the 5.6 DMRs as the trains leave the station, you should bookmark these MySQL Engineering development team specific blogs: 
   
    InnoDB Development Team Blog 
    Specific enhancements in the MySQL 5.6.4 include:InnoDB Full-Text Search Better scaling of read-only workloadsInnoDB 5.6.4 supports databases with 4k and 8k page sizesImproving InnoDB memory usage 
    Other engineering team blogs include:&#160; 
    Marc Alff's Blog on Performance Schema 
     Andew Morgan's MySQL HA Blog 
    Mats Kindahl's Blog on MySQL Replication&#160; 
    Lars Thalmann's Blog on MySQL Replication, Backup, Connectors 
    Chuck Bell's Blog on MySQL GPL Utilities 
   
  You can also track the thought and innovation leaders on the MySQL Optimizer and the new Optimizer specific improvements in 5.6.4 by following the MySQL Optimizer Team member blogs: 
   
    Gleb Shcepa 
    Jorgen Loland 
    Guilhem Bichot 
    Oystein Grovlen 
    Olav Sandsta 
    Tor Didriksen 
   
  And of course you can follow others on the Optimizer team and all of MySQL Engineering teams by bookmarking/subscribing to PlanetMySQL. We look forward to your feedback on MySQL 5.6.4, so please download your copy now and help us make a better MySQL.&#160;  
  As always, a sincere thanks for your continued support of MySQL!&#160;&#160;&#160;]]></description>
			<content:encoded><![CDATA[<p>I am pleased to announce that the MySQL Database 5.6.4 development milestone release (&quot;DMR&quot;) is now available for <a href="http://dev.mysql.com/downloads/mysql/#downloads" title="MySQL Downloads">download</a>&nbsp;(select the Development Release tab). MySQL 5.6.4 includes all 5.5 production-ready features and provides an aggreation of all of the new features that have been released in <a href="http://dev.mysql.com/doc/refman/5.6/en/news-5-6-x.html" title="MySQL 5.6 DMR release notes">earlier 5.6 DMRs</a>.&nbsp; 5.6.4 adds many bug fixes and more new &quot;early and often&quot; enhancements that are development and system QA complete and ready for Community evaluation and feedback.&nbsp; You can get the complete rundown of all the new 5.6.4 specific features <a href="http://dev.mysql.com/doc/refman/5.6/en/news-5-6-4.html" title="MySQL 5.6.4 release notes">here</a>.<br /><br />For those following the progression of the 5.6 DMRs as the trains leave the station, you should bookmark these MySQL Engineering development team specific blogs:</p> 
  <ul> 
    <li><a href="http://blogs.innodb.com/wp/">InnoDB Development Team Blog</a></li> 
    <p>Specific enhancements in the MySQL 5.6.4 include:</p><a href="http://blogs.innodb.com/wp/?p=1474">InnoDB Full-Text Search</a> <br /><br /><a href="http://blogs.innodb.com/wp/?p=1500">Better scaling of read-only workloads</a><br /><br /><a href="http://blogs.innodb.com/wp/?p=1484">InnoDB 5.6.4 supports databases with 4k and 8k page sizes</a><br /><br /><a href="http://blogs.innodb.com/wp/?p=1510">Improving InnoDB memory usage</a><br /><br /> 
    <p>Other engineering team blogs include:&nbsp;</p> 
    <li><a href="http://marcalff.blogspot.com/2011/12/mysql-564-performance-schema.html">Marc Alff's Blog on Performance Schema</a></li> 
    <li><a href="http://marcalff.blogspot.com/"></a> <a href="http://www.clusterdb.com/category/mysql-replication/">Andew Morgan's MySQL HA Blog</a></li> 
    <li><a href="http://www.clusterdb.com/category/mysql-replication/"></a><a href="http://mysqlmusings.blogspot.com/">Mats Kindahl's Blog on MySQL Replication&nbsp;</a></li> 
    <li><a href="http://larsthalmann.blogspot.com/">Lars Thalmann's Blog on MySQL Replication, Backup, Connectors</a></li> 
    <li><a href="http://larsthalmann.blogspot.com/"></a><a href="http://drcharlesbell.blogspot.com/">Chuck Bell's Blog on MySQL GPL Utilities</a></li> 
  </ul> 
  <p>You can also track the thought and innovation leaders on the MySQL Optimizer and the new Optimizer specific improvements in 5.6.4 by following the MySQL Optimizer Team member blogs:<br /></p> 
  <ul> 
    <li><a href="http://glebshchepa.blogspot.com/">Gleb Shcepa</a></li> 
    <li><a href="http://jorgenloland.blogspot.com/">Jorgen Loland</a></li> 
    <li><a href="http://guilhembichot.blogspot.com/">Guilhem Bichot</a></li> 
    <li><a href="http://oysteing.blogspot.com/">Oystein Grovlen</a></li> 
    <li><a href="http://olavsandstaa.blogspot.com/">Olav Sandsta</a></li> 
    <li><a href="http://didrikdidrik.blogspot.com/">Tor Didriksen</a></li> 
  </ul> 
  <p>And of course you can follow others on the Optimizer team and all of MySQL Engineering teams by bookmarking/subscribing to <a href="http://www.planetmysql.org/">PlanetMySQL.</a> <br /><br />We look forward to your feedback on MySQL 5.6.4, so please download your copy now and help us make a better MySQL.&nbsp; </p> 
  <p>As always, a sincere thanks for your continued support of MySQL!&nbsp;&nbsp;&nbsp; <br /></p> 
  <p> </p> 
  <p><br /> </p><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=31375&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=31375&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://planetmysql.ru/2011/12/20/mysql-5-6-4-development-milestone-now-available/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL 5.6.4 Development Milestone Now Available!</title>
		<link>http://blogs.oracle.com/MySQL/entry/mysql_5_6_4_development?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=mysql-5-6-4-development-milestone-now-available</link>
		<comments>http://blogs.oracle.com/MySQL/entry/mysql_5_6_4_development#comments</comments>
		<pubDate>Tue, 20 Dec 2011 17:44:19 +0000</pubDate>
		<dc:creator>Rob Young</dc:creator>
				<category><![CDATA[5.6]]></category>
		<category><![CDATA[InnoDB]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[optimizer]]></category>
		<category><![CDATA[Replication]]></category>

		<guid isPermaLink="false">http://blogs.oracle.com/MySQL/entry/mysql_5_6_4_development</guid>
		<description><![CDATA[I am pleased to announce that the MySQL Database 5.6.4 development milestone release (&#34;DMR&#34;) is now available for download&#160;(select the Development Release tab). MySQL 5.6.4 includes all 5.5 production-ready features and provides an aggreation of all of the new features that have been released in earlier 5.6 DMRs.&#160; 5.6.4 adds many bug fixes and more new &#34;early and often&#34; enhancements that are development and system QA complete and ready for Community evaluation and feedback.&#160; You can get the complete rundown of all the new 5.6.4 specific features here.For those following the progression of the 5.6 DMRs as the trains leave the station, you should bookmark these MySQL Engineering development team specific blogs: 
   
    InnoDB Development Team Blog 
    Specific enhancements in the MySQL 5.6.4 include:InnoDB Full-Text Search Better scaling of read-only workloadsInnoDB 5.6.4 supports databases with 4k and 8k page sizesImproving InnoDB memory usage 
    Other engineering team blogs include:&#160; 
    Marc Alff's Blog on Performance Schema 
     Andew Morgan's MySQL HA Blog 
    Mats Kindahl's Blog on MySQL Replication&#160; 
    Lars Thalmann's Blog on MySQL Replication, Backup, Connectors 
    Chuck Bell's Blog on MySQL GPL Utilities 
   
  You can also track the thought and innovation leaders on the MySQL Optimizer and the new Optimizer specific improvements in 5.6.4 by following the MySQL Optimizer Team member blogs: 
   
    Gleb Shcepa 
    Jorgen Loland 
    Guilhem Bichot 
    Oystein Grovlen 
    Olav Sandsta 
    Tor Didriksen 
   
  And of course you can follow others on the Optimizer team and all of MySQL Engineering teams by bookmarking/subscribing to PlanetMySQL. We look forward to your feedback on MySQL 5.6.4, so please download your copy now and help us make a better MySQL.&#160;  
  As always, a sincere thanks for your continued support of MySQL!&#160;&#160;&#160;]]></description>
			<content:encoded><![CDATA[<p>I am pleased to announce that the MySQL Database 5.6.4 development milestone release (&quot;DMR&quot;) is now available for <a href="http://dev.mysql.com/downloads/mysql/#downloads" title="MySQL Downloads">download</a>&nbsp;(select the Development Release tab). MySQL 5.6.4 includes all 5.5 production-ready features and provides an aggreation of all of the new features that have been released in <a href="http://dev.mysql.com/doc/refman/5.6/en/news-5-6-x.html" title="MySQL 5.6 DMR release notes">earlier 5.6 DMRs</a>.&nbsp; 5.6.4 adds many bug fixes and more new &quot;early and often&quot; enhancements that are development and system QA complete and ready for Community evaluation and feedback.&nbsp; You can get the complete rundown of all the new 5.6.4 specific features <a href="http://dev.mysql.com/doc/refman/5.6/en/news-5-6-4.html" title="MySQL 5.6.4 release notes">here</a>.<br /><br />For those following the progression of the 5.6 DMRs as the trains leave the station, you should bookmark these MySQL Engineering development team specific blogs:</p> 
  <ul> 
    <li><a href="http://blogs.innodb.com/wp/">InnoDB Development Team Blog</a></li> 
    <p>Specific enhancements in the MySQL 5.6.4 include:</p><a href="http://blogs.innodb.com/wp/?p=1474">InnoDB Full-Text Search</a> <br /><br /><a href="http://blogs.innodb.com/wp/?p=1500">Better scaling of read-only workloads</a><br /><br /><a href="http://blogs.innodb.com/wp/?p=1484">InnoDB 5.6.4 supports databases with 4k and 8k page sizes</a><br /><br /><a href="http://blogs.innodb.com/wp/?p=1510">Improving InnoDB memory usage</a><br /><br /> 
    <p>Other engineering team blogs include:&nbsp;</p> 
    <li><a href="http://marcalff.blogspot.com/2011/12/mysql-564-performance-schema.html">Marc Alff's Blog on Performance Schema</a></li> 
    <li><a href="http://marcalff.blogspot.com/"></a> <a href="http://www.clusterdb.com/category/mysql-replication/">Andew Morgan's MySQL HA Blog</a></li> 
    <li><a href="http://www.clusterdb.com/category/mysql-replication/"></a><a href="http://mysqlmusings.blogspot.com/">Mats Kindahl's Blog on MySQL Replication&nbsp;</a></li> 
    <li><a href="http://larsthalmann.blogspot.com/">Lars Thalmann's Blog on MySQL Replication, Backup, Connectors</a></li> 
    <li><a href="http://larsthalmann.blogspot.com/"></a><a href="http://drcharlesbell.blogspot.com/">Chuck Bell's Blog on MySQL GPL Utilities</a></li> 
  </ul> 
  <p>You can also track the thought and innovation leaders on the MySQL Optimizer and the new Optimizer specific improvements in 5.6.4 by following the MySQL Optimizer Team member blogs:<br /></p> 
  <ul> 
    <li><a href="http://glebshchepa.blogspot.com/">Gleb Shcepa</a></li> 
    <li><a href="http://jorgenloland.blogspot.com/">Jorgen Loland</a></li> 
    <li><a href="http://guilhembichot.blogspot.com/">Guilhem Bichot</a></li> 
    <li><a href="http://oysteing.blogspot.com/">Oystein Grovlen</a></li> 
    <li><a href="http://olavsandstaa.blogspot.com/">Olav Sandsta</a></li> 
    <li><a href="http://didrikdidrik.blogspot.com/">Tor Didriksen</a></li> 
  </ul> 
  <p>And of course you can follow others on the Optimizer team and all of MySQL Engineering teams by bookmarking/subscribing to <a href="http://www.planetmysql.org/">PlanetMySQL.</a> <br /><br />We look forward to your feedback on MySQL 5.6.4, so please download your copy now and help us make a better MySQL.&nbsp; </p> 
  <p>As always, a sincere thanks for your continued support of MySQL!&nbsp;&nbsp;&nbsp; <br /></p> 
  <p> </p> 
  <p><br /> </p><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=31375&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=31375&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://planetmysql.ru/2011/12/20/mysql-5-6-4-development-milestone-now-available/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL 5.6 Replication – New Early Access Features</title>
		<link>http://blogs.oracle.com/MySQL/entry/mysql_5_6_replication_new?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=mysql-5-6-replication-%25e2%2580%2593-new-early-access-features</link>
		<comments>http://blogs.oracle.com/MySQL/entry/mysql_5_6_replication_new#comments</comments>
		<pubDate>Mon, 01 Aug 2011 09:04:27 +0000</pubDate>
		<dc:creator>Oracle MySQL Group</dc:creator>
				<category><![CDATA[5.6]]></category>
		<category><![CDATA[api]]></category>
		<category><![CDATA[binlog]]></category>
		<category><![CDATA[commit]]></category>
		<category><![CDATA[group]]></category>
		<category><![CDATA[multi-threaded]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[Replication]]></category>
		<category><![CDATA[slaves]]></category>

		<guid isPermaLink="false">http://blogs.oracle.com/MySQL/entry/mysql_5_6_replication_new</guid>
		<description><![CDATA[At OSCON 2011 last week, Oracle delivered more early access (labs)
features for MySQL 5.6 replication. These features are focused on better integration,
performance and data integrity: 
      
      
    - The Binlog API: empowering the community to seamlessly
integrate MySQL with other applications and data stores; 
    - Binlog Group Commit and Enhanced Multi-Threaded Slaves:
continuing to deliver major improvements to replication performance; 
    - Durable Slave Reads: further enhancing data integrity. 
      
      
    These
new features build on the significant replication enhancements&#160;announced as part of the MySQL 5.6.2 Development Milestone Release back in
April. 
      
      
    We
are always listening to our customers and community. And, based on their needs and input, the MySQL
engineering team continues to take replication to the next level. 
      
      
    This
new functionality is available for evaluation now and can be downloaded today
from http://labs.mysql.com/ 
      
      
    Highlights
for each of the new capabilities are discussed below with links to blogs
written by the MySQL engineers, describing implementation and how to get
started in evaluating them. 
      
      
    We
value your feedback and therefore encourage you to share your input and
experiences through the comments sections of this and the blogs referenced
below. 
      
    
    
    
  Binlog API 
  The
Binlog API empowers the MySQL community to seamlessly integrate MySQL with both
new and legacy applications and data stores. 
    
    
  Data
volumes around the world now growing at the rate of 40% per annum,
driving users to implement more integrated data management technologies to
capture, integrate and analyze the trillions of bytes coming daily from web
applications, social networking, mobile broadband networks, embedded sensors,
etc.  
    
    
  The
Binlog API enables developers to reduce the complexity of integration by
standardizing their SQL data management operations on MySQL, while replicating
data to other applications within their data management infrastructure.  
    
    
  The Binlog API exposes a programmatic C++ interface to the binary log, implemented as a standalone library. 
Using the API, developers can read and parse binary log events both from existing binlog files as well as from running servers and replicate the 
changes into other data stores. 
    
    
  The Binlog API can be evaluated with the MySQL 5.6.2
Development Milestone Release as well as the current GA 5.5 release. 
    
  Learn
more &#62;&#62;&#160; 
    
    
  You
can download the code as part of MySQL Server Snapshot:
mysql-5.6-labs-binary-log-api from http://labs.mysql.com 
    
    
  To demonstrate the possibilities of the Binlog API, an example application for replicating changes from MySQL Server to Apache Solr 
 (a full text search server) has been developed. The example is available with the source download on Launchpad  
     
  Enhanced Multi-Threaded Slaves  
  Multi-threaded
slaves were previewed as part of a MySQL 5.6 Labs build&#160;in April 2011, delivering significant performance enhancements by allowing updates to be applied in parallel across databases, rather
than sequentially.  
    
    
  On-going
testing and feedback from the community has enabled Oracle to release an
enhanced implementation of multi-threaded slaves including: 
  - code
refactoring; 
  - bug
fixes; 
  - crash-recovery
processes; 
  - new
slave management options - now, when a user issues a STOP SLAVE command, the
operation waits until all threads across each database have applied their
updates before stopping, ensuring slave consistency. Slaves can still be
stopped immediately by killing the SQL thread on the slave; 
  - improved
support for statement-based replication by enhancing the handling of temporary
tables; 
  - simplified
configuration through
the renaming, reworking and reduction in parameters, without affecting overall
tuning ability.  
    
    
  The
enhancements delivered as part of this labs release enables the community to
extend evaluation and prototyping of new applications using a more robust and
feature-complete implementation of multi-threaded slaves. 
    
    
  Learn
more &#62;&#62;  
    
    
  You
can download the code as part of MySQL Server Snapshot: mysql-5.6-labs-multi-threaded-slave
from http://labs.mysql.com 
    
    
    
    
  Binlog Group Commit 
  Designed
to improve the performance of MySQL replication, Group Commit applies updates
to the binary log in parallel and then commits them as a group to the binlog on
disk. 
    
    
  Users
have complete control over the frequency of commits to disk – providing two
options: 
  1. Configure the number of transactions that should be grouped together
before commit 
  2. Define the time interval, with millisecond granularity, before
the binary log is persisted. 
    
    
  Checksums
are used to ensure all events have been written to the binary log.  
    
    
  The current Binlog Group Commit is a
snapshot of a work-in-progress.&#160; We have not benchmarked the
implementation at this time, and we expect results from that exercise to
influence final implementation decisions.&#160; This is a great opportunity for
the community to evaluate the implementation and feedback to the MySQL
development team. 
    
    
  Learn
more &#62;&#62;  
    
    
  You
can download the code as part of MySQL Server Snapshot: mysql-5.6-labs-binlog-group-commit
from http://labs.mysql.com 
    
    
    
    
  Durable Slave Reads  
  Users
now have the option to control when a slave reads the master’s binary log.  There are two options: 
    
   
  - Read the binlog as soon as updates are applied to it (with the
risk of the data being lost in the event of a master crash). 
  - Read the binlog only once the updates have been committed to disk,
making them read-durable and therefore not risking lost transactions, but
meaning the slave will be behind the master.  
    
    
  This
new flexibility means users can configure for performance or read-durability,
depending on the requirements of their application. 
    
    
    
    
  Summary 
  So
in summary, the enhancements delivered by these latest early access features deliver
new capabilities and flexibility that benefits almost all users of MySQL
replication. 
    
    
  You can
evaluate them today by downloading the snapshot from http://labs.mysql.com/ 
    
    
  Let
us know what you think of these enhancements directly in comments for each
blog. We look forward to working with the community to perfect these new
features.]]></description>
			<content:encoded><![CDATA[<link rel="File-List" href="file://localhost/Users/User/Library/Caches/TemporaryItems/msoclip/0/clip_filelist.xml" /> <!--[if gte mso 9]><xml>
 <o:DocumentProperties>
  <o:Revision>0</o:Revision>
  <o:TotalTime>0</o:TotalTime>
  <o:Pages>1</o:Pages>
  <o:Words>1037</o:Words>
  <o:Characters>5914</o:Characters>
  <o:Company>Homework</o:Company>
  <o:Lines>49</o:Lines>
  <o:Paragraphs>13</o:Paragraphs>
  <o:CharactersWithSpaces>6938</o:CharactersWithSpaces>
  <o:Version>14.0</o:Version>
 </o:DocumentProperties>
 <o:OfficeDocumentSettings>
  <o:AllowPNG/>
 </o:OfficeDocumentSettings>
</xml><![endif]--> <link rel="themeData" href="file://localhost/Users/User/Library/Caches/TemporaryItems/msoclip/0/clip_themedata.xml" /> <!--[if gte mso 9]><xml>
 <w:WordDocument>
  <w:View>Normal</w:View>
  <w:Zoom>0</w:Zoom>
  <w:TrackMoves/>
  <w:TrackFormatting/>
  <w:PunctuationKerning/>
  <w:ValidateAgainstSchemas/>
  <w:SaveIfXMLInval>false</w:SaveIfXMLInvalid>
  <w:IgnoreMixedContent>false</w:IgnoreMixedContent>
  <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText>
  <w:DoNotPromoteQF/>
  <w:LidThemeOther>EN-GB</w:LidThemeOther>
  <w:LidThemeAsian>JA</w:LidThemeAsian>
  <w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript>
  <w:Compatibility>
   <w:BreakWrappedTables/>
   <w:SnapToGridInCell/>
   <w:WrapTextWithPunct/>
   <w:UseAsianBreakRules/>
   <w:DontGrowAutofit/>
   <w:SplitPgBreakAndParaMark/>
   <w:EnableOpenTypeKerning/>
   <w:DontFlipMirrorIndents/>
   <w:OverrideTableStyleHps/>
  </w:Compatibility>
  <m:mathPr>
   <m:mathFont m:val="Cambria Math"/>
   <m:brkBin m:val="before"/>
   <m:brkBinSub m:val="&#45;-"/>
   <m:smallFrac m:val="off"/>
   <m:dispDef/>
   <m:lMargin m:val="0"/>
   <m:rMargin m:val="0"/>
   <m:defJc m:val="centerGroup"/>
   <m:wrapIndent m:val="1440"/>
   <m:intLim m:val="subSup"/>
   <m:naryLim m:val="undOvr"/>
  </m:mathPr></w:WordDocument>
</xml><![endif]--><!--[if gte mso 9]><xml>
 <w:LatentStyles DefLockedState="false" DefUnhideWhenUsed="true"
  DefSemiHidden="true" DefQFormat="false" DefPriority="99"
  LatentStyleCount="276">
  <w:LsdException Locked="false" Priority="0" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Normal"/>
  <w:LsdException Locked="false" Priority="9" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="heading 1"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 2"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 3"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 4"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 5"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 6"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 7"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 8"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 9"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 1"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 2"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 3"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 4"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 5"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 6"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 7"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 8"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 9"/>
  <w:LsdException Locked="false" Priority="35" QFormat="true" Name="caption"/>
  <w:LsdException Locked="false" Priority="10" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Title"/>
  <w:LsdException Locked="false" Priority="0" Name="Default Paragraph Font"/>
  <w:LsdException Locked="false" Priority="11" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Subtitle"/>
  <w:LsdException Locked="false" Priority="22" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Strong"/>
  <w:LsdException Locked="false" Priority="20" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Emphasis"/>
  <w:LsdException Locked="false" Priority="59" SemiHidden="false"
   UnhideWhenUsed="false" Name="Table Grid"/>
  <w:LsdException Locked="false" UnhideWhenUsed="false" Name="Placeholder Text"/>
  <w:LsdException Locked="false" Priority="1" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="No Spacing"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 1"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 1"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 1"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 1"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 1"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 1"/>
  <w:LsdException Locked="false" UnhideWhenUsed="false" Name="Revision"/>
  <w:LsdException Locked="false" Priority="34" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="List Paragraph"/>
  <w:LsdException Locked="false" Priority="29" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Quote"/>
  <w:LsdException Locked="false" Priority="30" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Intense Quote"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 1"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 1"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 1"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 1"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 1"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 1"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 1"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 1"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 2"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 2"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 2"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 2"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 2"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 2"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 2"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 2"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 2"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 2"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 2"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 2"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 2"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 2"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 3"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 3"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 3"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 3"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 3"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 3"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 3"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 3"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 3"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 3"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 3"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 3"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 3"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 3"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 4"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 4"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 4"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 4"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 4"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 4"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 4"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 4"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 4"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 4"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 4"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 4"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 4"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 4"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 5"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 5"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 5"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 5"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 5"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 5"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 5"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 5"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 5"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 5"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 5"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 5"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 5"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 5"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 6"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 6"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 6"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 6"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 6"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 6"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 6"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 6"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 6"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 6"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 6"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 6"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 6"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 6"/>
  <w:LsdException Locked="false" Priority="19" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Subtle Emphasis"/>
  <w:LsdException Locked="false" Priority="21" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Intense Emphasis"/>
  <w:LsdException Locked="false" Priority="31" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Subtle Reference"/>
  <w:LsdException Locked="false" Priority="32" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Intense Reference"/>
  <w:LsdException Locked="false" Priority="33" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Book Title"/>
  <w:LsdException Locked="false" Priority="37" Name="Bibliography"/>
  <w:LsdException Locked="false" Priority="39" QFormat="true" Name="TOC Heading"/>
 </w:LatentStyles>
</xml><![endif]-->  <!--[if gte mso 10]>

<![endif]--> <!--StartFragment--> <span> 
    <p>      <link rel="File-List" href="file://localhost/Users/User/Library/Caches/TemporaryItems/msoclip/0/clip_filelist.xml" /> <!--[if gte mso 9]><xml>
 <o:DocumentProperties>
  <o:Revision>0</o:Revision>
  <o:TotalTime>0</o:TotalTime>
  <o:Pages>1</o:Pages>
  <o:Words>204</o:Words>
  <o:Characters>1168</o:Characters>
  <o:Company>Homework</o:Company>
  <o:Lines>9</o:Lines>
  <o:Paragraphs>2</o:Paragraphs>
  <o:CharactersWithSpaces>1370</o:CharactersWithSpaces>
  <o:Version>14.0</o:Version>
 </o:DocumentProperties>
 <o:OfficeDocumentSettings>
  <o:AllowPNG/>
 </o:OfficeDocumentSettings>
</xml><![endif]--> <link rel="themeData" href="file://localhost/Users/User/Library/Caches/TemporaryItems/msoclip/0/clip_themedata.xml" /> <!--[if gte mso 9]><xml>
 <w:WordDocument>
  <w:View>Normal</w:View>
  <w:Zoom>0</w:Zoom>
  <w:TrackMoves/>
  <w:TrackFormatting/>
  <w:PunctuationKerning/>
  <w:ValidateAgainstSchemas/>
  <w:SaveIfXMLInval>false</w:SaveIfXMLInvalid>
  <w:IgnoreMixedContent>false</w:IgnoreMixedContent>
  <w:AlwaysShowPlaceholderText>false</w:AlwaysShowPlaceholderText>
  <w:DoNotPromoteQF/>
  <w:LidThemeOther>EN-GB</w:LidThemeOther>
  <w:LidThemeAsian>JA</w:LidThemeAsian>
  <w:LidThemeComplexScript>X-NONE</w:LidThemeComplexScript>
  <w:Compatibility>
   <w:BreakWrappedTables/>
   <w:SnapToGridInCell/>
   <w:WrapTextWithPunct/>
   <w:UseAsianBreakRules/>
   <w:DontGrowAutofit/>
   <w:SplitPgBreakAndParaMark/>
   <w:EnableOpenTypeKerning/>
   <w:DontFlipMirrorIndents/>
   <w:OverrideTableStyleHps/>
  </w:Compatibility>
  <m:mathPr>
   <m:mathFont m:val="Cambria Math"/>
   <m:brkBin m:val="before"/>
   <m:brkBinSub m:val="&#45;-"/>
   <m:smallFrac m:val="off"/>
   <m:dispDef/>
   <m:lMargin m:val="0"/>
   <m:rMargin m:val="0"/>
   <m:defJc m:val="centerGroup"/>
   <m:wrapIndent m:val="1440"/>
   <m:intLim m:val="subSup"/>
   <m:naryLim m:val="undOvr"/>
  </m:mathPr></w:WordDocument>
</xml><![endif]--><!--[if gte mso 9]><xml>
 <w:LatentStyles DefLockedState="false" DefUnhideWhenUsed="true"
  DefSemiHidden="true" DefQFormat="false" DefPriority="99"
  LatentStyleCount="276">
  <w:LsdException Locked="false" Priority="0" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Normal"/>
  <w:LsdException Locked="false" Priority="9" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="heading 1"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 2"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 3"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 4"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 5"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 6"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 7"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 8"/>
  <w:LsdException Locked="false" Priority="9" QFormat="true" Name="heading 9"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 1"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 2"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 3"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 4"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 5"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 6"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 7"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 8"/>
  <w:LsdException Locked="false" Priority="39" Name="toc 9"/>
  <w:LsdException Locked="false" Priority="35" QFormat="true" Name="caption"/>
  <w:LsdException Locked="false" Priority="10" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Title"/>
  <w:LsdException Locked="false" Priority="0" Name="Default Paragraph Font"/>
  <w:LsdException Locked="false" Priority="11" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Subtitle"/>
  <w:LsdException Locked="false" Priority="22" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Strong"/>
  <w:LsdException Locked="false" Priority="20" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Emphasis"/>
  <w:LsdException Locked="false" Priority="59" SemiHidden="false"
   UnhideWhenUsed="false" Name="Table Grid"/>
  <w:LsdException Locked="false" UnhideWhenUsed="false" Name="Placeholder Text"/>
  <w:LsdException Locked="false" Priority="1" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="No Spacing"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 1"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 1"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 1"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 1"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 1"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 1"/>
  <w:LsdException Locked="false" UnhideWhenUsed="false" Name="Revision"/>
  <w:LsdException Locked="false" Priority="34" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="List Paragraph"/>
  <w:LsdException Locked="false" Priority="29" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Quote"/>
  <w:LsdException Locked="false" Priority="30" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Intense Quote"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 1"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 1"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 1"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 1"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 1"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 1"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 1"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 1"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 2"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 2"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 2"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 2"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 2"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 2"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 2"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 2"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 2"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 2"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 2"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 2"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 2"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 2"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 3"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 3"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 3"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 3"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 3"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 3"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 3"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 3"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 3"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 3"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 3"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 3"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 3"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 3"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 4"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 4"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 4"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 4"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 4"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 4"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 4"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 4"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 4"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 4"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 4"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 4"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 4"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 4"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 5"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 5"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 5"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 5"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 5"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 5"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 5"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 5"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 5"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 5"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 5"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 5"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 5"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 5"/>
  <w:LsdException Locked="false" Priority="60" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Shading Accent 6"/>
  <w:LsdException Locked="false" Priority="61" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light List Accent 6"/>
  <w:LsdException Locked="false" Priority="62" SemiHidden="false"
   UnhideWhenUsed="false" Name="Light Grid Accent 6"/>
  <w:LsdException Locked="false" Priority="63" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 1 Accent 6"/>
  <w:LsdException Locked="false" Priority="64" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Shading 2 Accent 6"/>
  <w:LsdException Locked="false" Priority="65" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 1 Accent 6"/>
  <w:LsdException Locked="false" Priority="66" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium List 2 Accent 6"/>
  <w:LsdException Locked="false" Priority="67" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 1 Accent 6"/>
  <w:LsdException Locked="false" Priority="68" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 2 Accent 6"/>
  <w:LsdException Locked="false" Priority="69" SemiHidden="false"
   UnhideWhenUsed="false" Name="Medium Grid 3 Accent 6"/>
  <w:LsdException Locked="false" Priority="70" SemiHidden="false"
   UnhideWhenUsed="false" Name="Dark List Accent 6"/>
  <w:LsdException Locked="false" Priority="71" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Shading Accent 6"/>
  <w:LsdException Locked="false" Priority="72" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful List Accent 6"/>
  <w:LsdException Locked="false" Priority="73" SemiHidden="false"
   UnhideWhenUsed="false" Name="Colorful Grid Accent 6"/>
  <w:LsdException Locked="false" Priority="19" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Subtle Emphasis"/>
  <w:LsdException Locked="false" Priority="21" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Intense Emphasis"/>
  <w:LsdException Locked="false" Priority="31" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Subtle Reference"/>
  <w:LsdException Locked="false" Priority="32" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Intense Reference"/>
  <w:LsdException Locked="false" Priority="33" SemiHidden="false"
   UnhideWhenUsed="false" QFormat="true" Name="Book Title"/>
  <w:LsdException Locked="false" Priority="37" Name="Bibliography"/>
  <w:LsdException Locked="false" Priority="39" QFormat="true" Name="TOC Heading"/>
 </w:LatentStyles>
</xml><![endif]-->  <!--[if gte mso 10]>

<![endif]--> <!--StartFragment--> </p> 
    <p><span>At OSCON 2011 last week, Oracle delivered more early access (labs)
features for MySQL 5.6 replication. These features are focused on better integration,
performance and data integrity:</span></p> 
    <p> </p> 
    <p><span lang="EN-US"><o:p> </o:p></span></p> 
    <p><!--[if !supportLists]--><span lang="EN-US"><span>-<span> </span></span></span><!--[endif]--><span lang="EN-US">The Binlog API: empowering the community to seamlessly
integrate MySQL with other applications and data stores;<o:p /></span></p> 
    <p><!--[if !supportLists]--><span lang="EN-US"><span>-<span> </span></span></span><!--[endif]--><span lang="EN-US">Binlog Group Commit and Enhanced Multi-Threaded Slaves:
continuing to deliver major improvements to replication performance;<o:p /></span></p> 
    <p><!--[if !supportLists]--><span lang="EN-US"><span>-<span> </span></span></span><!--[endif]--><span lang="EN-US">Durable Slave Reads: further enhancing data integrity.<o:p /></span></p> 
    <p><span lang="EN-US"><o:p> </o:p></span></p> 
    <p> </p> 
    <p><span lang="EN-US">These
new features build on the <a href="http://blogs.oracle.com/MySQL/entry/mysql_development_milestone_562_taking_mysql_replication_to_the_next_level">significant replication enhancements</a>&nbsp;announced as part of the MySQL 5.6.2 Development Milestone Release back in
April.<o:p /></span></p> 
    <p><span lang="EN-US"><o:p> </o:p></span></p> 
    <p> </p> 
    <p><span lang="EN-US">We
are always listening to our customers and community.<span> </span>And, based on their needs and input, the MySQL
engineering team continues to take replication to the next level.<o:p /></span></p> 
    <p><span lang="EN-US"><o:p> </o:p></span></p> 
    <p> </p> 
    <p><span lang="EN-US">This
new functionality is available for evaluation now and can be downloaded today
from <a href="http://labs.mysql.com/">http://labs.mysql.com/</a><o:p /></span></p> 
    <p><span lang="EN-US"><o:p> </o:p></span></p> 
    <p> </p> 
    <p><span lang="EN-US">Highlights
for each of the new capabilities are discussed below with links to blogs
written by the MySQL engineers, describing implementation and how to get
started in evaluating them.<o:p /></span></p> 
    <p><span lang="EN-US"><o:p> </o:p></span></p> 
    <p> </p> 
    <p><span lang="EN-US">We
value your feedback and therefore encourage you to share your input and
experiences through the comments sections of this and the blogs referenced
below.</span></p> 
    <p> </p></span> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><b><span lang="EN-US">Binlog API<o:p /></span></b></p> 
  <p><span lang="EN-US">The
Binlog API empowers the MySQL community to seamlessly integrate MySQL with both
new and legacy applications and data stores.<o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">Data
volumes around the world now growing at the rate of <a href="http://www.mckinsey.com/mgi/publications/big_data/pdfs/MGI_big_data_full_report.pdf">40% per annum</a>,
driving users to implement more integrated data management technologies to
capture, integrate and analyze the trillions of bytes coming daily from web
applications, social networking, mobile broadband networks, embedded sensors,
etc. <o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">The
Binlog API enables developers to reduce the complexity of integration by
standardizing their SQL data management operations on MySQL, while replicating
data to other applications within their data management infrastructure. <o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <pre><span>The Binlog API exposes a programmatic C++ interface to the binary log, implemented as a standalone library. </span>
<span>Using the API, developers </span><span>can </span><span>read and parse binary log events both from existing binlog files as well as from running servers and replicate the </span>
<span>changes into other data stores.<o:p></o:p></span></pre> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span>The Binlog API can be evaluated with the MySQL 5.6.2
Development Milestone Release as well as the current GA 5.5 release.<o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p><span lang="EN-US"><a href="http://intuitive-search.blogspot.com/2011/07/binary-log-api-and-replication-listener.html">Learn
more &gt;&gt;&nbsp;</a><o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">You
can download the code as part of MySQL Server Snapshot:
mysql-5.6-labs-binary-log-api from <a href="http://labs.mysql.com">http://labs.mysql.com</a><o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <pre><span>To demonstrate the possibilities of the Binlog API, an example application for replicating changes from MySQL Server to <a href="http://lucene.apache.org/solr/%20"  title="">Apache Solr </a></span>
<span> (a full text search server) has been developed. The example is available with the <a href="https://launchpad.net/mysql-replication-listener"  title="">source download on Launchpad</a></span><span> </span></pre> 
  <p><span lang="EN-US"><o:p> </o:p></span><span> </span></p> 
  <p><b><span lang="EN-US">Enhanced Multi-Threaded Slaves <o:p /></span></b></p> 
  <p><span lang="EN-US">Multi-threaded
slaves were previewed as part of a <a href="http://d2-systems.blogspot.com/2011/04/mysql-56x-feature-preview-multi.html">MySQL 5.6 Labs build</a>&nbsp;in April 2011, delivering significant performance enhancements by </span><span lang="EN-US">allowing updates to be applied in parallel across databases, rather
than sequentially. <o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">On-going
testing and feedback from the community has enabled Oracle to release an
enhanced implementation of multi-threaded slaves including:<o:p /></span></p> 
  <p><!--[if !supportLists]--><span lang="EN-US"><span>-<span> </span></span></span><!--[endif]--><span lang="EN-US">code
refactoring;<o:p /></span></p> 
  <p><!--[if !supportLists]--><span lang="EN-US"><span>-<span> </span></span></span><!--[endif]--><span lang="EN-US">bug
fixes;<o:p /></span></p> 
  <p><!--[if !supportLists]--><span lang="EN-US"><span>-<span> </span></span></span><!--[endif]--><span lang="EN-US">crash-recovery
processes;<o:p /></span></p> 
  <p><!--[if !supportLists]--><span lang="EN-US"><span>-<span> </span></span></span><!--[endif]--><span lang="EN-US">new
slave management options - now, when a user issues a STOP SLAVE command, the
operation waits until all threads across each database have applied their
updates before stopping, ensuring slave consistency. Slaves can still be
stopped immediately by killing the SQL thread on the slave;<o:p /></span></p> 
  <p><!--[if !supportLists]--><span lang="EN-US"><span>-<span> </span></span></span><!--[endif]--><span lang="EN-US">improved
support for statement-based replication by enhancing the handling of temporary
tables;<o:p /></span></p> 
  <p><!--[if !supportLists]--><span lang="EN-US"><span>-<span> </span></span></span><!--[endif]--><span lang="EN-US">simplified
configuration </span><span lang="EN-US">through
the renaming, reworking and reduction in parameters, without affecting overall
tuning ability. </span><span lang="EN-US"><o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">The
enhancements delivered as part of this labs release enables the community to
extend evaluation and prototyping of new applications using a more robust and
feature-complete implementation of multi-threaded slaves.<o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US"><a href="http://d2-systems.blogspot.com/2011/07/update-on-multi-threaded-slave.html">Learn
more &gt;&gt;</a> <o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">You
can download the code as part of MySQL Server Snapshot: mysql-5.6-labs-multi-threaded-slave
from <a href="http://labs.mysql.com">http://labs.mysql.com</a><o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p> </p> 
  <p><b><span lang="EN-US">Binlog Group Commit<o:p /></span></b></p> 
  <p><span lang="EN-US">Designed
to improve the performance of MySQL replication, Group Commit applies updates
to the binary log in parallel and then commits them as a group to the binlog on
disk.<o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">Users
have complete control over the frequency of commits to disk – providing two
options:<o:p /></span></p> 
  <p><span lang="EN-US">1. Configure the number of transactions that should be grouped together
before commit<o:p /></span></p> 
  <p><span lang="EN-US">2. Define the time interval, with millisecond granularity, before
the binary log is persisted.<o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">Checksums
are used to ensure all events have been written to the binary log. <o:p /></span></p> 
  <p><span><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">The current Binlog Group Commit is a
snapshot of a work-in-progress.&nbsp; We have not benchmarked the
implementation at this time, and we expect results from that exercise to
influence final implementation decisions.&nbsp; This is a great opportunity for
the community to evaluate the implementation and feedback to the MySQL
development team.</span><span lang="EN-US"><o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US"><a href="http://mysqlmusings.blogspot.com/2011/07/binlog-group-commit-experiments.html">Learn
more &gt;&gt;</a> <o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">You
can download the code as part of MySQL Server Snapshot: mysql-5.6-labs-binlog-group-commit
from http://labs.mysql.com<o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p> </p> 
  <p><b><span lang="EN-US">Durable Slave Reads <o:p /></span></b></p> 
  <p><span lang="EN-US">Users
now have the option to control when a slave reads the master’s binary log. <span> </span>There are two options:</span></p> 
  <p> </p> 
  <p><!--[if !supportLists]--><span lang="EN-US"><span></span></span></p> 
  <p><span lang="EN-US"><span>-<span> </span></span></span><!--[endif]--><span lang="EN-US">Read the binlog as soon as updates are applied to it (with the
risk of the data being lost in the event of a master crash).<o:p /></span></p> 
  <p><!--[if !supportLists]--><span lang="EN-US"><span>-<span> </span></span></span><!--[endif]--><span lang="EN-US">Read the binlog only once the updates have been committed to disk,
making them read-durable and therefore not risking lost transactions, but
meaning the slave will be behind the master. <o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">This
new flexibility means users can configure for performance or read-durability,
depending on the requirements of their application.<o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p> </p> 
  <p><b><span lang="EN-US">Summary<o:p /></span></b></p> 
  <p><span lang="EN-US">So
in summary, the enhancements delivered by these latest early access features deliver
new capabilities and flexibility that benefits almost all users of MySQL
replication.<o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">You can
evaluate them today by downloading the snapshot from <a href="http://labs.mysql.com/">http://labs.mysql.com/</a><o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> 
  <p> </p> 
  <p><span lang="EN-US">Let
us know what you think of these enhancements directly in comments for each
blog. We look forward to working with the community to perfect these new
features.</span><span lang="EN-US"><o:p /></span></p> 
  <p><span lang="EN-US"><o:p> </o:p></span></p> <!--EndFragment--><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=29579&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=29579&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://planetmysql.ru/2011/08/01/mysql-5-6-replication-%e2%80%93-new-early-access-features/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>InnoDB Full-Text Search Tutorial</title>
		<link>http://blogs.innodb.com/wp/2011/07/innodb-full-text-search-tutorial/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=innodb-full-text-search-tutorial</link>
		<comments>http://blogs.innodb.com/wp/2011/07/innodb-full-text-search-tutorial/#comments</comments>
		<pubDate>Wed, 27 Jul 2011 00:04:19 +0000</pubDate>
		<dc:creator>John Russell</dc:creator>
				<category><![CDATA[5.6]]></category>
		<category><![CDATA[Feature]]></category>
		<category><![CDATA[fts]]></category>
		<category><![CDATA[Search]]></category>

		<guid isPermaLink="false">http://blogs.innodb.com/wp/?p=1115</guid>
		<description><![CDATA[The InnoDB full-text search capability is an exciting feature. The full-text search itself is generally useful to have in an RDBMS. If an application is using all InnoDB tables except for one that is used for full-text searches, now that last table can be switched to InnoDB. If putting the full-text data in a MyISAM table led to scalability problems, duplication, or a less-than-ideal schema design, now those issues can be addressed.


In this post, I&#8217;ll take you through some of the basics of setting up and querying an InnoDB FULLTEXT search index. I&#8217;ll leave the scalability and performance aspects to Jimmy&#8217;s and Vinay&#8217;s blog posts, and just use some toy-sized data for demonstration purposes.

Creating a Table with a Full-Text Search Index

The key component of this feature is an index of type FULLTEXT, applied to one or more columns of an InnoDB table.


In Jimmy&#8217;s post, he mentions some scalability considerations where you might create the table (including a special FTS_DOC_ID column), load the data, then create the FULLTEXT index afterward. For simplicity (and since the data volume is so small), I&#8217;ll create the table with the index in place, then load the data afterward.



use test;
-- We will do some commits and rollbacks to demonstrate transactional features.
-- So turn off the default setting that commits immediately after each statement.
set autocommit=0;

drop table if exists quotes;
-- In 5.5 and above, by default this table is an InnoDB table.
-- The full-text search feature lets us define the FULLTEXT index.

create table quotes
  (    id int unsigned auto_increment primary key
    , author varchar(64)    , quote varchar(4000)
    , source varchar(64)
    , fulltext(quote)
  );

-- Get some words and phrases to search for into the table.
insert into quotes (author, quote, source) values
  ('Abraham Lincoln', 'Fourscore and seven years ago...',
  'Gettysburg Address')
, ('George Harrison', 'All those years ago...',
  'Live In Japan')
, ('Arthur C. Clarke', 'Then 10 years ago the monolith was discovered.',
  '2010: The Year We Make Contact')
, ('Benjamin Franklin',
  'Early to bed and early to rise, makes a man healthy, wealthy, and wise.',
  'Poor Richard''s Almanack')
, ('James Thurber',
  'Early to rise and early to bed makes a male healthy and wealthy and dead.',
  'The New Yorker')
, ('K', '1500 hundred years ago, everybody knew that the Earth was the center of the universe.',
  'Men in Black')
;

-- Since this is an InnoDB table, we are mindful of transactions.
commit;

Word and Phrase Search &#8211; Natural Language Mode

Once the data is loaded and committed, you can run queries using the MATCH(columns) AGAINST (search expression) operator to do the actual searches. You can combine this operator with all the usual WHERE and similar clauses in the SELECT statement.


The simplest kind of search is to find a single word, or a phrase with all words in exact order. For this type of search, use the IN NATURAL LANGUAGE clause inside the AGAINST() call. This technique typically involves a user-entered string that you pass verbatim to the query (of course, after escaping any quotation marks or other special characters to prevent SQL injection attacks).


-- Search for a single word.
select author as "Monolith" from quotes
  where match(quote) against ('monolith' in natural language mode);
+------------------+
&#124; Monolith         &#124;
+------------------+
&#124; Arthur C. Clarke &#124;
+------------------+
1 row in set (0.01 sec)

select author as "Ago" from quotes
  where match(quote) against ('ago' in natural language mode);
+------------------+
&#124; Ago              &#124;
+------------------+
&#124; Abraham Lincoln  &#124;
&#124; George Harrison  &#124;
&#124; Arthur C. Clarke &#124;
&#124; K                &#124;
+------------------+
4 rows in set (0.00 sec)

-- Search for a phrase.
select author as "Years Ago" from quotes
  where match(quote) against ('years ago' in natural language mode);
+------------------+
&#124; Years Ago        &#124;
+------------------+
&#124; Abraham Lincoln  &#124;
&#124; George Harrison  &#124;
&#124; Arthur C. Clarke &#124;
&#124; K                &#124;
+------------------+
4 rows in set (0.00 sec)

AND / OR / NOT Operators &#8211; Boolean Mode

For more complicated searches, you can have multiple words and phrases and search for different combinations of optional and required terms, not necessarily in the same order. This technique typically involves several data values that you query from elsewhere, or splitting apart a user-entered string and applying your own rules to the words and phrases inside.


-- Search for a combination of words, not in the same order as the original.
select author as "Ago and Years" from quotes
  where match(quote) against ('+ago +years' in boolean mode);
+------------------+
&#124; Ago and Years    &#124;
+------------------+
&#124; Abraham Lincoln  &#124;
&#124; George Harrison  &#124;
&#124; Arthur C. Clarke &#124;
&#124; K                &#124;
+------------------+
4 rows in set (0.00 sec)

-- Search for other Boolean combinations of words.
select author as "Fourscore or Monolith" from quotes
  where match(quote) against ('fourscore monolith' in boolean mode);
+-----------------------+
&#124; Fourscore or Monolith &#124;
+-----------------------+
&#124; Abraham Lincoln       &#124;
&#124; Arthur C. Clarke      &#124;
+-----------------------+
2 rows in set (0.00 sec)

select author as "Years and not Monolith" from quotes
  where match(quote) against ('+years -monolith' in boolean mode);
+------------------------+
&#124; Years and not Monolith &#124;
+------------------------+
&#124; Abraham Lincoln        &#124;
&#124; George Harrison        &#124;
&#124; K                      &#124;
+------------------------+
3 rows in set (0.00 sec)

Proximity Search

Proximity search is a special case of Boolean search using the @ operator within the AGAINST() string. You supply 2 or more words, double-quoted, within the single-quoted AGAINST() string, followed by @distance to specify how far apart these words can be. The distance represents the maximum number of bytes between the starting points of all these words.


-- The starting points for these words are too far apart
-- (not within 20 bytes), so no results.
select quote as "Too Far Apart" from quotes
  where match(quote) against ('"early wise" @20' in boolean mode);
Empty set (0.00 sec)

-- But the starting points of all words are within 100 bytes,
-- so this query does give results.
select quote as "Early...Wise" from quotes
  where match(quote) against ('"early wise" @100' in boolean mode);
+-------------------------------------------------------------------------+
&#124; Early...Wise                                                            &#124;
+-------------------------------------------------------------------------+
&#124; Early to bed and early to rise, makes a man healthy, wealthy, and wise. &#124;
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)

-- In this case, the smallest distance that produces results is 49.
select quote as "Early...Wise" from quotes
  where match(quote) against ('"early wise" @49' in boolean mode);
+-------------------------------------------------------------------------+
&#124; Early...Wise                                                            &#124;
+-------------------------------------------------------------------------+
&#124; Early to bed and early to rise, makes a man healthy, wealthy, and wise. &#124;
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)

-- Here is an example showing 2 results, with the words close to each other.
select quote as "Early...Bed" from quotes
  where match(quote) against ('"early bed" @20' in boolean mode);
+---------------------------------------------------------------------------+
&#124; Early...Bed                                                               &#124;
+---------------------------------------------------------------------------+
&#124; Early to bed and early to rise, makes a man healthy, wealthy, and wise.   &#124;
&#124; Early to rise and early to bed makes a male healthy and wealthy and dead. &#124;
+---------------------------------------------------------------------------+
2 rows in set (0.00 sec)

Relevance Ranking

The relevance ranking is fairly basic, derived from word frequencies within each document and the search data overall. Typically, you would only ORDER BY this value for very simplistic searches of small documents; for any important search you would layer your own ranking logic on top, perhaps with the MySQL relevance value as one factor in the overall rank.


-- Get the relevance of a single word.
select substr(quote,1,20) as "And",
  match(quote) against ('and' in natural language mode) as "Relevance"
  from quotes order by "Relevance" desc;
+----------------------+--------------------+
&#124; And                  &#124; Relevance          &#124;
+----------------------+--------------------+
&#124; Fourscore and seven  &#124; 0.0906190574169159 &#124;
&#124; All those years ago. &#124;                  0 &#124;
&#124; Then 10 years ago th &#124;                  0 &#124;
&#124; Early to bed and ear &#124; 0.1812381148338318 &#124;
&#124; Early to rise and ea &#124; 0.2718571722507477 &#124;
&#124; 1500 hundred years a &#124;                  0 &#124;
+----------------------+--------------------+
6 rows in set (0.00 sec)

Transactions

The key idea behind bringing full-text search to InnoDB tables is to make this feature compatible with transactions, so that you can include full-text columns alongside other columns in tables in ways that make sense in terms of schema design, and multiple sessions can update the full-text column data (and/or other columns in the table) simultaneously. The full-text data doesn&#8217;t have to be treated as read-only or read-mostly.


As mentioned in Jimmy&#8217;s blog post, the table structures that manipulate the full-text data behind the scenes are only updated at COMMIT time. So make sure to insert or update full-text data in one transaction, commit, and then run any full-text queries in a subsequent transaction. (Actually, in the examples below, it looks like the data is taken out of the full-text results as soon as a DELETE is issued, then comes back if the deletion is rolled back. I think that is explained in Jimmy&#8217;s blog post by the discussion about the delete-marking optimization to avoid huge updates to the full-text index for deleted data.)


drop table if exists quotes_uncommitted;

create table quotes_uncommitted
  (
      author varchar(64)
    , quote varchar(4000)
    , source varchar(64)
    , fulltext(quote)
    , primary key (author, quote(128))
  );

-- We insert but don't immediately commit.
insert into quotes_uncommitted select author, quote, source from quotes;
-- Within the same transaction, a full-text search does not see the uncommitted data.
select count(author), author as "Uncommitted Results" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------+
&#124; count(author) &#124; Uncommitted Results &#124;
+---------------+---------------------+
&#124;             0 &#124; NULL                &#124;
+---------------+---------------------+
1 row in set (0.00 sec)

-- If the newly inserted rows are rolled back...
rollback;
-- ...then the full-text search still doesn't see them.
select count(author), author as "Rolled-Back Results" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------+
&#124; count(author) &#124; Rolled-Back Results &#124;
+---------------+---------------------+
&#124;             0 &#124; NULL                &#124;
+---------------+---------------------+
1 row in set (0.00 sec)

-- OK, let's start with some committed data in the table, then empty the table,
-- and then try some FTS queries
-- both before and after the commit.
insert into quotes_uncommitted select author, quote, source from quotes;
commit;
delete from quotes_uncommitted;
select count(author), author as "Deleted but still not committed" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------------------+
&#124; count(author) &#124; Deleted but still not committed &#124;
+---------------+---------------------------------+
&#124;             0 &#124; NULL                            &#124;
+---------------+---------------------------------+
1 row in set (0.00 sec)

rollback;
select count(author), author as "Deleted and rolled back" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-------------------------+
&#124; count(author) &#124; Deleted and rolled back &#124;
+---------------+-------------------------+
&#124;             4 &#124; Abraham Lincoln         &#124;
+---------------+-------------------------+
1 row in set (0.00 sec)

delete from quotes_uncommitted;
commit;
select count(author), author as "Deleted and committed" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-----------------------+
&#124; count(author) &#124; Deleted and committed &#124;
+---------------+-----------------------+
&#124;             0 &#124; NULL                  &#124;
+---------------+-----------------------+
1 row in set (0.00 sec)

insert into quotes_uncommitted select author, quote, source from quotes;
commit;
truncate table quotes_uncommitted;
select count(author), author as "Truncated" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-----------+
&#124; count(author) &#124; Truncated &#124;
+---------------+-----------+
&#124;             0 &#124; NULL      &#124;
+---------------+-----------+
1 row in set (0.00 sec)

Multi-Column Searches

Although you can only have one FULLTEXT index in an InnoDB table, that index can apply to multiple columns, allowing you to search when you aren&#8217;t sure which column contains the term. With a multi-column index, we can MATCH() against all the columns to find words that appear in any of those columns. Always reference all the same columns in the MATCH() clause as in the FULLTEXT index definition, because the information about which column the words appear in is not included in the full-text search data.


drop table if exists quotes_multi_col;

create table quotes_multi_col
  (
    id int unsigned auto_increment primary key
    , author varchar(64)
    , quote varchar(4000)
    , source varchar(64)
    , fulltext(author, quote, source)
  );

insert into quotes_multi_col select * from quotes;
commit;

select author as "Poor 1 (NL)", substr(quote,1,15) as "Poor 2 (NL)", source as "Poor 3 (NL)" from
  quotes_multi_col where match(author, quote, source)
  against ('poor' in natural language mode);
+-------------------+-----------------+-------------------------+
&#124; Poor 1 (NL)       &#124; Poor 2 (NL)     &#124; Poor 3 (NL)             &#124;
+-------------------+-----------------+-------------------------+
&#124; Benjamin Franklin &#124; Early to bed an &#124; Poor Richard's Almanack &#124;
+-------------------+-----------------+-------------------------+
1 row in set (0.00 sec)

select author as "Poor 1 (BOOL)", substr(quote,1,15) as "Poor 2 (BOOL)", source as "Poor 3 (BOOL)"
  from quotes_multi_col where match(author, quote, source)
  against ('poor' in boolean mode);
+-------------------+-----------------+-------------------------+
&#124; Poor 1 (BOOL)     &#124; Poor 2 (BOOL)   &#124; Poor 3 (BOOL)           &#124;
+-------------------+-----------------+-------------------------+
&#124; Benjamin Franklin &#124; Early to bed an &#124; Poor Richard's Almanack &#124;
+-------------------+-----------------+-------------------------+
1 row in set (0.00 sec)

select author as "Clarke 1 (NL)", substr(quote,1,15) as "Clarke 2 (NL)", source as "Clarke 3 (NL)"
  from quotes_multi_col where match(author, quote, source)
  against ('clarke' in natural language mode);
+------------------+-----------------+--------------------------------+
&#124; Clarke 1 (NL)    &#124; Clarke 2 (NL)   &#124; Clarke 3 (NL)                  &#124;
+------------------+-----------------+--------------------------------+
&#124; Arthur C. Clarke &#124; Then 10 years a &#124; 2010: The Year We Make Contact &#124;
+------------------+-----------------+--------------------------------+
1 row in set (0.00 sec)

Interaction with Other Indexes

Remember that the design of your primary key index and secondary indexes is a big factor in query performance for InnoDB tables.


You can include parts (prefixes) of the full-text column(s) within the primary key.
However, that might not be a good idea if (a) the associated columns will ever be updated &#8212; which causes an expensive reorganization within the InnoDB table, or (b) if the table will have any other secondary indexes &#8212; the primary key values for a row are duplicated in the entry for that row in every secondary index, making index operations require more I/O and memory.
As mentioned in Jimmy&#8217;s blog post, adding the FULLTEXT index to the table is going to create a new column and associated index in the original table, so you could set up the column and index ahead of time, to avoid table reorganization later.
You can use the unique constraint of the primary key or a UNIQUE index to prevent duplicate values or combinations of values from being entered.
You can use the not-null constraint of the primary key to prevent blank values or combinations of values from being entered.
For the Labs release, the InnoDB FULLTEXT processing isn&#8217;t integrated with the MySQL optimizer and its estimates for which index is best to use, so don&#8217;t draw conclusions about performance characteristics from this early preview.

Stopwords

Stopwords are typically short, commonly used words that you designate as not significant for a search. They are left out of the FULLTEXT index and ignored when entered in FULLTEXT queries. For example, a search for &#8216;the&#8217; is unsuccessful because it&#8217;s in the default stopword list. For your own customized search, you might create a bigger list (say, with common words from several languages) or a smaller one (for example, a music or movie site where words such as &#8220;The&#8221; in names and titles are significant). The details about customizing the stopword list are in Jimmy&#8217;s blog post.


select count(*), author as "Stopword 1", quote as "Stopword 2", source as "Stopword 3"
  from quotes_multi_col
  where match(author, quote, source) against ('the' in natural language mode);
+----------+------------+------------+------------+
&#124; count(*) &#124; Stopword 1 &#124; Stopword 2 &#124; Stopword 3 &#124;
+----------+------------+------------+------------+
&#124;        0 &#124; NULL       &#124; NULL       &#124; NULL       &#124;
+----------+------------+------------+------------+]]></description>
			<content:encoded><![CDATA[<p>
The InnoDB full-text search capability is an exciting feature. The full-text search itself is generally useful to have in an RDBMS. If an application is using all InnoDB tables except for one that is used for full-text searches, now that last table can be switched to InnoDB. If putting the full-text data in a MyISAM table led to scalability problems, duplication, or a less-than-ideal schema design, now those issues can be addressed.
</p>
<p>
In this post, I&#8217;ll take you through some of the basics of setting up and querying an InnoDB <code>FULLTEXT</code> search index. I&#8217;ll leave the scalability and performance aspects to <a href="http://blogs.innodb.com/wp/2011/07/overview-and-getting-started-with-innodb-fts/">Jimmy&#8217;s</a> and Vinay&#8217;s blog posts, and just use some toy-sized data for demonstration purposes.
</p>
<h3>Creating a Table with a Full-Text Search Index</h3>
<p>
The key component of this feature is an index of type <code>FULLTEXT</code>, applied to one or more columns of an InnoDB table.
</p>
<p>
In <a href="http://blogs.innodb.com/wp/2011/07/overview-and-getting-started-with-innodb-fts/">Jimmy&#8217;s post</a>, he mentions some scalability considerations where you might create the table (including a special <code>FTS_DOC_ID</code> column), load the data, then create the <code>FULLTEXT</code> index afterward. For simplicity (and since the data volume is so small), I&#8217;ll create the table with the index in place, then load the data afterward.
</p>
<p><span></span></p>
<pre>
use test;
-- We will do some commits and rollbacks to demonstrate transactional features.
-- So turn off the default setting that commits immediately after each statement.
set autocommit=0;

drop table if exists quotes;
-- In 5.5 and above, by default this table is an InnoDB table.
-- The full-text search feature lets us define the FULLTEXT index.

create table quotes
  (    id int unsigned auto_increment primary key
    , author varchar(64)    , quote varchar(4000)
    , source varchar(64)
    , fulltext(quote)
  );

-- Get some words and phrases to search for into the table.
insert into quotes (author, quote, source) values
  ('Abraham Lincoln', 'Fourscore and seven years ago...',
  'Gettysburg Address')
, ('George Harrison', 'All those years ago...',
  'Live In Japan')
, ('Arthur C. Clarke', 'Then 10 years ago the monolith was discovered.',
  '2010: The Year We Make Contact')
, ('Benjamin Franklin',
  'Early to bed and early to rise, makes a man healthy, wealthy, and wise.',
  'Poor Richard''s Almanack')
, ('James Thurber',
  'Early to rise and early to bed makes a male healthy and wealthy and dead.',
  'The New Yorker')
, ('K', '1500 hundred years ago, everybody knew that the Earth was the center of the universe.',
  'Men in Black')
;

-- Since this is an InnoDB table, we are mindful of transactions.
commit;
</pre>
<h3>Word and Phrase Search &#8211; Natural Language Mode</h3>
<p>
Once the data is loaded <i>and committed</i>, you can run queries using the <code>MATCH(<em>columns</em>) AGAINST (<em>search expression</em>)</code> operator to do the actual searches. You can combine this operator with all the usual <code>WHERE</code> and similar clauses in the <code>SELECT</code> statement.
</p>
<p>
The simplest kind of search is to find a single word, or a phrase with all words in exact order. For this type of search, use the <code>IN NATURAL LANGUAGE</code> clause inside the <code>AGAINST()</code> call. This technique typically involves a user-entered string that you pass verbatim to the query (of course, after escaping any quotation marks or other special characters to prevent SQL injection attacks).
</p>
<pre>
-- Search for a single word.
select author as "Monolith" from quotes
  where match(quote) against ('monolith' in natural language mode);
+------------------+
| Monolith         |
+------------------+
| Arthur C. Clarke |
+------------------+
1 row in set (0.01 sec)

select author as "Ago" from quotes
  where match(quote) against ('ago' in natural language mode);
+------------------+
| Ago              |
+------------------+
| Abraham Lincoln  |
| George Harrison  |
| Arthur C. Clarke |
| K                |
+------------------+
4 rows in set (0.00 sec)

-- Search for a phrase.
select author as "Years Ago" from quotes
  where match(quote) against ('years ago' in natural language mode);
+------------------+
| Years Ago        |
+------------------+
| Abraham Lincoln  |
| George Harrison  |
| Arthur C. Clarke |
| K                |
+------------------+
4 rows in set (0.00 sec)
</pre>
<h3>AND / OR / NOT Operators &#8211; Boolean Mode</h3>
<p>
For more complicated searches, you can have multiple words and phrases and search for different combinations of optional and required terms, not necessarily in the same order. This technique typically involves several data values that you query from elsewhere, or splitting apart a user-entered string and applying your own rules to the words and phrases inside.
</p>
<pre>
-- Search for a combination of words, not in the same order as the original.
select author as "Ago and Years" from quotes
  where match(quote) against ('+ago +years' in boolean mode);
+------------------+
| Ago and Years    |
+------------------+
| Abraham Lincoln  |
| George Harrison  |
| Arthur C. Clarke |
| K                |
+------------------+
4 rows in set (0.00 sec)

-- Search for other Boolean combinations of words.
select author as "Fourscore or Monolith" from quotes
  where match(quote) against ('fourscore monolith' in boolean mode);
+-----------------------+
| Fourscore or Monolith |
+-----------------------+
| Abraham Lincoln       |
| Arthur C. Clarke      |
+-----------------------+
2 rows in set (0.00 sec)

select author as "Years and not Monolith" from quotes
  where match(quote) against ('+years -monolith' in boolean mode);
+------------------------+
| Years and not Monolith |
+------------------------+
| Abraham Lincoln        |
| George Harrison        |
| K                      |
+------------------------+
3 rows in set (0.00 sec)
</pre>
<h4>Proximity Search</h4>
<p>
Proximity search is a special case of Boolean search using the <code>@</code> operator within the <code>AGAINST()</code> string. You supply 2 or more words, double-quoted, within the single-quoted <code>AGAINST()</code> string, followed by <code>@<em>distance</em></code> to specify how far apart these words can be. The distance represents the maximum number of bytes between the starting points of all these words.
</p>
<pre>
-- The starting points for these words are too far apart
-- (not within 20 bytes), so no results.
select quote as "Too Far Apart" from quotes
  where match(quote) against ('"early wise" @20' in boolean mode);
Empty set (0.00 sec)

-- But the starting points of all words are within 100 bytes,
-- so this query does give results.
select quote as "Early...Wise" from quotes
  where match(quote) against ('"early wise" @100' in boolean mode);
+-------------------------------------------------------------------------+
| Early...Wise                                                            |
+-------------------------------------------------------------------------+
| Early to bed and early to rise, makes a man healthy, wealthy, and wise. |
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)

-- In this case, the smallest distance that produces results is 49.
select quote as "Early...Wise" from quotes
  where match(quote) against ('"early wise" @49' in boolean mode);
+-------------------------------------------------------------------------+
| Early...Wise                                                            |
+-------------------------------------------------------------------------+
| Early to bed and early to rise, makes a man healthy, wealthy, and wise. |
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)

-- Here is an example showing 2 results, with the words close to each other.
select quote as "Early...Bed" from quotes
  where match(quote) against ('"early bed" @20' in boolean mode);
+---------------------------------------------------------------------------+
| Early...Bed                                                               |
+---------------------------------------------------------------------------+
| Early to bed and early to rise, makes a man healthy, wealthy, and wise.   |
| Early to rise and early to bed makes a male healthy and wealthy and dead. |
+---------------------------------------------------------------------------+
2 rows in set (0.00 sec)
</pre>
<h3>Relevance Ranking</h3>
<p>
The relevance ranking is fairly basic, derived from word frequencies within each document and the search data overall. Typically, you would only <code>ORDER BY</code> this value for very simplistic searches of small documents; for any important search you would layer your own ranking logic on top, perhaps with the MySQL relevance value as one factor in the overall rank.
</p>
<pre>
-- Get the relevance of a single word.
select substr(quote,1,20) as "And",
  match(quote) against ('and' in natural language mode) as "Relevance"
  from quotes order by "Relevance" desc;
+----------------------+--------------------+
| And                  | Relevance          |
+----------------------+--------------------+
| Fourscore and seven  | 0.0906190574169159 |
| All those years ago. |                  0 |
| Then 10 years ago th |                  0 |
| Early to bed and ear | 0.1812381148338318 |
| Early to rise and ea | 0.2718571722507477 |
| 1500 hundred years a |                  0 |
+----------------------+--------------------+
6 rows in set (0.00 sec)
</pre>
<h3>Transactions</h3>
<p>
The key idea behind bringing full-text search to InnoDB tables is to make this feature compatible with transactions, so that you can include full-text columns alongside other columns in tables in ways that make sense in terms of schema design, and multiple sessions can update the full-text column data (and/or other columns in the table) simultaneously. The full-text data doesn&#8217;t have to be treated as read-only or read-mostly.
</p>
<p>
As mentioned in <a href="http://blogs.innodb.com/wp/2011/07/overview-and-getting-started-with-innodb-fts/">Jimmy&#8217;s blog post</a>, the table structures that manipulate the full-text data behind the scenes are only updated at <code>COMMIT</code> time. So make sure to insert or update full-text data in one transaction, commit, and then run any full-text queries in a subsequent transaction. (Actually, in the examples below, it looks like the data is taken out of the full-text results as soon as a <code>DELETE</code> is issued, then comes back if the deletion is rolled back. I think that is explained in Jimmy&#8217;s blog post by the discussion about the delete-marking optimization to avoid huge updates to the full-text index for deleted data.)
</p>
<pre>
drop table if exists quotes_uncommitted;

create table quotes_uncommitted
  (
      author varchar(64)
    , quote varchar(4000)
    , source varchar(64)
    , fulltext(quote)
    , primary key (author, quote(128))
  );

-- We insert but don't immediately commit.
insert into quotes_uncommitted select author, quote, source from quotes;
-- Within the same transaction, a full-text search does not see the uncommitted data.
select count(author), author as "Uncommitted Results" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------+
| count(author) | Uncommitted Results |
+---------------+---------------------+
|             0 | NULL                |
+---------------+---------------------+
1 row in set (0.00 sec)

-- If the newly inserted rows are rolled back...
rollback;
-- ...then the full-text search still doesn't see them.
select count(author), author as "Rolled-Back Results" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------+
| count(author) | Rolled-Back Results |
+---------------+---------------------+
|             0 | NULL                |
+---------------+---------------------+
1 row in set (0.00 sec)

-- OK, let's start with some committed data in the table, then empty the table,
-- and then try some FTS queries
-- both before and after the commit.
insert into quotes_uncommitted select author, quote, source from quotes;
commit;
delete from quotes_uncommitted;
select count(author), author as "Deleted but still not committed" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------------------+
| count(author) | Deleted but still not committed |
+---------------+---------------------------------+
|             0 | NULL                            |
+---------------+---------------------------------+
1 row in set (0.00 sec)

rollback;
select count(author), author as "Deleted and rolled back" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-------------------------+
| count(author) | Deleted and rolled back |
+---------------+-------------------------+
|             4 | Abraham Lincoln         |
+---------------+-------------------------+
1 row in set (0.00 sec)

delete from quotes_uncommitted;
commit;
select count(author), author as "Deleted and committed" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-----------------------+
| count(author) | Deleted and committed |
+---------------+-----------------------+
|             0 | NULL                  |
+---------------+-----------------------+
1 row in set (0.00 sec)

insert into quotes_uncommitted select author, quote, source from quotes;
commit;
truncate table quotes_uncommitted;
select count(author), author as "Truncated" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-----------+
| count(author) | Truncated |
+---------------+-----------+
|             0 | NULL      |
+---------------+-----------+
1 row in set (0.00 sec)
</pre>
<h3>Multi-Column Searches</h3>
<p>
Although you can only have one <code>FULLTEXT</code> index in an InnoDB table, that index can apply to multiple columns, allowing you to search when you aren&#8217;t sure which column contains the term. With a multi-column index, we can <code>MATCH()</code> against all the columns to find words that appear in any of those columns. Always reference all the same columns in the <code>MATCH()</code> clause as in the <code>FULLTEXT</code> index definition, because the information about which column the words appear in is not included in the full-text search data.
</p>
<pre>
drop table if exists quotes_multi_col;

create table quotes_multi_col
  (
    id int unsigned auto_increment primary key
    , author varchar(64)
    , quote varchar(4000)
    , source varchar(64)
    , fulltext(author, quote, source)
  );

insert into quotes_multi_col select * from quotes;
commit;

select author as "Poor 1 (NL)", substr(quote,1,15) as "Poor 2 (NL)", source as "Poor 3 (NL)" from
  quotes_multi_col where match(author, quote, source)
  against ('poor' in natural language mode);
+-------------------+-----------------+-------------------------+
| Poor 1 (NL)       | Poor 2 (NL)     | Poor 3 (NL)             |
+-------------------+-----------------+-------------------------+
| Benjamin Franklin | Early to bed an | Poor Richard's Almanack |
+-------------------+-----------------+-------------------------+
1 row in set (0.00 sec)

select author as "Poor 1 (BOOL)", substr(quote,1,15) as "Poor 2 (BOOL)", source as "Poor 3 (BOOL)"
  from quotes_multi_col where match(author, quote, source)
  against ('poor' in boolean mode);
+-------------------+-----------------+-------------------------+
| Poor 1 (BOOL)     | Poor 2 (BOOL)   | Poor 3 (BOOL)           |
+-------------------+-----------------+-------------------------+
| Benjamin Franklin | Early to bed an | Poor Richard's Almanack |
+-------------------+-----------------+-------------------------+
1 row in set (0.00 sec)

select author as "Clarke 1 (NL)", substr(quote,1,15) as "Clarke 2 (NL)", source as "Clarke 3 (NL)"
  from quotes_multi_col where match(author, quote, source)
  against ('clarke' in natural language mode);
+------------------+-----------------+--------------------------------+
| Clarke 1 (NL)    | Clarke 2 (NL)   | Clarke 3 (NL)                  |
+------------------+-----------------+--------------------------------+
| Arthur C. Clarke | Then 10 years a | 2010: The Year We Make Contact |
+------------------+-----------------+--------------------------------+
1 row in set (0.00 sec)
</pre>
<h3>Interaction with Other Indexes</h3>
<p>
Remember that the design of your primary key index and secondary indexes is a big factor in query performance for InnoDB tables.
</p>
<ul>
<li>You can include parts (prefixes) of the full-text column(s) within the primary key.</li>
<li>However, that might not be a good idea if (a) the associated columns will ever be updated &#8212; which causes an expensive reorganization within the InnoDB table, or (b) if the table will have any other secondary indexes &#8212; the primary key values for a row are duplicated in the entry for that row in every secondary index, making index operations require more I/O and memory.</li>
<li>As mentioned in <a href="http://blogs.innodb.com/wp/2011/07/overview-and-getting-started-with-innodb-fts/">Jimmy&#8217;s blog post</a>, adding the <code>FULLTEXT</code> index to the table is going to create a new column and associated index in the original table, so you could set up the column and index ahead of time, to avoid table reorganization later.</li>
<li>You can use the unique constraint of the primary key or a <code>UNIQUE</code> index to prevent duplicate values or combinations of values from being entered.</li>
<li>You can use the not-null constraint of the primary key to prevent blank values or combinations of values from being entered.</li>
<li>For the Labs release, the InnoDB <code>FULLTEXT</code> processing isn&#8217;t integrated with the MySQL optimizer and its estimates for which index is best to use, so don&#8217;t draw conclusions about performance characteristics from this early preview.</li>
</ul>
<h3>Stopwords</h3>
<p>
Stopwords are typically short, commonly used words that you designate as not significant for a search. They are left out of the <code>FULLTEXT</code> index and ignored when entered in <code>FULLTEXT</code> queries. For example, a search for &#8216;the&#8217; is unsuccessful because it&#8217;s in the default stopword list. For your own customized search, you might create a bigger list (say, with common words from several languages) or a smaller one (for example, a music or movie site where words such as &#8220;The&#8221; in names and titles are significant). The details about customizing the stopword list are in <a href="http://blogs.innodb.com/wp/2011/07/overview-and-getting-started-with-innodb-fts/">Jimmy&#8217;s blog post</a>.
</p>
<pre>
select count(*), author as "Stopword 1", quote as "Stopword 2", source as "Stopword 3"
  from quotes_multi_col
  where match(author, quote, source) against ('the' in natural language mode);
+----------+------------+------------+------------+
| count(*) | Stopword 1 | Stopword 2 | Stopword 3 |
+----------+------------+------------+------------+
|        0 | NULL       | NULL       | NULL       |
+----------+------------+------------+------------+
</pre><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=29493&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=29493&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://planetmysql.ru/2011/07/27/innodb-full-text-search-tutorial/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>InnoDB Full-Text Search Tutorial</title>
		<link>http://blogs.innodb.com/wp/2011/07/innodb-full-text-search-tutorial/?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=innodb-full-text-search-tutorial</link>
		<comments>http://blogs.innodb.com/wp/2011/07/innodb-full-text-search-tutorial/#comments</comments>
		<pubDate>Wed, 27 Jul 2011 00:04:19 +0000</pubDate>
		<dc:creator>John Russell</dc:creator>
				<category><![CDATA[5.6]]></category>
		<category><![CDATA[Feature]]></category>
		<category><![CDATA[fts]]></category>
		<category><![CDATA[Search]]></category>

		<guid isPermaLink="false">http://blogs.innodb.com/wp/?p=1115</guid>
		<description><![CDATA[The InnoDB full-text search capability is an exciting feature. The full-text search itself is generally useful to have in an RDBMS. If an application is using all InnoDB tables except for one that is used for full-text searches, now that last table can be switched to InnoDB. If putting the full-text data in a MyISAM table led to scalability problems, duplication, or a less-than-ideal schema design, now those issues can be addressed.


In this post, I&#8217;ll take you through some of the basics of setting up and querying an InnoDB FULLTEXT search index. I&#8217;ll leave the scalability and performance aspects to Jimmy&#8217;s and Vinay&#8217;s blog posts, and just use some toy-sized data for demonstration purposes.

Creating a Table with a Full-Text Search Index

The key component of this feature is an index of type FULLTEXT, applied to one or more columns of an InnoDB table.


In Jimmy&#8217;s post, he mentions some scalability considerations where you might create the table (including a special FTS_DOC_ID column), load the data, then create the FULLTEXT index afterward. For simplicity (and since the data volume is so small), I&#8217;ll create the table with the index in place, then load the data afterward.



use test;
-- We will do some commits and rollbacks to demonstrate transactional features.
-- So turn off the default setting that commits immediately after each statement.
set autocommit=0;

drop table if exists quotes;
-- In 5.5 and above, by default this table is an InnoDB table.
-- The full-text search feature lets us define the FULLTEXT index.

create table quotes
  (    id int unsigned auto_increment primary key
    , author varchar(64)    , quote varchar(4000)
    , source varchar(64)
    , fulltext(quote)
  );

-- Get some words and phrases to search for into the table.
insert into quotes (author, quote, source) values
  ('Abraham Lincoln', 'Fourscore and seven years ago...',
  'Gettysburg Address')
, ('George Harrison', 'All those years ago...',
  'Live In Japan')
, ('Arthur C. Clarke', 'Then 10 years ago the monolith was discovered.',
  '2010: The Year We Make Contact')
, ('Benjamin Franklin',
  'Early to bed and early to rise, makes a man healthy, wealthy, and wise.',
  'Poor Richard''s Almanack')
, ('James Thurber',
  'Early to rise and early to bed makes a male healthy and wealthy and dead.',
  'The New Yorker')
, ('K', '1500 hundred years ago, everybody knew that the Earth was the center of the universe.',
  'Men in Black')
;

-- Since this is an InnoDB table, we are mindful of transactions.
commit;

Word and Phrase Search &#8211; Natural Language Mode

Once the data is loaded and committed, you can run queries using the MATCH(columns) AGAINST (search expression) operator to do the actual searches. You can combine this operator with all the usual WHERE and similar clauses in the SELECT statement.


The simplest kind of search is to find a single word, or a phrase with all words in exact order. For this type of search, use the IN NATURAL LANGUAGE clause inside the AGAINST() call. This technique typically involves a user-entered string that you pass verbatim to the query (of course, after escaping any quotation marks or other special characters to prevent SQL injection attacks).


-- Search for a single word.
select author as "Monolith" from quotes
  where match(quote) against ('monolith' in natural language mode);
+------------------+
&#124; Monolith         &#124;
+------------------+
&#124; Arthur C. Clarke &#124;
+------------------+
1 row in set (0.01 sec)

select author as "Ago" from quotes
  where match(quote) against ('ago' in natural language mode);
+------------------+
&#124; Ago              &#124;
+------------------+
&#124; Abraham Lincoln  &#124;
&#124; George Harrison  &#124;
&#124; Arthur C. Clarke &#124;
&#124; K                &#124;
+------------------+
4 rows in set (0.00 sec)

-- Search for a phrase.
select author as "Years Ago" from quotes
  where match(quote) against ('years ago' in natural language mode);
+------------------+
&#124; Years Ago        &#124;
+------------------+
&#124; Abraham Lincoln  &#124;
&#124; George Harrison  &#124;
&#124; Arthur C. Clarke &#124;
&#124; K                &#124;
+------------------+
4 rows in set (0.00 sec)

AND / OR / NOT Operators &#8211; Boolean Mode

For more complicated searches, you can have multiple words and phrases and search for different combinations of optional and required terms, not necessarily in the same order. This technique typically involves several data values that you query from elsewhere, or splitting apart a user-entered string and applying your own rules to the words and phrases inside.


-- Search for a combination of words, not in the same order as the original.
select author as "Ago and Years" from quotes
  where match(quote) against ('+ago +years' in boolean mode);
+------------------+
&#124; Ago and Years    &#124;
+------------------+
&#124; Abraham Lincoln  &#124;
&#124; George Harrison  &#124;
&#124; Arthur C. Clarke &#124;
&#124; K                &#124;
+------------------+
4 rows in set (0.00 sec)

-- Search for other Boolean combinations of words.
select author as "Fourscore or Monolith" from quotes
  where match(quote) against ('fourscore monolith' in boolean mode);
+-----------------------+
&#124; Fourscore or Monolith &#124;
+-----------------------+
&#124; Abraham Lincoln       &#124;
&#124; Arthur C. Clarke      &#124;
+-----------------------+
2 rows in set (0.00 sec)

select author as "Years and not Monolith" from quotes
  where match(quote) against ('+years -monolith' in boolean mode);
+------------------------+
&#124; Years and not Monolith &#124;
+------------------------+
&#124; Abraham Lincoln        &#124;
&#124; George Harrison        &#124;
&#124; K                      &#124;
+------------------------+
3 rows in set (0.00 sec)

Proximity Search

Proximity search is a special case of Boolean search using the @ operator within the AGAINST() string. You supply 2 or more words, double-quoted, within the single-quoted AGAINST() string, followed by @distance to specify how far apart these words can be. The distance represents the maximum number of bytes between the starting points of all these words.


-- The starting points for these words are too far apart
-- (not within 20 bytes), so no results.
select quote as "Too Far Apart" from quotes
  where match(quote) against ('"early wise" @20' in boolean mode);
Empty set (0.00 sec)

-- But the starting points of all words are within 100 bytes,
-- so this query does give results.
select quote as "Early...Wise" from quotes
  where match(quote) against ('"early wise" @100' in boolean mode);
+-------------------------------------------------------------------------+
&#124; Early...Wise                                                            &#124;
+-------------------------------------------------------------------------+
&#124; Early to bed and early to rise, makes a man healthy, wealthy, and wise. &#124;
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)

-- In this case, the smallest distance that produces results is 49.
select quote as "Early...Wise" from quotes
  where match(quote) against ('"early wise" @49' in boolean mode);
+-------------------------------------------------------------------------+
&#124; Early...Wise                                                            &#124;
+-------------------------------------------------------------------------+
&#124; Early to bed and early to rise, makes a man healthy, wealthy, and wise. &#124;
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)

-- Here is an example showing 2 results, with the words close to each other.
select quote as "Early...Bed" from quotes
  where match(quote) against ('"early bed" @20' in boolean mode);
+---------------------------------------------------------------------------+
&#124; Early...Bed                                                               &#124;
+---------------------------------------------------------------------------+
&#124; Early to bed and early to rise, makes a man healthy, wealthy, and wise.   &#124;
&#124; Early to rise and early to bed makes a male healthy and wealthy and dead. &#124;
+---------------------------------------------------------------------------+
2 rows in set (0.00 sec)

Relevance Ranking

The relevance ranking is fairly basic, derived from word frequencies within each document and the search data overall. Typically, you would only ORDER BY this value for very simplistic searches of small documents; for any important search you would layer your own ranking logic on top, perhaps with the MySQL relevance value as one factor in the overall rank.


-- Get the relevance of a single word.
select substr(quote,1,20) as "And",
  match(quote) against ('and' in natural language mode) as "Relevance"
  from quotes order by "Relevance" desc;
+----------------------+--------------------+
&#124; And                  &#124; Relevance          &#124;
+----------------------+--------------------+
&#124; Fourscore and seven  &#124; 0.0906190574169159 &#124;
&#124; All those years ago. &#124;                  0 &#124;
&#124; Then 10 years ago th &#124;                  0 &#124;
&#124; Early to bed and ear &#124; 0.1812381148338318 &#124;
&#124; Early to rise and ea &#124; 0.2718571722507477 &#124;
&#124; 1500 hundred years a &#124;                  0 &#124;
+----------------------+--------------------+
6 rows in set (0.00 sec)

Transactions

The key idea behind bringing full-text search to InnoDB tables is to make this feature compatible with transactions, so that you can include full-text columns alongside other columns in tables in ways that make sense in terms of schema design, and multiple sessions can update the full-text column data (and/or other columns in the table) simultaneously. The full-text data doesn&#8217;t have to be treated as read-only or read-mostly.


As mentioned in Jimmy&#8217;s blog post, the table structures that manipulate the full-text data behind the scenes are only updated at COMMIT time. So make sure to insert or update full-text data in one transaction, commit, and then run any full-text queries in a subsequent transaction. (Actually, in the examples below, it looks like the data is taken out of the full-text results as soon as a DELETE is issued, then comes back if the deletion is rolled back. I think that is explained in Jimmy&#8217;s blog post by the discussion about the delete-marking optimization to avoid huge updates to the full-text index for deleted data.)


drop table if exists quotes_uncommitted;

create table quotes_uncommitted
  (
      author varchar(64)
    , quote varchar(4000)
    , source varchar(64)
    , fulltext(quote)
    , primary key (author, quote(128))
  );

-- We insert but don't immediately commit.
insert into quotes_uncommitted select author, quote, source from quotes;
-- Within the same transaction, a full-text search does not see the uncommitted data.
select count(author), author as "Uncommitted Results" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------+
&#124; count(author) &#124; Uncommitted Results &#124;
+---------------+---------------------+
&#124;             0 &#124; NULL                &#124;
+---------------+---------------------+
1 row in set (0.00 sec)

-- If the newly inserted rows are rolled back...
rollback;
-- ...then the full-text search still doesn't see them.
select count(author), author as "Rolled-Back Results" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------+
&#124; count(author) &#124; Rolled-Back Results &#124;
+---------------+---------------------+
&#124;             0 &#124; NULL                &#124;
+---------------+---------------------+
1 row in set (0.00 sec)

-- OK, let's start with some committed data in the table, then empty the table,
-- and then try some FTS queries
-- both before and after the commit.
insert into quotes_uncommitted select author, quote, source from quotes;
commit;
delete from quotes_uncommitted;
select count(author), author as "Deleted but still not committed" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------------------+
&#124; count(author) &#124; Deleted but still not committed &#124;
+---------------+---------------------------------+
&#124;             0 &#124; NULL                            &#124;
+---------------+---------------------------------+
1 row in set (0.00 sec)

rollback;
select count(author), author as "Deleted and rolled back" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-------------------------+
&#124; count(author) &#124; Deleted and rolled back &#124;
+---------------+-------------------------+
&#124;             4 &#124; Abraham Lincoln         &#124;
+---------------+-------------------------+
1 row in set (0.00 sec)

delete from quotes_uncommitted;
commit;
select count(author), author as "Deleted and committed" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-----------------------+
&#124; count(author) &#124; Deleted and committed &#124;
+---------------+-----------------------+
&#124;             0 &#124; NULL                  &#124;
+---------------+-----------------------+
1 row in set (0.00 sec)

insert into quotes_uncommitted select author, quote, source from quotes;
commit;
truncate table quotes_uncommitted;
select count(author), author as "Truncated" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-----------+
&#124; count(author) &#124; Truncated &#124;
+---------------+-----------+
&#124;             0 &#124; NULL      &#124;
+---------------+-----------+
1 row in set (0.00 sec)

Multi-Column Searches

Although you can only have one FULLTEXT index in an InnoDB table, that index can apply to multiple columns, allowing you to search when you aren&#8217;t sure which column contains the term. With a multi-column index, we can MATCH() against all the columns to find words that appear in any of those columns. Always reference all the same columns in the MATCH() clause as in the FULLTEXT index definition, because the information about which column the words appear in is not included in the full-text search data.


drop table if exists quotes_multi_col;

create table quotes_multi_col
  (
    id int unsigned auto_increment primary key
    , author varchar(64)
    , quote varchar(4000)
    , source varchar(64)
    , fulltext(author, quote, source)
  );

insert into quotes_multi_col select * from quotes;
commit;

select author as "Poor 1 (NL)", substr(quote,1,15) as "Poor 2 (NL)", source as "Poor 3 (NL)" from
  quotes_multi_col where match(author, quote, source)
  against ('poor' in natural language mode);
+-------------------+-----------------+-------------------------+
&#124; Poor 1 (NL)       &#124; Poor 2 (NL)     &#124; Poor 3 (NL)             &#124;
+-------------------+-----------------+-------------------------+
&#124; Benjamin Franklin &#124; Early to bed an &#124; Poor Richard's Almanack &#124;
+-------------------+-----------------+-------------------------+
1 row in set (0.00 sec)

select author as "Poor 1 (BOOL)", substr(quote,1,15) as "Poor 2 (BOOL)", source as "Poor 3 (BOOL)"
  from quotes_multi_col where match(author, quote, source)
  against ('poor' in boolean mode);
+-------------------+-----------------+-------------------------+
&#124; Poor 1 (BOOL)     &#124; Poor 2 (BOOL)   &#124; Poor 3 (BOOL)           &#124;
+-------------------+-----------------+-------------------------+
&#124; Benjamin Franklin &#124; Early to bed an &#124; Poor Richard's Almanack &#124;
+-------------------+-----------------+-------------------------+
1 row in set (0.00 sec)

select author as "Clarke 1 (NL)", substr(quote,1,15) as "Clarke 2 (NL)", source as "Clarke 3 (NL)"
  from quotes_multi_col where match(author, quote, source)
  against ('clarke' in natural language mode);
+------------------+-----------------+--------------------------------+
&#124; Clarke 1 (NL)    &#124; Clarke 2 (NL)   &#124; Clarke 3 (NL)                  &#124;
+------------------+-----------------+--------------------------------+
&#124; Arthur C. Clarke &#124; Then 10 years a &#124; 2010: The Year We Make Contact &#124;
+------------------+-----------------+--------------------------------+
1 row in set (0.00 sec)

Interaction with Other Indexes

Remember that the design of your primary key index and secondary indexes is a big factor in query performance for InnoDB tables.


You can include parts (prefixes) of the full-text column(s) within the primary key.
However, that might not be a good idea if (a) the associated columns will ever be updated &#8212; which causes an expensive reorganization within the InnoDB table, or (b) if the table will have any other secondary indexes &#8212; the primary key values for a row are duplicated in the entry for that row in every secondary index, making index operations require more I/O and memory.
As mentioned in Jimmy&#8217;s blog post, adding the FULLTEXT index to the table is going to create a new column and associated index in the original table, so you could set up the column and index ahead of time, to avoid table reorganization later.
You can use the unique constraint of the primary key or a UNIQUE index to prevent duplicate values or combinations of values from being entered.
You can use the not-null constraint of the primary key to prevent blank values or combinations of values from being entered.
For the Labs release, the InnoDB FULLTEXT processing isn&#8217;t integrated with the MySQL optimizer and its estimates for which index is best to use, so don&#8217;t draw conclusions about performance characteristics from this early preview.

Stopwords

Stopwords are typically short, commonly used words that you designate as not significant for a search. They are left out of the FULLTEXT index and ignored when entered in FULLTEXT queries. For example, a search for &#8216;the&#8217; is unsuccessful because it&#8217;s in the default stopword list. For your own customized search, you might create a bigger list (say, with common words from several languages) or a smaller one (for example, a music or movie site where words such as &#8220;The&#8221; in names and titles are significant). The details about customizing the stopword list are in Jimmy&#8217;s blog post.


select count(*), author as "Stopword 1", quote as "Stopword 2", source as "Stopword 3"
  from quotes_multi_col
  where match(author, quote, source) against ('the' in natural language mode);
+----------+------------+------------+------------+
&#124; count(*) &#124; Stopword 1 &#124; Stopword 2 &#124; Stopword 3 &#124;
+----------+------------+------------+------------+
&#124;        0 &#124; NULL       &#124; NULL       &#124; NULL       &#124;
+----------+------------+------------+------------+]]></description>
			<content:encoded><![CDATA[<p>
The InnoDB full-text search capability is an exciting feature. The full-text search itself is generally useful to have in an RDBMS. If an application is using all InnoDB tables except for one that is used for full-text searches, now that last table can be switched to InnoDB. If putting the full-text data in a MyISAM table led to scalability problems, duplication, or a less-than-ideal schema design, now those issues can be addressed.
</p>
<p>
In this post, I&#8217;ll take you through some of the basics of setting up and querying an InnoDB <code>FULLTEXT</code> search index. I&#8217;ll leave the scalability and performance aspects to <a href="http://blogs.innodb.com/wp/2011/07/overview-and-getting-started-with-innodb-fts/">Jimmy&#8217;s</a> and Vinay&#8217;s blog posts, and just use some toy-sized data for demonstration purposes.
</p>
<h3>Creating a Table with a Full-Text Search Index</h3>
<p>
The key component of this feature is an index of type <code>FULLTEXT</code>, applied to one or more columns of an InnoDB table.
</p>
<p>
In <a href="http://blogs.innodb.com/wp/2011/07/overview-and-getting-started-with-innodb-fts/">Jimmy&#8217;s post</a>, he mentions some scalability considerations where you might create the table (including a special <code>FTS_DOC_ID</code> column), load the data, then create the <code>FULLTEXT</code> index afterward. For simplicity (and since the data volume is so small), I&#8217;ll create the table with the index in place, then load the data afterward.
</p>
<p><span></span></p>
<pre>
use test;
-- We will do some commits and rollbacks to demonstrate transactional features.
-- So turn off the default setting that commits immediately after each statement.
set autocommit=0;

drop table if exists quotes;
-- In 5.5 and above, by default this table is an InnoDB table.
-- The full-text search feature lets us define the FULLTEXT index.

create table quotes
  (    id int unsigned auto_increment primary key
    , author varchar(64)    , quote varchar(4000)
    , source varchar(64)
    , fulltext(quote)
  );

-- Get some words and phrases to search for into the table.
insert into quotes (author, quote, source) values
  ('Abraham Lincoln', 'Fourscore and seven years ago...',
  'Gettysburg Address')
, ('George Harrison', 'All those years ago...',
  'Live In Japan')
, ('Arthur C. Clarke', 'Then 10 years ago the monolith was discovered.',
  '2010: The Year We Make Contact')
, ('Benjamin Franklin',
  'Early to bed and early to rise, makes a man healthy, wealthy, and wise.',
  'Poor Richard''s Almanack')
, ('James Thurber',
  'Early to rise and early to bed makes a male healthy and wealthy and dead.',
  'The New Yorker')
, ('K', '1500 hundred years ago, everybody knew that the Earth was the center of the universe.',
  'Men in Black')
;

-- Since this is an InnoDB table, we are mindful of transactions.
commit;
</pre>
<h3>Word and Phrase Search &#8211; Natural Language Mode</h3>
<p>
Once the data is loaded <i>and committed</i>, you can run queries using the <code>MATCH(<em>columns</em>) AGAINST (<em>search expression</em>)</code> operator to do the actual searches. You can combine this operator with all the usual <code>WHERE</code> and similar clauses in the <code>SELECT</code> statement.
</p>
<p>
The simplest kind of search is to find a single word, or a phrase with all words in exact order. For this type of search, use the <code>IN NATURAL LANGUAGE</code> clause inside the <code>AGAINST()</code> call. This technique typically involves a user-entered string that you pass verbatim to the query (of course, after escaping any quotation marks or other special characters to prevent SQL injection attacks).
</p>
<pre>
-- Search for a single word.
select author as "Monolith" from quotes
  where match(quote) against ('monolith' in natural language mode);
+------------------+
| Monolith         |
+------------------+
| Arthur C. Clarke |
+------------------+
1 row in set (0.01 sec)

select author as "Ago" from quotes
  where match(quote) against ('ago' in natural language mode);
+------------------+
| Ago              |
+------------------+
| Abraham Lincoln  |
| George Harrison  |
| Arthur C. Clarke |
| K                |
+------------------+
4 rows in set (0.00 sec)

-- Search for a phrase.
select author as "Years Ago" from quotes
  where match(quote) against ('years ago' in natural language mode);
+------------------+
| Years Ago        |
+------------------+
| Abraham Lincoln  |
| George Harrison  |
| Arthur C. Clarke |
| K                |
+------------------+
4 rows in set (0.00 sec)
</pre>
<h3>AND / OR / NOT Operators &#8211; Boolean Mode</h3>
<p>
For more complicated searches, you can have multiple words and phrases and search for different combinations of optional and required terms, not necessarily in the same order. This technique typically involves several data values that you query from elsewhere, or splitting apart a user-entered string and applying your own rules to the words and phrases inside.
</p>
<pre>
-- Search for a combination of words, not in the same order as the original.
select author as "Ago and Years" from quotes
  where match(quote) against ('+ago +years' in boolean mode);
+------------------+
| Ago and Years    |
+------------------+
| Abraham Lincoln  |
| George Harrison  |
| Arthur C. Clarke |
| K                |
+------------------+
4 rows in set (0.00 sec)

-- Search for other Boolean combinations of words.
select author as "Fourscore or Monolith" from quotes
  where match(quote) against ('fourscore monolith' in boolean mode);
+-----------------------+
| Fourscore or Monolith |
+-----------------------+
| Abraham Lincoln       |
| Arthur C. Clarke      |
+-----------------------+
2 rows in set (0.00 sec)

select author as "Years and not Monolith" from quotes
  where match(quote) against ('+years -monolith' in boolean mode);
+------------------------+
| Years and not Monolith |
+------------------------+
| Abraham Lincoln        |
| George Harrison        |
| K                      |
+------------------------+
3 rows in set (0.00 sec)
</pre>
<h4>Proximity Search</h4>
<p>
Proximity search is a special case of Boolean search using the <code>@</code> operator within the <code>AGAINST()</code> string. You supply 2 or more words, double-quoted, within the single-quoted <code>AGAINST()</code> string, followed by <code>@<em>distance</em></code> to specify how far apart these words can be. The distance represents the maximum number of bytes between the starting points of all these words.
</p>
<pre>
-- The starting points for these words are too far apart
-- (not within 20 bytes), so no results.
select quote as "Too Far Apart" from quotes
  where match(quote) against ('"early wise" @20' in boolean mode);
Empty set (0.00 sec)

-- But the starting points of all words are within 100 bytes,
-- so this query does give results.
select quote as "Early...Wise" from quotes
  where match(quote) against ('"early wise" @100' in boolean mode);
+-------------------------------------------------------------------------+
| Early...Wise                                                            |
+-------------------------------------------------------------------------+
| Early to bed and early to rise, makes a man healthy, wealthy, and wise. |
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)

-- In this case, the smallest distance that produces results is 49.
select quote as "Early...Wise" from quotes
  where match(quote) against ('"early wise" @49' in boolean mode);
+-------------------------------------------------------------------------+
| Early...Wise                                                            |
+-------------------------------------------------------------------------+
| Early to bed and early to rise, makes a man healthy, wealthy, and wise. |
+-------------------------------------------------------------------------+
1 row in set (0.00 sec)

-- Here is an example showing 2 results, with the words close to each other.
select quote as "Early...Bed" from quotes
  where match(quote) against ('"early bed" @20' in boolean mode);
+---------------------------------------------------------------------------+
| Early...Bed                                                               |
+---------------------------------------------------------------------------+
| Early to bed and early to rise, makes a man healthy, wealthy, and wise.   |
| Early to rise and early to bed makes a male healthy and wealthy and dead. |
+---------------------------------------------------------------------------+
2 rows in set (0.00 sec)
</pre>
<h3>Relevance Ranking</h3>
<p>
The relevance ranking is fairly basic, derived from word frequencies within each document and the search data overall. Typically, you would only <code>ORDER BY</code> this value for very simplistic searches of small documents; for any important search you would layer your own ranking logic on top, perhaps with the MySQL relevance value as one factor in the overall rank.
</p>
<pre>
-- Get the relevance of a single word.
select substr(quote,1,20) as "And",
  match(quote) against ('and' in natural language mode) as "Relevance"
  from quotes order by "Relevance" desc;
+----------------------+--------------------+
| And                  | Relevance          |
+----------------------+--------------------+
| Fourscore and seven  | 0.0906190574169159 |
| All those years ago. |                  0 |
| Then 10 years ago th |                  0 |
| Early to bed and ear | 0.1812381148338318 |
| Early to rise and ea | 0.2718571722507477 |
| 1500 hundred years a |                  0 |
+----------------------+--------------------+
6 rows in set (0.00 sec)
</pre>
<h3>Transactions</h3>
<p>
The key idea behind bringing full-text search to InnoDB tables is to make this feature compatible with transactions, so that you can include full-text columns alongside other columns in tables in ways that make sense in terms of schema design, and multiple sessions can update the full-text column data (and/or other columns in the table) simultaneously. The full-text data doesn&#8217;t have to be treated as read-only or read-mostly.
</p>
<p>
As mentioned in <a href="http://blogs.innodb.com/wp/2011/07/overview-and-getting-started-with-innodb-fts/">Jimmy&#8217;s blog post</a>, the table structures that manipulate the full-text data behind the scenes are only updated at <code>COMMIT</code> time. So make sure to insert or update full-text data in one transaction, commit, and then run any full-text queries in a subsequent transaction. (Actually, in the examples below, it looks like the data is taken out of the full-text results as soon as a <code>DELETE</code> is issued, then comes back if the deletion is rolled back. I think that is explained in Jimmy&#8217;s blog post by the discussion about the delete-marking optimization to avoid huge updates to the full-text index for deleted data.)
</p>
<pre>
drop table if exists quotes_uncommitted;

create table quotes_uncommitted
  (
      author varchar(64)
    , quote varchar(4000)
    , source varchar(64)
    , fulltext(quote)
    , primary key (author, quote(128))
  );

-- We insert but don't immediately commit.
insert into quotes_uncommitted select author, quote, source from quotes;
-- Within the same transaction, a full-text search does not see the uncommitted data.
select count(author), author as "Uncommitted Results" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------+
| count(author) | Uncommitted Results |
+---------------+---------------------+
|             0 | NULL                |
+---------------+---------------------+
1 row in set (0.00 sec)

-- If the newly inserted rows are rolled back...
rollback;
-- ...then the full-text search still doesn't see them.
select count(author), author as "Rolled-Back Results" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------+
| count(author) | Rolled-Back Results |
+---------------+---------------------+
|             0 | NULL                |
+---------------+---------------------+
1 row in set (0.00 sec)

-- OK, let's start with some committed data in the table, then empty the table,
-- and then try some FTS queries
-- both before and after the commit.
insert into quotes_uncommitted select author, quote, source from quotes;
commit;
delete from quotes_uncommitted;
select count(author), author as "Deleted but still not committed" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+---------------------------------+
| count(author) | Deleted but still not committed |
+---------------+---------------------------------+
|             0 | NULL                            |
+---------------+---------------------------------+
1 row in set (0.00 sec)

rollback;
select count(author), author as "Deleted and rolled back" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-------------------------+
| count(author) | Deleted and rolled back |
+---------------+-------------------------+
|             4 | Abraham Lincoln         |
+---------------+-------------------------+
1 row in set (0.00 sec)

delete from quotes_uncommitted;
commit;
select count(author), author as "Deleted and committed" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-----------------------+
| count(author) | Deleted and committed |
+---------------+-----------------------+
|             0 | NULL                  |
+---------------+-----------------------+
1 row in set (0.00 sec)

insert into quotes_uncommitted select author, quote, source from quotes;
commit;
truncate table quotes_uncommitted;
select count(author), author as "Truncated" from quotes_uncommitted
  where match(quote) against ('ago' in natural language mode);
+---------------+-----------+
| count(author) | Truncated |
+---------------+-----------+
|             0 | NULL      |
+---------------+-----------+
1 row in set (0.00 sec)
</pre>
<h3>Multi-Column Searches</h3>
<p>
Although you can only have one <code>FULLTEXT</code> index in an InnoDB table, that index can apply to multiple columns, allowing you to search when you aren&#8217;t sure which column contains the term. With a multi-column index, we can <code>MATCH()</code> against all the columns to find words that appear in any of those columns. Always reference all the same columns in the <code>MATCH()</code> clause as in the <code>FULLTEXT</code> index definition, because the information about which column the words appear in is not included in the full-text search data.
</p>
<pre>
drop table if exists quotes_multi_col;

create table quotes_multi_col
  (
    id int unsigned auto_increment primary key
    , author varchar(64)
    , quote varchar(4000)
    , source varchar(64)
    , fulltext(author, quote, source)
  );

insert into quotes_multi_col select * from quotes;
commit;

select author as "Poor 1 (NL)", substr(quote,1,15) as "Poor 2 (NL)", source as "Poor 3 (NL)" from
  quotes_multi_col where match(author, quote, source)
  against ('poor' in natural language mode);
+-------------------+-----------------+-------------------------+
| Poor 1 (NL)       | Poor 2 (NL)     | Poor 3 (NL)             |
+-------------------+-----------------+-------------------------+
| Benjamin Franklin | Early to bed an | Poor Richard's Almanack |
+-------------------+-----------------+-------------------------+
1 row in set (0.00 sec)

select author as "Poor 1 (BOOL)", substr(quote,1,15) as "Poor 2 (BOOL)", source as "Poor 3 (BOOL)"
  from quotes_multi_col where match(author, quote, source)
  against ('poor' in boolean mode);
+-------------------+-----------------+-------------------------+
| Poor 1 (BOOL)     | Poor 2 (BOOL)   | Poor 3 (BOOL)           |
+-------------------+-----------------+-------------------------+
| Benjamin Franklin | Early to bed an | Poor Richard's Almanack |
+-------------------+-----------------+-------------------------+
1 row in set (0.00 sec)

select author as "Clarke 1 (NL)", substr(quote,1,15) as "Clarke 2 (NL)", source as "Clarke 3 (NL)"
  from quotes_multi_col where match(author, quote, source)
  against ('clarke' in natural language mode);
+------------------+-----------------+--------------------------------+
| Clarke 1 (NL)    | Clarke 2 (NL)   | Clarke 3 (NL)                  |
+------------------+-----------------+--------------------------------+
| Arthur C. Clarke | Then 10 years a | 2010: The Year We Make Contact |
+------------------+-----------------+--------------------------------+
1 row in set (0.00 sec)
</pre>
<h3>Interaction with Other Indexes</h3>
<p>
Remember that the design of your primary key index and secondary indexes is a big factor in query performance for InnoDB tables.
</p>
<ul>
<li>You can include parts (prefixes) of the full-text column(s) within the primary key.</li>
<li>However, that might not be a good idea if (a) the associated columns will ever be updated &#8212; which causes an expensive reorganization within the InnoDB table, or (b) if the table will have any other secondary indexes &#8212; the primary key values for a row are duplicated in the entry for that row in every secondary index, making index operations require more I/O and memory.</li>
<li>As mentioned in <a href="http://blogs.innodb.com/wp/2011/07/overview-and-getting-started-with-innodb-fts/">Jimmy&#8217;s blog post</a>, adding the <code>FULLTEXT</code> index to the table is going to create a new column and associated index in the original table, so you could set up the column and index ahead of time, to avoid table reorganization later.</li>
<li>You can use the unique constraint of the primary key or a <code>UNIQUE</code> index to prevent duplicate values or combinations of values from being entered.</li>
<li>You can use the not-null constraint of the primary key to prevent blank values or combinations of values from being entered.</li>
<li>For the Labs release, the InnoDB <code>FULLTEXT</code> processing isn&#8217;t integrated with the MySQL optimizer and its estimates for which index is best to use, so don&#8217;t draw conclusions about performance characteristics from this early preview.</li>
</ul>
<h3>Stopwords</h3>
<p>
Stopwords are typically short, commonly used words that you designate as not significant for a search. They are left out of the <code>FULLTEXT</code> index and ignored when entered in <code>FULLTEXT</code> queries. For example, a search for &#8216;the&#8217; is unsuccessful because it&#8217;s in the default stopword list. For your own customized search, you might create a bigger list (say, with common words from several languages) or a smaller one (for example, a music or movie site where words such as &#8220;The&#8221; in names and titles are significant). The details about customizing the stopword list are in <a href="http://blogs.innodb.com/wp/2011/07/overview-and-getting-started-with-innodb-fts/">Jimmy&#8217;s blog post</a>.
</p>
<pre>
select count(*), author as "Stopword 1", quote as "Stopword 2", source as "Stopword 3"
  from quotes_multi_col
  where match(author, quote, source) against ('the' in natural language mode);
+----------+------------+------------+------------+
| count(*) | Stopword 1 | Stopword 2 | Stopword 3 |
+----------+------------+------------+------------+
|        0 | NULL       | NULL       | NULL       |
+----------+------------+------------+------------+
</pre><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=29493&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=29493&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://planetmysql.ru/2011/07/27/innodb-full-text-search-tutorial/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>A first look at delayed replication in MySQL 5.6</title>
		<link>http://datacharmer.blogspot.com/2011/01/first-look-at-delayed-replication-in.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=a-first-look-at-delayed-replication-in-mysql-5-6</link>
		<comments>http://datacharmer.blogspot.com/2011/01/first-look-at-delayed-replication-in.html#comments</comments>
		<pubDate>Sun, 30 Jan 2011 19:09:00 +0000</pubDate>
		<dc:creator>Giuseppe Maxia</dc:creator>
				<category><![CDATA[5.6]]></category>
		<category><![CDATA[delayed]]></category>
		<category><![CDATA[master]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[Replication]]></category>
		<category><![CDATA[sandbox]]></category>
		<category><![CDATA[Slave]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[If you like fresh features, you should not miss this one. MySQL 5.6.2 includes, among other improvements, the implementation of Time delayed replication, a feature that lets you tell the slave not to apply changes from the master immediately, but to wait N seconds.The feature is documented in WL#344. (There was a manual online as well together with the binaries for MySQL 5.6.0, but they were removed after a few days for a good reason. I am confident that both the manual and some binaries will eventually show up soon).Since as of today there are no binaries for MySQL 5.6.x, you need to get the code and compile it yourself. Just get the code from https://code.launchpad.net/mysql-server and compile it using the instructions in building MySQL 5.5 with cmake.To get a taste of this new feature, the quickest way is to set up replication using the binaries that you have built and MySQL Sandbox.make_replication_sandbox  mysql-5.6.2-m5-osx10.6-.tar.gz # the file name may change, depending on the operating system you are usingSoon you will have one master and two slaves in $HOME/sandboxes/rsandbox_5_6_2.What you have to do is connect to one of the slaves and enter these commands:STOP SLAVE;change master to master_delay=60;START SLAVE;Let's say that you did this to slave #2.Now whatever you do in the master will be replicated immediately in slave #1, but it will executed with 60 seconds delay in slave #2.To be clear, the IO_THREADs of both slaves keep getting data from the master as fast as they can, same as they did until version 5.5, but slave #2 will hold the SQL_THREAD for the defined amount of seconds.This new state is visible in the output of the SHOW SLAVE STATUS command, which lists this information after you do something in the master like creating a table or inserting data:               SQL_Delay: 60     SQL_Remaining_Delay: 43 Slave_SQL_Running_State: Waiting until MASTER_DELAY seconds after master executed eventThe main purpose of delayed replication is to protect the server against human mistakes. If I accidentally drop a table, the statement is instantly replicated to all the slaves, but it is not executed to the delayed slaves.$ ./m -e 'drop table test.t1 '$ ./use_all 'show tables from test'# master  # server: 1: # server: 2: Tables_in_testt1The table is gone in the master, and it is gone in the regular slave, but it is still there in the delayed slave. And if I detect the problem before the delayed statement gets executed (a delay time longer than 60 seconds would be advisable in this case, 3600=1 hour, seems healthier), then I may be able to recover the data.I notice en passant that there is much more than delayed replication going on in MySQL 5.6. For example, the information_schema tables related to InnoDB have increased from 7 to 18: show tables from information_schema like 'innodb%';+----------------------------------------+&#124; Tables_in_information_schema (innodb%) &#124;+----------------------------------------+&#124; INNODB_CMPMEM                          &#124;&#124; INNODB_TRX                             &#124;&#124; INNODB_BUFFER_PAGE                     &#124;&#124; INNODB_LOCK_WAITS                      &#124;&#124; INNODB_SYS_TABLESTATS                  &#124;&#124; INNODB_CMP                             &#124;&#124; INNODB_SYS_COLUMNS                     &#124;&#124; INNODB_CMPMEM_RESET                    &#124;&#124; INNODB_SYS_FOREIGN_COLS                &#124;&#124; INNODB_BUFFER_PAGE_LRU                 &#124;&#124; INNODB_BUFFER_POOL_STATS               &#124;&#124; INNODB_CMP_RESET                       &#124;&#124; INNODB_SYS_FOREIGN                     &#124;&#124; INNODB_METRICS                         &#124;&#124; INNODB_SYS_INDEXES                     &#124;&#124; INNODB_LOCKS                           &#124;&#124; INNODB_SYS_FIELDS                      &#124;&#124; INNODB_SYS_TABLES                      &#124;+----------------------------------------+18 rows in set (0.00 sec)What they do and how to play with them will be matter for some more investigation.]]></description>
			<content:encoded><![CDATA[<table border="0"><tbody><tr><td><a href="http://forge.mysql.com/worklog/task.php?id=344"><img alt="Delayed replication" border="0" src="https://lh3.googleusercontent.com/_gVfZHGgf5LA/TUWrNQRopwI/AAAAAAAABBc/xYBHwmaAalg/delayed_replication.png" title="Delayed replication" width="300" /></a> </td><td>If you like fresh features, you should not miss this one. MySQL 5.6.2 includes, among other improvements, the implementation of <a href="http://forge.mysql.com/worklog/task.php?id=344">Time delayed replication</a>, a feature that lets you tell the slave not to apply changes from the master immediately, but to wait N seconds.</td></tr></tbody></table>The feature is documented in <a href="http://forge.mysql.com/worklog/task.php?id=344">WL#344</a>. (There was a manual online as well together with the binaries for MySQL 5.6.0, but they were removed after a few days for a good reason. I am confident that both the manual and some binaries will eventually show up soon).<br />Since as of today there are no binaries for MySQL 5.6.x, you need to get the code and compile it yourself. Just get the code from <a href="https://code.launchpad.net/mysql-server">https://code.launchpad.net/mysql-server</a> and compile it using the instructions in <a href="http://datacharmer.blogspot.com/2010/04/building-mysql-55-with-cmake.html">building MySQL 5.5 with cmake</a>.<br />To get a taste of this new feature, the quickest way is to set up replication using the binaries that you have built and <a href="http://mysqlsandbox.net/">MySQL Sandbox</a>.<br /><pre>make_replication_sandbox  mysql-5.6.2-m5-osx10.6-.tar.gz <br /># the file name may change, depending on the operating system you are using<br /></pre>Soon you will have one master and two slaves in $HOME/sandboxes/rsandbox_5_6_2.<br />What you have to do is connect to one of the slaves and enter these commands:<br /><pre><code><br />STOP SLAVE;<br />change master to <b>master_delay=60</b>;<br />START SLAVE;<br /></code></pre>Let's say that you did this to slave #2.<br />Now whatever you do in the master will be replicated immediately in slave #1, but it will executed with 60 seconds delay in slave #2.<br />To be clear, the IO_THREADs of both slaves keep getting data from the master as fast as they can, same as they did until version 5.5, but slave #2 will hold the SQL_THREAD for the defined amount of seconds.<br />This new state is visible in the output of the SHOW SLAVE STATUS command, which lists this information after you do something in the master like creating a table or inserting data:<br /><pre><code><br />               SQL_Delay: 60<br />     SQL_Remaining_Delay: 43<br /> Slave_SQL_Running_State: Waiting until MASTER_DELAY seconds after master executed event<br /></code></pre>The main purpose of delayed replication is to protect the server against human mistakes. If I accidentally drop a table, the statement is instantly replicated to all the slaves, but it is not executed to the delayed slaves.<br /><pre><code><br />$ ./m -e 'drop table test.t1 '<br />$ ./use_all 'show tables from test'<br /># master  <br /># server: 1: <br /># server: 2: <br />Tables_in_test<br />t1<br /></code></pre>The table is gone in the master, and it is gone in the regular slave, but it is still there in the delayed slave. And if I detect the problem before the delayed statement gets executed (a delay time longer than 60 seconds would be advisable in this case, 3600=1 hour, seems healthier), then I may be able to recover the data.<br /><br />I notice en passant that there is much more than delayed replication going on in MySQL 5.6. For example, the information_schema tables related to InnoDB have increased from 7 to 18:<br /><pre><code><br /> show tables from information_schema like 'innodb%';<br />+----------------------------------------+<br />| Tables_in_information_schema (innodb%) |<br />+----------------------------------------+<br />| INNODB_CMPMEM                          |<br />| INNODB_TRX                             |<br />| INNODB_BUFFER_PAGE                     |<br />| INNODB_LOCK_WAITS                      |<br />| INNODB_SYS_TABLESTATS                  |<br />| INNODB_CMP                             |<br />| INNODB_SYS_COLUMNS                     |<br />| INNODB_CMPMEM_RESET                    |<br />| INNODB_SYS_FOREIGN_COLS                |<br />| INNODB_BUFFER_PAGE_LRU                 |<br />| INNODB_BUFFER_POOL_STATS               |<br />| INNODB_CMP_RESET                       |<br />| INNODB_SYS_FOREIGN                     |<br />| INNODB_METRICS                         |<br />| INNODB_SYS_INDEXES                     |<br />| INNODB_LOCKS                           |<br />| INNODB_SYS_FIELDS                      |<br />| INNODB_SYS_TABLES                      |<br />+----------------------------------------+<br />18 rows in set (0.00 sec)<br /></code></pre>What they do and how to play with them will be matter for some more investigation.<div><img width="1" height="1" src="https://blogger.googleusercontent.com/tracker/16959946-2276785366454591199?l=datacharmer.blogspot.com" alt="" /></div><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=27133&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=27133&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://planetmysql.ru/2011/01/30/a-first-look-at-delayed-replication-in-mysql-5-6/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Exchanging partitions with tables</title>
		<link>http://datacharmer.blogspot.com/2010/04/exchanging-partitions-with-tables.html?utm_source=rss&#038;utm_medium=rss&#038;utm_campaign=exchanging-partitions-with-tables</link>
		<comments>http://datacharmer.blogspot.com/2010/04/exchanging-partitions-with-tables.html#comments</comments>
		<pubDate>Thu, 29 Apr 2010 16:40:00 +0000</pubDate>
		<dc:creator>Giuseppe Maxia</dc:creator>
				<category><![CDATA[5.6]]></category>
		<category><![CDATA[dba]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[partitions]]></category>
		<category><![CDATA[usability]]></category>

		<guid isPermaLink="false"></guid>
		<description><![CDATA[While I was presenting my partitioning tutorial at the latest MySQL Conference, I announced a new feature that was, as far as I knew, still in the planning stage. Mattias Jonsson, one of the partitions developers, was in attendance, and corrected me, explaining that the feature was actually available in a prototype. So, we can have a look at this improvement, which I am sure will make DBAs quite happy. The new feature is an instantaneous exchange between a partition and a table with the same structure. Using this feature, you can transfer the contents of one partition to one table, and vice versa. Since the transition is done only in the attribution of the data, there is no copy involved. The data stays where it is at the moment. What is in the table ends up in the partition and what's in the partition ends up in the table. Let's see an example. With the data in figure 1, where we have a partitioned table t1 and an empty table t2 with the same structure, we can issue the following statement:ALTER TABLE t1EXCHANGE PARTITION p2WITH TABLE t2 After the exchange, partition p2 is empty, and table t2 contains 4 records.If we repeat the command, the contents will be swapped again, leaving table t2 empty and partition p2 with its original contents.If you want to test on your own, you can get the code from Launchpad. Once you get the code, you can use cmake to build the server.$ cmake-gui .# add the options you need. For example, enable innodb # or else you will need to load it as a plugin.$ make &#38;&#38; ./scripts/make_binary_distributionYou can then use this script to test the new functionality. You may want to change Innodb with MyISAM to test it thoroughly. At the moment, it doesn't work with the archive engine (yet).# ############################# test_exchange_partitions.sql# ############################use test;set default_storage_engine=innodb;drop procedure if exists compare_tables;delimiter //create procedure compare_tables (wanted int)reads sql databegin    set @part_table     := (select count(*) from t1);    set @non_part_table := (select count(*) from t2);    select @part_table, @non_part_table,         if(@non_part_table = wanted, "OK", "error") as expected;end //delimiter ;drop table if exists t1, t2;create table t1 (i int) # not null primary key)partition by range (i)     (partition p01 values less than  (100001), partition p02 values less than  (200001), partition p03 values less than  (300001), partition p04 values less than  (400001), partition p05 values less than  (500001), partition p06 values less than  (600001), partition p07 values less than  (700001), partition p08 values less than  (800001), partition p09 values less than  (900001), partition p10 values less than (1000001), partition p11 values less than (maxvalue));create table t2 (i int ) ; # not null primary key);select table_name, engine from information_schema.tables where table_schema='test' and table_type='base table';select 'generating 1 million records. ...' as info;# generates 1 million records# see this article for details# http://datacharmer.blogspot.com/2007/12/data-from-nothing-solution-to-pop-quiz.htmlcreate or replace view v3 as select null union all select null union all select null;create or replace view v10 as select null from v3 a, v3 b union all select null;create or replace view v1000 as select null from v10 a, v10 b, v10 c;set @n = 0;insert into t1 select @n:=@n+1 from v1000 a,v1000 b;select partition_name, table_rows from information_schema . partitions where table_name='t1' and table_schema='test';call compare_tables(0);alter table t1 exchange partition p04 with table t2; call compare_tables(100000);select partition_name, table_rows from information_schema . partitions where table_name='t1' and table_schema='test';alter table t1 exchange partition p04 with table t2; call compare_tables(0);alter table t1 exchange partition p04 with table t2; call compare_tables(100000);Here is a test run:$ ~/sandboxes/msb_5_6_99/use -t test -vvv &#60; test_exch_part.sql --------------set default_storage_engine=innodb--------------Query OK, 0 rows affected (0.00 sec)--------------drop procedure if exists compare_tables--------------Query OK, 0 rows affected (0.00 sec)--------------create procedure compare_tables (wanted int)reads sql databegin    set @part_table     := (select count(*) from t1);    set @non_part_table := (select count(*) from t2);    select @part_table, @non_part_table,         if(@non_part_table = wanted, "OK", "error") as expected;end--------------Query OK, 0 rows affected (0.00 sec)--------------drop table if exists t1, t2--------------Query OK, 0 rows affected (0.07 sec)--------------create table t1 (i int) partition by range (i)     (partition p01 values less than  (100001), partition p02 values less than  (200001), partition p03 values less than  (300001), partition p04 values less than  (400001), partition p05 values less than  (500001), partition p06 values less than  (600001), partition p07 values less than  (700001), partition p08 values less than  (800001), partition p09 values less than  (900001), partition p10 values less than (1000001), partition p11 values less than (maxvalue))--------------Query OK, 0 rows affected (0.08 sec)--------------create table t2 (i int )--------------Query OK, 0 rows affected (0.14 sec)--------------select table_name, engine from information_schema.tables where table_schema='test' and table_type='base table'--------------+------------+--------+&#124; table_name &#124; engine &#124;+------------+--------+&#124; t1         &#124; InnoDB &#124;&#124; t2         &#124; InnoDB &#124;+------------+--------+2 rows in set (0.01 sec)--------------select 'generating 1 million records. ...' as info--------------+-----------------------------------+&#124; info                              &#124;+-----------------------------------+&#124; generating 1 million records. ... &#124;+-----------------------------------+1 row in set (0.00 sec)--------------create or replace view v3 as select null union all select null union all select null--------------Query OK, 0 rows affected (0.12 sec)--------------create or replace view v10 as select null from v3 a, v3 b union all select null--------------Query OK, 0 rows affected (0.14 sec)--------------create or replace view v1000 as select null from v10 a, v10 b, v10 c--------------Query OK, 0 rows affected (0.09 sec)--------------set @n = 0--------------Query OK, 0 rows affected (0.00 sec)--------------insert into t1 select @n:=@n+1 from v1000 a,v1000 b--------------Query OK, 1000000 rows affected (10.01 sec)Records: 1000000  Duplicates: 0  Warnings: 0--------------select partition_name, table_rows from information_schema . partitions where table_name='t1' and table_schema='test'--------------+----------------+------------+&#124; partition_name &#124; table_rows &#124;+----------------+------------+&#124; p01            &#124;     100623 &#124;&#124; p02            &#124;     100623 &#124;&#124; p03            &#124;     100623 &#124;&#124; p04            &#124;     100623 &#124;&#124; p05            &#124;     100623 &#124;&#124; p06            &#124;     100623 &#124;&#124; p07            &#124;     100623 &#124;&#124; p08            &#124;     100623 &#124;&#124; p09            &#124;     100623 &#124;&#124; p10            &#124;     100623 &#124;&#124; p11            &#124;          0 &#124;+----------------+------------+11 rows in set (0.01 sec)--------------call compare_tables(0)--------------+-------------+-----------------+----------+&#124; @part_table &#124; @non_part_table &#124; expected &#124;+-------------+-----------------+----------+&#124;     1000000 &#124;               0 &#124; OK       &#124;+-------------+-----------------+----------+1 row in set (0.56 sec)Query OK, 0 rows affected (0.56 sec)--------------alter table t1 exchange partition p04 with table t2--------------Query OK, 0 rows affected (0.01 sec)--------------call compare_tables(100000)--------------+-------------+-----------------+----------+&#124; @part_table &#124; @non_part_table &#124; expected &#124;+-------------+-----------------+----------+&#124;      900000 &#124;          100000 &#124; OK       &#124;+-------------+-----------------+----------+1 row in set (0.54 sec)Query OK, 0 rows affected (0.54 sec)--------------select partition_name, table_rows from information_schema . partitions where table_name='t1' and table_schema='test'--------------+----------------+------------+&#124; partition_name &#124; table_rows &#124;+----------------+------------+&#124; p01            &#124;     100623 &#124;&#124; p02            &#124;     100623 &#124;&#124; p03            &#124;     100623 &#124;&#124; p04            &#124;          0 &#124;&#124; p05            &#124;     100623 &#124;&#124; p06            &#124;     100623 &#124;&#124; p07            &#124;     100623 &#124;&#124; p08            &#124;     100623 &#124;&#124; p09            &#124;     100623 &#124;&#124; p10            &#124;      91799 &#124;&#124; p11            &#124;          0 &#124;+----------------+------------+11 rows in set (0.01 sec)--------------alter table t1 exchange partition p04 with table t2--------------Query OK, 0 rows affected (0.05 sec)--------------call compare_tables(0)--------------+-------------+-----------------+----------+&#124; @part_table &#124; @non_part_table &#124; expected &#124;+-------------+-----------------+----------+&#124;     1000000 &#124;               0 &#124; OK       &#124;+-------------+-----------------+----------+1 row in set (0.56 sec)Query OK, 0 rows affected (0.56 sec)--------------alter table t1 exchange partition p04 with table t2--------------Query OK, 0 rows affected (0.00 sec)--------------call compare_tables(100000)--------------+-------------+-----------------+----------+&#124; @part_table &#124; @non_part_table &#124; expected &#124;+-------------+-----------------+----------+&#124;      900000 &#124;          100000 &#124; OK       &#124;+-------------+-----------------+----------+1 row in set (0.56 sec)Query OK, 0 rows affected (0.56 sec)ByeNotice that the value for "table_rows" is only approximate with InnoDB, while it is reliable for MyISAM. Anyway, when it says that a partition has 0 records, it's reliable for any engine. Here you see that, after the exchange, partition  p04 is empty.The exchange is repeated twice, to make sure that it works both ways.Notice also that, if the table contains data that doesn't fit with the partition, the server throws an error, and the exchange does not happen.mysql &#62; insert into t2 values (2000000);Query OK, 1 row affected (0.00 sec)mysql &#62; alter table t1 exchange partition p04 with table t2;ERROR 1697 (HY000): Found row that does not match the partitionIf you remove the offending row from the table, the exchange works as expected.]]></description>
			<content:encoded><![CDATA[<table border="0"><tr><td><a href="http://datacharmer.blogspot.com/"><img src="http://lh4.ggpht.com/_gVfZHGgf5LA/S9mgr9RD2YI/AAAAAAAAA3o/qro7xTepnTo/mysql_partitions.png" alt="MySQL Partitions" title="MySQL Partitions" width="250" border="0" /></a></td><td>While I was presenting my partitioning tutorial at the latest MySQL Conference, I announced a new feature that was, as far as I knew, still in the planning stage. Mattias Jonsson, one of the partitions developers, was in attendance, and corrected me, explaining that the feature was actually available in a prototype. </td></tr></table><br />So, we can have a look at this improvement, which I am sure will make DBAs quite happy. The new feature is an <a href="http://forge.mysql.com/worklog/task.php?id=4445">instantaneous exchange between a partition and a table</a> with the same structure. Using this feature, you can transfer the contents of one partition to one table, and vice versa. Since the transition is done only in the attribution of the data, there is no copy involved. The data stays where it is at the moment. What is in the table ends up in the partition and what's in the partition ends up in the table. Let's see an example.<br /><img src="http://lh5.ggpht.com/_gVfZHGgf5LA/S9mgrskJ-RI/AAAAAAAAA3g/2amf0u_n6FY/exchange_partition_before.png" border="1" /> <br />With the data in figure 1, where we have a partitioned table t1 and an empty table t2 with the same structure, we can issue the following statement:<br /><pre><code>ALTER TABLE t1<br />EXCHANGE PARTITION p2<br />WITH TABLE t2</code></pre><br /><img src="http://lh6.ggpht.com/_gVfZHGgf5LA/S9mgr1fxIcI/AAAAAAAAA3k/R_4WrM3FgQo/exchange_partition_after.png" border="1" /> <br />After the exchange, partition p2 is empty, and table t2 contains 4 records.<br />If we repeat the command, the contents will be swapped again, leaving table t2 empty and partition p2 with its original contents.<br /><br />If you want to test on your own, you can get the code from <a href="https://code.edge.launchpad.net/~mysql/mysql-server/mysql-next-mr-wl4445">Launchpad</a>. Once you get the code, you can use <a href="http://datacharmer.blogspot.com/2010/04/building-mysql-55-with-cmake.html">cmake to build the server</a>.<br /><pre><code><br />$ cmake-gui .<br /># add the options you need. For example, enable innodb <br /># or else you will need to load it as a plugin.<br />$ make && ./scripts/make_binary_distribution<br /></code></pre><br />You can then use this script to test the new functionality. You may want to change Innodb with MyISAM to test it thoroughly. At the moment, it doesn't work with the archive engine (yet).<br /><pre><code><br /># ############################<br /># test_exchange_partitions.sql<br /># ############################<br />use test;<br />set default_storage_engine=innodb;<br />drop procedure if exists compare_tables;<br />delimiter //<br />create procedure compare_tables (wanted int)<br />reads sql data<br />begin<br />    set @part_table     := (select count(*) from t1);<br />    set @non_part_table := (select count(*) from t2);<br />    select @part_table, @non_part_table, <br />        if(@non_part_table = wanted, "OK", "error") as expected;<br />end //<br />delimiter ;<br /><br />drop table if exists t1, t2;<br />create table t1 (i int) # not null primary key)<br />partition by range (i) <br />    (<br />partition p01 values less than  (100001), <br />partition p02 values less than  (200001), <br />partition p03 values less than  (300001), <br />partition p04 values less than  (400001), <br />partition p05 values less than  (500001), <br />partition p06 values less than  (600001), <br />partition p07 values less than  (700001), <br />partition p08 values less than  (800001), <br />partition p09 values less than  (900001), <br />partition p10 values less than (1000001), <br />partition p11 values less than (maxvalue));<br /><br />create table t2 (i int ) ; # not null primary key);<br /><br />select table_name, engine <br />from information_schema.tables <br />where table_schema='test' and table_type='base table';<br /><br /><br />select 'generating 1 million records. ...' as info;<br /># generates 1 million records<br /># see this article for details<br /># http://datacharmer.blogspot.com/2007/12/data-from-nothing-solution-to-pop-quiz.html<br />create or replace view v3 as select null union all select null union all select null;<br />create or replace view v10 as select null from v3 a, v3 b union all select null;<br />create or replace view v1000 as select null from v10 a, v10 b, v10 c;<br />set @n = 0;<br />insert into t1 select @n:=@n+1 from v1000 a,v1000 b;<br /><br />select partition_name, table_rows from information_schema . partitions where table_name='t1' and table_schema='test';<br /><br />call compare_tables(0);<br /><br />alter table t1 exchange partition p04 with table t2; <br />call compare_tables(100000);<br /><br />select partition_name, table_rows from information_schema . partitions where table_name='t1' and table_schema='test';<br /><br />alter table t1 exchange partition p04 with table t2; <br />call compare_tables(0);<br /><br />alter table t1 exchange partition p04 with table t2; <br />call compare_tables(100000);<br /></code></pre><br />Here is a test run:<br /><pre><code><br />$ ~/sandboxes/msb_5_6_99/use -t test -vvv &lt; test_exch_part.sql <br />--------------<br />set default_storage_engine=innodb<br />--------------<br /><br />Query OK, 0 rows affected (0.00 sec)<br /><br />--------------<br />drop procedure if exists compare_tables<br />--------------<br /><br />Query OK, 0 rows affected (0.00 sec)<br /><br />--------------<br />create procedure compare_tables (wanted int)<br />reads sql data<br />begin<br />    set @part_table     := (select count(*) from t1);<br />    set @non_part_table := (select count(*) from t2);<br />    select @part_table, @non_part_table, <br />        if(@non_part_table = wanted, "OK", "error") as expected;<br />end<br />--------------<br /><br />Query OK, 0 rows affected (0.00 sec)<br /><br />--------------<br />drop table if exists t1, t2<br />--------------<br /><br />Query OK, 0 rows affected (0.07 sec)<br /><br />--------------<br />create table t1 (i int) <br />partition by range (i) <br />    (<br />partition p01 values less than  (100001), <br />partition p02 values less than  (200001), <br />partition p03 values less than  (300001), <br />partition p04 values less than  (400001), <br />partition p05 values less than  (500001), <br />partition p06 values less than  (600001), <br />partition p07 values less than  (700001), <br />partition p08 values less than  (800001), <br />partition p09 values less than  (900001), <br />partition p10 values less than (1000001), <br />partition p11 values less than (maxvalue))<br />--------------<br /><br />Query OK, 0 rows affected (0.08 sec)<br /><br />--------------<br />create table t2 (i int )<br />--------------<br /><br />Query OK, 0 rows affected (0.14 sec)<br /><br />--------------<br />select table_name, engine <br />from information_schema.tables <br />where table_schema='test' and table_type='base table'<br />--------------<br /><br />+------------+--------+<br />| table_name | engine |<br />+------------+--------+<br />| t1         | InnoDB |<br />| t2         | InnoDB |<br />+------------+--------+<br />2 rows in set (0.01 sec)<br /><br />--------------<br />select 'generating 1 million records. ...' as info<br />--------------<br /><br />+-----------------------------------+<br />| info                              |<br />+-----------------------------------+<br />| generating 1 million records. ... |<br />+-----------------------------------+<br />1 row in set (0.00 sec)<br /><br />--------------<br />create or replace view v3 as select null union all select null union all select null<br />--------------<br /><br />Query OK, 0 rows affected (0.12 sec)<br /><br />--------------<br />create or replace view v10 as select null from v3 a, v3 b union all select null<br />--------------<br /><br />Query OK, 0 rows affected (0.14 sec)<br /><br />--------------<br />create or replace view v1000 as select null from v10 a, v10 b, v10 c<br />--------------<br /><br />Query OK, 0 rows affected (0.09 sec)<br /><br />--------------<br />set @n = 0<br />--------------<br /><br />Query OK, 0 rows affected (0.00 sec)<br /><br />--------------<br />insert into t1 select @n:=@n+1 from v1000 a,v1000 b<br />--------------<br /><br />Query OK, 1000000 rows affected (10.01 sec)<br />Records: 1000000  Duplicates: 0  Warnings: 0<br /><br />--------------<br />select partition_name, table_rows from information_schema . partitions where table_name='t1' and table_schema='test'<br />--------------<br /><br />+----------------+------------+<br />| partition_name | table_rows |<br />+----------------+------------+<br />| p01            |     100623 |<br />| p02            |     100623 |<br />| p03            |     100623 |<br />| p04            |     100623 |<br />| p05            |     100623 |<br />| p06            |     100623 |<br />| p07            |     100623 |<br />| p08            |     100623 |<br />| p09            |     100623 |<br />| p10            |     100623 |<br />| p11            |          0 |<br />+----------------+------------+<br />11 rows in set (0.01 sec)<br /><br />--------------<br />call compare_tables(0)<br />--------------<br /><br />+-------------+-----------------+----------+<br />| @part_table | @non_part_table | expected |<br />+-------------+-----------------+----------+<br />|     1000000 |               0 | OK       |<br />+-------------+-----------------+----------+<br />1 row in set (0.56 sec)<br /><br />Query OK, 0 rows affected (0.56 sec)<br /><br />--------------<br />alter table t1 exchange partition p04 with table t2<br />--------------<br /><br />Query OK, 0 rows affected (0.01 sec)<br /><br />--------------<br />call compare_tables(100000)<br />--------------<br /><br />+-------------+-----------------+----------+<br />| @part_table | @non_part_table | expected |<br />+-------------+-----------------+----------+<br />|      900000 |          100000 | OK       |<br />+-------------+-----------------+----------+<br />1 row in set (0.54 sec)<br /><br />Query OK, 0 rows affected (0.54 sec)<br /><br />--------------<br />select partition_name, table_rows from information_schema . partitions where table_name='t1' and table_schema='test'<br />--------------<br /><br />+----------------+------------+<br />| partition_name | table_rows |<br />+----------------+------------+<br />| p01            |     100623 |<br />| p02            |     100623 |<br />| p03            |     100623 |<br />| p04            |          0 |<br />| p05            |     100623 |<br />| p06            |     100623 |<br />| p07            |     100623 |<br />| p08            |     100623 |<br />| p09            |     100623 |<br />| p10            |      91799 |<br />| p11            |          0 |<br />+----------------+------------+<br />11 rows in set (0.01 sec)<br /><br />--------------<br />alter table t1 exchange partition p04 with table t2<br />--------------<br /><br />Query OK, 0 rows affected (0.05 sec)<br /><br />--------------<br />call compare_tables(0)<br />--------------<br /><br />+-------------+-----------------+----------+<br />| @part_table | @non_part_table | expected |<br />+-------------+-----------------+----------+<br />|     1000000 |               0 | OK       |<br />+-------------+-----------------+----------+<br />1 row in set (0.56 sec)<br /><br />Query OK, 0 rows affected (0.56 sec)<br /><br />--------------<br />alter table t1 exchange partition p04 with table t2<br />--------------<br /><br />Query OK, 0 rows affected (0.00 sec)<br /><br />--------------<br />call compare_tables(100000)<br />--------------<br /><br />+-------------+-----------------+----------+<br />| @part_table | @non_part_table | expected |<br />+-------------+-----------------+----------+<br />|      900000 |          100000 | OK       |<br />+-------------+-----------------+----------+<br />1 row in set (0.56 sec)<br /><br />Query OK, 0 rows affected (0.56 sec)<br /><br />Bye<br /></code></pre><br />Notice that the value for "table_rows" is only approximate with InnoDB, while it is reliable for MyISAM. Anyway, when it says that a partition has 0 records, it's reliable for any engine. Here you see that, after the exchange, partition  p04 is empty.<br />The exchange is repeated twice, to make sure that it works both ways.<br />Notice also that, if the table contains data that doesn't fit with the partition, the server throws an error, and the exchange does not happen.<br /><pre><code><br />mysql &gt; insert into t2 values (2000000);<br />Query OK, 1 row affected (0.00 sec)<br /><br />mysql &gt; alter table t1 exchange partition p04 with table t2;<br />ERROR 1697 (HY000): Found row that does not match the partition<br /></code></pre><br />If you remove the offending row from the table, the exchange works as expected.<div><img width="1" height="1" src="https://blogger.googleusercontent.com/tracker/16959946-8561679074431929442?l=datacharmer.blogspot.com" alt="" /></div><br/>PlanetMySQL Voting:
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=24558&vote=1&apivote=1">Vote UP</a> /
	 <a href="http://planet.mysql.com/entry/vote/?entry_id=24558&vote=-1&apivote=1">Vote DOWN</a>]]></content:encoded>
			<wfw:commentRss>http://planetmysql.ru/2010/04/29/exchanging-partitions-with-tables/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

