<?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>Boy Plankton . com &#187; Computers</title>
	<atom:link href="http://www.boyplankton.com/category/computers/feed/" rel="self" type="application/rss+xml" />
	<link>http://www.boyplankton.com</link>
	<description></description>
	<lastBuildDate>Tue, 12 May 2009 03:32:45 +0000</lastBuildDate>
	<generator>http://wordpress.org/?v=2.9.2</generator>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
			<item>
		<title></title>
		<link>http://www.boyplankton.com/2009/05/11/143/</link>
		<comments>http://www.boyplankton.com/2009/05/11/143/#comments</comments>
		<pubDate>Tue, 12 May 2009 03:32:45 +0000</pubDate>
		<dc:creator>Boy Plankton</dc:creator>
				<category><![CDATA[Database]]></category>
		<category><![CDATA[Groovy]]></category>

		<guid isPermaLink="false">http://www.boyplankton.com/?p=143</guid>
		<description><![CDATA[I&#8217;ve been trying to write a bunch of scripts that perform some database administration tasks in Groovy.  Groovy offers a several advantages over most of my other options.  It runs practically everywhere.  There are JDBC drivers available for every database that I have to work with.  Most importantly to me though, [...]]]></description>
			<content:encoded><![CDATA[<p>I&#8217;ve been trying to write a bunch of scripts that perform some database administration tasks in <a href="http://groovy.codehaus.org/">Groovy</a>.  Groovy offers a several advantages over most of my other options.  It runs practically everywhere.  There are JDBC drivers available for every database that I have to work with.  Most importantly to me though, SQL programming in Groovy is really simple.</p>
<p>At first I thought it was a Groovy limitation that was causing my scripts to run out of heap space when I ran them against large tables.  It turned out to be the <a href="http://dev.mysql.com/doc/refman/5.0/en/connector-j-reference-implementation-notes.html">default behavior of the MySQL JDBC driver</a> though.</p>
<blockquote><p>By default, <strong>ResultSets are completely retrieved and stored in memory</strong>. In most cases this is the most efficient way to operate, and due to the design of the MySQL network protocol is easier to implement. If you are working with ResultSets that have a large number of rows or large values, and can not allocate heap space in your JVM for the memory required, you can tell the driver to stream the results back one row at a time.</p></blockquote>
<p>The solution is to override three default settings.  The first two override the default settings of the ResultSets that will be returned by the statement.<br />
<code>sql.setResultSetType( java.sql.ResultSet.TYPE_FORWARD_ONLY )<br />
sql.setResultSetConcurrency( java.sql.ResultSet.CONCUR_READ_ONLY )</code></p>
<p>This one adds a closure that gets called every time a statement gets created.<br />
<code>sql.withStatement{ stmt -> stmt.setFetchSize( Integer.MIN_VALUE ) }</code></p>
]]></content:encoded>
			<wfw:commentRss>http://www.boyplankton.com/2009/05/11/143/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Screen</title>
		<link>http://www.boyplankton.com/2007/05/28/screen/</link>
		<comments>http://www.boyplankton.com/2007/05/28/screen/#comments</comments>
		<pubDate>Mon, 28 May 2007 13:47:47 +0000</pubDate>
		<dc:creator>Boy Plankton</dc:creator>
				<category><![CDATA[Administration]]></category>

		<guid isPermaLink="false">http://www.boyplankton.com/2007/05/28/screen/</guid>
		<description><![CDATA[If I could only take two pieces of software with me onto a deserted island, the first would be Vim, the second would be Screen.  Screen provides two very important features that make my life quite a bit easier.

Persistence.  If the network has an error, or I just happen to click the silly [...]]]></description>
			<content:encoded><![CDATA[<p>If I could only take two pieces of software with me onto a deserted island, the first would be <a href="http://www.vim.org/">Vim</a>, the second would be <a href="http://www.gnu.org/software/screen">Screen</a>.  Screen provides two very important features that make my life quite a bit easier.</p>
<ol>
<li>Persistence.  If the network has an error, or I just happen to click the silly little x on my terminal window, then all I have to do is ssh back into the server and restart screen (screen -r).  It will be like I never left.  This means that I don&#8217;t have to worry about running everything with a nohup, and if I start a job at work I can go home and reconnect (screen -Dr) to my terminal session in order to continue monitoring the job.</li>
<li>Multiple windows.  Ssh into the server, start up screen, start a job in that shell, type &#8216;Ctrl-a c&#8217;, and voila, a new shell in a new window.  Now I can go and adjust the size and position of the windows.  For instance, let&#8217;s say that I need to tail five different log files.  I can start up five windows, resize and position them all on the screen, and then I&#8217;m monitoring all those files in one terminal session.  I can switch back and forth between the windows by typing &#8216;Ctrl-a #&#8217; where # is the number of the window, &#8216;Ctrl-a n&#8217; for next, &#8216;Ctrl-a p&#8217; for previous, or &#8216;Ctrl-a &#8220;&#8216; to get a list of all the windows that you can arrow up or down through.  You can also rename the window you are in by typing &#8216;Ctrl-a A&#8217;.</li>
</ol>
<p>Unfortunately, screen sucks straight out of the box.  I recommend creating a .screenrc with these lines at a minimum:</p>
<div style="background-color:#000040;color:#c0c0c0">
<pre>
startup_message off
shell bash

termcapinfo xterm "ks=E[?1lE:kuE[A:kd=E[B:kl=E[D:kr=E[C:kh=E[5~:kH=E[F"

caption always "%{= bb}%{+b w}%n %h %=%t %c"
hardstatus alwayslastline "%{-b gk}%-w%{+b kg}%50>%n %t%{-b gk}%+w%<"

" screen starts at window 0. These two lines make the first window 1, and
" binds 0 to window 10.
bind c screen 1
bind 0 select 10
</pre>
</div>
<p>After starting screen you should now have two lines at the bottom of your screen.  The first should be blue and have the window number, name, and system time.  The second should be green and have a listing of all the current windows and their names.  I prefer a dark background and these colors work really well on top of black.  You should review the <a href="http://www.linuxmanpages.com/man1/screen.1.php">screen manpage</a> for more things that you can add to those status lines.  One of my favorites is %l which displays the current load on the system.</p>
<p>Another thing that you can do with screen is create custom screenrc files for different situations.  I have several that I use which automatically open certain windows for me.  For instance, if I "cp .screenrc .mysqlscreenrc" and then add the following lines to the bottom of .mysqlscreenrc:</p>
<div style="background-color:#000040;color:#c0c0c0">
<pre>
screen -t "database 1"   1 mysql -h hostname -u username -ppassword data1
screen -t "database 2"   2 mysql -h hostname -u username -ppassword data2
screen -t "localhost"    3 bash
</pre>
</div>
<p>Then, add this alias "alias mysqlscreen='screen -S mysql -c ~/.mysqlscreenrc'".  Now all you have to do is type mysqlscreen when you log in, and it will automatically open three windows for you.  Two of them will open up mysql monitor sessions, and the third will open up a shell.  The -S part of the screen command names the screen session.  That way if you become disconnected then a 'screen -ls' will be more meaningful, and it will be easier to know which session is which when you do your 'screen -r'.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.boyplankton.com/2007/05/28/screen/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Essential .vimrc</title>
		<link>http://www.boyplankton.com/2007/05/27/essential-vimrc/</link>
		<comments>http://www.boyplankton.com/2007/05/27/essential-vimrc/#comments</comments>
		<pubDate>Sun, 27 May 2007 23:25:49 +0000</pubDate>
		<dc:creator>Boy Plankton</dc:creator>
				<category><![CDATA[Administration]]></category>

		<guid isPermaLink="false">http://www.boyplankton.com/2007/05/27/essential-vimrc/</guid>
		<description><![CDATA[These two lines will create and display a custom status line.  The status line will include the name of the file, the ascii and hex values for the character under the cursor, and the location of the cursor.


set statusline=%F%m%r%h%w\ ASC=[\%03.3b]\ HEX=[\\x\%02.2B]\ %l,%v\ %p%%
set laststatus=2


]]></description>
			<content:encoded><![CDATA[<p>These two lines will create and display a custom status line.  The status line will include the name of the file, the ascii and hex values for the character under the cursor, and the location of the cursor.</p>
<div style="background-color:#000040;color:#c0c0c0">
<pre>
<font color="#ffff60">set</font> <font color="#ff80ff">statusline</font>=%F%m%r%h%w\ ASC=[\%03.3b]\ HEX=[\\x\%02.2B]\ %l<font color="#ffff60">,</font>%v\ %p%%
<font color="#ffff60">set</font> <font color="#ff80ff">laststatus</font>=2
</pre>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.boyplankton.com/2007/05/27/essential-vimrc/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>2 Years</title>
		<link>http://www.boyplankton.com/2007/05/21/2-years/</link>
		<comments>http://www.boyplankton.com/2007/05/21/2-years/#comments</comments>
		<pubDate>Tue, 22 May 2007 00:32:03 +0000</pubDate>
		<dc:creator>Boy Plankton</dc:creator>
				<category><![CDATA[Administration]]></category>

		<guid isPermaLink="false">http://www.boyplankton.com/2007/05/21/2-years/</guid>
		<description><![CDATA[One of my servers at work hit a milestone last night:
4:32pm  up 730 days, 22:25,  231 users,  load average: 0.60, 0.63, 0.69
Update: Stupid Murphy and his stupid laws.  Just spent an hour working on two other machines.  Only solution appeared to be rebooting both machines.
]]></description>
			<content:encoded><![CDATA[<p>One of my servers at work hit a milestone last night:<br />
<blockquote>4:32pm  up 730 days, 22:25,  231 users,  load average: 0.60, 0.63, 0.69</p></blockquote>
<p>Update: Stupid Murphy and his stupid laws.  Just spent an hour working on two other machines.  Only solution appeared to be rebooting both machines.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.boyplankton.com/2007/05/21/2-years/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Practical Jokes</title>
		<link>http://www.boyplankton.com/2007/05/07/practical-jokes/</link>
		<comments>http://www.boyplankton.com/2007/05/07/practical-jokes/#comments</comments>
		<pubDate>Tue, 08 May 2007 02:08:56 +0000</pubDate>
		<dc:creator>Boy Plankton</dc:creator>
				<category><![CDATA[Computers]]></category>

		<guid isPermaLink="false">http://www.boyplankton.com/2007/05/07/practical-jokes/</guid>
		<description><![CDATA[Was reading this article on customizing Ubuntu today.  Nothing really new in there for me except for this nugget:
There&#8217;s an old practical joke where people pry the keys off a keyboard and replace them out of order. A user who is not a touch typist can get really confused when they look for the [...]]]></description>
			<content:encoded><![CDATA[<p>Was reading this <a href="http://www.extremetech.com/article2/0,1697,2126234,00.asp">article on customizing Ubuntu</a> today.  Nothing really new in there for me except for this nugget:</p>
<blockquote><p>There&#8217;s an old practical joke where people pry the keys off a keyboard and replace them out of order. A user who is not a touch typist can get really confused when they look for the right letters. The xmodmap command can be used for an excellent alternative to this prank. For example, you can add the following commands into the victim&#8217;s .bashrc file:xmodmap -e &#8216;keycode 91 = t T&#8217;<br />
xmodmap -e &#8216;keycode 92 = s S&#8217;</p>
<p>This prank swaps the s and t keys on the keyboard without requiring any physical keyboard modifications. Alternately, if you can pry up and replace the keys on the keyboard, follow it up by remapping the keyboard so it actually works correctly!</p></blockquote>
<p>Never thought of that before. Now I have no choice but to try it out.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.boyplankton.com/2007/05/07/practical-jokes/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Secustick</title>
		<link>http://www.boyplankton.com/2007/04/19/secustick/</link>
		<comments>http://www.boyplankton.com/2007/04/19/secustick/#comments</comments>
		<pubDate>Fri, 20 Apr 2007 03:36:52 +0000</pubDate>
		<dc:creator>Boy Plankton</dc:creator>
				<category><![CDATA[Computers]]></category>

		<guid isPermaLink="false">http://www.boyplankton.com/2007/04/19/secustick/</guid>
		<description><![CDATA[This morning Bruce Schneier posted a fascinating article on the psychology and economics of buying bad products.  The example that he uses is a &#8220;secure&#8221; USB keychain drive called Secustick that was, supposedly, commissioned by the French government, tested and approved by a French intelligence service, and is apparently in use all over the [...]]]></description>
			<content:encoded><![CDATA[<p>This morning Bruce Schneier posted a fascinating article on the <a href="http://www.schneier.com/blog/archives/2007/04/a_security_mark.html">psychology and economics of buying bad products</a>.  The example that he uses is a &#8220;secure&#8221; USB keychain drive called <a href="http://www.secustick.nl/engels/index.html">Secustick</a> that was, supposedly, commissioned by the French government, tested and approved by a French intelligence service, and is apparently in use all over the world by governments and major corporations in the financial services industries.</p>
<p>A week ago a <a href="http://tweakers.net/reviews/683/1">review of the Secustick</a> was posted on Tweakers.net.  They loaded the software on the stick into a debugger and learned that all someone had to do was place a breakpoint after a function called VerifyPassWord(), alter the return code from a 0 to a 1, and voilà! (Pun intended.) Anybody could get access to the contents of the drive.</p>
<p>It&#8217;s generally my belief that the free market is efficient.  However, Bruce&#8217;s essay brings up a good point.  Shoddy products like the Secustick have an advantage in the marketplace.  Namely, they cost less to produce.  Normally this isn&#8217;t an issue because the consumer has the skillset and the ability to evaluate that the clothing they are buying from Walmart isn&#8217;t as high a quality as what they would buy from JC Penney&#8217;s.  How are you supposed to know when it comes to something like the Secustick?  Or a firewall?  Or a virus scanner?  Or a &lt;insert any suitably complicated technology&gt;?</p>
]]></content:encoded>
			<wfw:commentRss>http://www.boyplankton.com/2007/04/19/secustick/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Compound Indexes</title>
		<link>http://www.boyplankton.com/2007/03/27/compound-indexes/</link>
		<comments>http://www.boyplankton.com/2007/03/27/compound-indexes/#comments</comments>
		<pubDate>Wed, 28 Mar 2007 04:33:26 +0000</pubDate>
		<dc:creator>Boy Plankton</dc:creator>
				<category><![CDATA[Database]]></category>

		<guid isPermaLink="false">http://www.boyplankton.com/2007/03/27/compound-indexes/</guid>
		<description><![CDATA[Important rule, MySQL only supports using one index per join.
If a multiple-column index exists on col1 and col2, the appropriate rows can be fetched directly. If separate single-column indexes exist on col1 and col2, the optimizer tries to find the most restrictive index by deciding which index finds fewer rows and using that index to [...]]]></description>
			<content:encoded><![CDATA[<p>Important rule, MySQL only supports using <a href="http://dev.mysql.com/doc/refman/5.0/en/mysql-indexes.html">one index per join</a>.</p>
<blockquote><p>If a multiple-column index exists on col1 and col2, the appropriate rows can be fetched directly. If separate single-column indexes exist on col1 and col2, the optimizer tries to find the most restrictive index by deciding which index finds fewer rows and using that index to fetch the rows.</p></blockquote>
<p>To demonstrate this I went and got some historical stock price data from <a href="http://biz.swcp.com/stocks/">this site</a>.  After that, I created a test table and added some indexes in order to test some concepts.<br />
<span id="more-104"></span></p>
<pre>
<font color="#ffff60">CREATE</font> <font color="#ffff60">TABLE</font> stockdata (
   sdate <font color="#60ff60">DATE</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font> ,
   ticker <font color="#60ff60">VARCHAR( </font><font color="#ffa0a0">4</font><font color="#60ff60"> )</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font> ,
   open <font color="#60ff60">DECIMAL( </font><font color="#ffa0a0">8</font><font color="#60ff60">,</font><font color="#ffa0a0">2</font><font color="#60ff60"> )</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font> ,
   high <font color="#60ff60">DECIMAL( </font><font color="#ffa0a0">8</font><font color="#60ff60">,</font><font color="#ffa0a0">2</font><font color="#60ff60"> )</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font> ,
   low <font color="#60ff60">DECIMAL( </font><font color="#ffa0a0">8</font><font color="#60ff60">,</font><font color="#ffa0a0">2</font><font color="#60ff60"> )</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font> ,
   close <font color="#60ff60">DECIMAL( </font><font color="#ffa0a0">8</font><font color="#60ff60">,</font><font color="#ffa0a0">2</font><font color="#60ff60"> )</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font> ,
   volume <font color="#60ff60">INT</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font>
) ENGINE = <font color="#ffff60">MYISAM</font> ;

<font color="#ffff60">load</font> <font color="#ffff60">data</font> <font color="#ffff60">infile</font> <font color="#ffa0a0">'sp500hst.txt'</font>
<font color="#ffff60">into</font> <font color="#ffff60">table</font> stockdata
<font color="#ffff60">fields</font> <font color="#ffff60">terminated</font> <font color="#ffff60">by</font> <font color="#ffa0a0">','</font>;

<font color="#ffff60">create</font> <font color="#ffff60">index</font> ix_sdate <font color="#ffff60">on</font> stockdata ( sdate );
<font color="#ffff60">create</font> <font color="#ffff60">index</font> ix_ticker <font color="#ffff60">on</font> stockdata ( ticker );</pre>
<p>With the data loaded and indexes created on the ticker field and the sdate field, we&#8217;re ready to take a look at why this is important.  Here&#8217;s the query that we&#8217;re going to use to test with:</p>
<pre>
<font color="#ffff60">explain</font> extended
<font color="#ffff60">select</font> *
<font color="#ffff60">from</font> stockdata
<font color="#ffff60">where</font> ticker = <font color="#ffa0a0">'DIS'</font>
<font color="#ffff60">and</font> sdate <font color="#ffff60">between</font>
   <font color="#ffa0a0">'2007-02-01'</font> <font color="#ffff60">and</font> <font color="#ffa0a0">'2007-02-28'</font>;</pre>
<pre>
+----+-------------+-----------+------+--------------------+-----------+---------+-------+------+-------------+
| id | select_type | table     | type | possible_keys      | key       | key_len | ref   | rows | Extra       |
+----+-------------+-----------+------+--------------------+-----------+---------+-------+------+-------------+
|  1 | SIMPLE      | stockdata | ref  | ix_sdate,ix_ticker | ix_ticker | 6       | const |  245 | Using where |
+----+-------------+-----------+------+--------------------+-----------+---------+-------+------+-------------+</pre>
<p>According to the results of our EXPLAIN, the optimizer chose to use the index on the ticker field, but not the index on the sdate field.  It chose that index because there are only 253 entries in the data with a ticker of &#8216;DIS&#8217;, but there are 9501 entries with an sdate in Feb 2007.  Basically, the plan is for it to grab all 253 &#8216;DIS&#8217; entries, and then it will apply a filter to them in order to find the ones that are within the date range that we are looking for.  Note that under &#8216;rows&#8217;, the optimizer thinks that this index will return 245 rows instead of 253.</p>
<p>This isn&#8217;t exactly ideal.  It would be nice to have the database utilize indexes on both those fields.  Since the database can only use one index per join, we will have to rely on a multiple-column ( aka compound ) index to get the desired performance.</p>
<pre>
<font color="#ffff60">drop</font> <font color="#ffff60">index</font> ix_sdate <font color="#ffff60">on</font> stockdata;
<font color="#ffff60">drop</font> <font color="#ffff60">index</font> ix_ticker <font color="#ffff60">on</font> stockdata;

<font color="#ffff60">create</font> <font color="#ffff60">index</font> ix_compound <font color="#ffff60">on</font> stockdata( sdate, ticker );</pre>
<pre>+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+
| id | select_type | table     | type  | possible_keys | key         | key_len | ref  | rows | Extra       |
+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+
|  1 | SIMPLE      | stockdata | range | ix_compound   | ix_compound | 9       | NULL | 8872 | Using where |
+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+</pre>
<p>According to the optimizer it&#8217;s going to have to filter through 8872 rows.  This appears to be less efficient than just having the index on the ticker field.  Why?  Because the order of the columns in a multiple-column index is very important and should be from most selective to least. In this example, we&#8217;ve ordered them just the opposite.  Instead, let&#8217;s drop that index and recreate it with the fields in the opposite order:</p>
<pre>
<font color="#ffff60">drop</font> <font color="#ffff60">index</font> ix_compound <font color="#ffff60">on</font> stockdata;
<font color="#ffff60">create</font> <font color="#ffff60">index</font> ix_compound <font color="#ffff60">on</font> stockdata( ticker, sdate );</pre>
<pre>
+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+
| id | select_type | table     | type  | possible_keys | key         | key_len | ref  | rows | Extra       |
+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+
|  1 | SIMPLE      | stockdata | range | ix_compound   | ix_compound | 9       | NULL |   14 | Using where |
+----+-------------+-----------+-------+---------------+-------------+---------+------+------+-------------+</pre>
<p>Down to an estimated 14 rows.  The query actually returns 19.  By utilizing a multiple-column index and carefully selecting the order of the columns in the index, I think this is about the best performance that we can get out of this query by just adjusting the indexes.  I&#8217;m certain that there are other optimizations that could be performed.  For instance, I could have done a better job structuring the table.  Different data types would probably occupy less space on disk, require fewer IO reads, etc &#8230; That said, playing with these indexes probably gets us the biggest bang for the buck.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.boyplankton.com/2007/03/27/compound-indexes/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>MySQL Indexes</title>
		<link>http://www.boyplankton.com/2007/03/25/mysql-indexes/</link>
		<comments>http://www.boyplankton.com/2007/03/25/mysql-indexes/#comments</comments>
		<pubDate>Mon, 26 Mar 2007 05:01:05 +0000</pubDate>
		<dc:creator>Boy Plankton</dc:creator>
				<category><![CDATA[Database]]></category>

		<guid isPermaLink="false">http://www.boyplankton.com/2007/03/25/mysql-indexes/</guid>
		<description><![CDATA[It dawned on me this morning, following a conversation with Casey, that I don&#8217;t know nearly enough about MySQL database administration.  The vast majority of my DB experience is with Informix and Oracle.  So, I did a little reading up and performed some experiments.

First question that I had was how to analyze the [...]]]></description>
			<content:encoded><![CDATA[<p>It dawned on me this morning, following a conversation with <a href="http://gl-casey.livejournal.com/">Casey</a>, that I don&#8217;t know nearly enough about MySQL database administration.  The vast majority of my DB experience is with Informix and Oracle.  So, I did a little reading up and performed some experiments.<br />
<span id="more-103"></span><br />
First question that I had was how to analyze the queries, and the tables, to determine when/which indexes should be applied.  To begin I created a test database called monkey with just a few tables:</p>
<pre>
<font color="#ffff60">CREATE</font> <font color="#ffff60">TABLE</font> `<font color="#ffff60">order</font>` (
`id` <font color="#60ff60">INT</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font> <font color="#ffff60">AUTO_INCREMENT</font> <font color="#ffff60">PRIMARY</font> <font color="#ffff60">KEY</font> ,
`name` <font color="#60ff60">VARCHAR( </font><font color="#ffa0a0">20</font><font color="#60ff60"> )</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font>
) ENGINE = INNODB ;

<font color="#ffff60">CREATE</font> <font color="#ffff60">TABLE</font> `suborder` (
`id` <font color="#60ff60">INT</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font> <font color="#ffff60">AUTO_INCREMENT</font> <font color="#ffff60">PRIMARY</font> <font color="#ffff60">KEY</font> ,
`order_id` <font color="#60ff60">INT</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font>,
`name` <font color="#60ff60">VARCHAR( </font><font color="#ffa0a0">20</font><font color="#60ff60"> )</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font>
) ENGINE = INNODB ;

<font color="#ffff60">CREATE</font> <font color="#ffff60">TABLE</font> `infraorder` (
`id` <font color="#60ff60">INT</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font> <font color="#ffff60">AUTO_INCREMENT</font> <font color="#ffff60">PRIMARY</font> <font color="#ffff60">KEY</font> ,
`suborder_id` <font color="#60ff60">INT</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font>,
`name` <font color="#60ff60">VARCHAR( </font><font color="#ffa0a0">20</font><font color="#60ff60"> )</font> <font color="#ffff60">NOT</font> <font color="#ffa500">NULL</font>
) ENGINE = INNODB ;

<font color="#ffff60">insert</font> <font color="#ffff60">into</font> `<font color="#ffff60">order</font>` ( name ) <font color="#ffff60">values</font> ( <font color="#ffa0a0">'Primates'</font> );

<font color="#ffff60">insert</font> <font color="#ffff60">into</font> `suborder` ( order_id, name ) <font color="#ffff60">values</font> ( <font color="#ffa0a0">1</font>, <font color="#ffa0a0">'Strepsirrhini'</font> );
<font color="#ffff60">insert</font> <font color="#ffff60">into</font> `suborder` ( order_id, name ) <font color="#ffff60">values</font> ( <font color="#ffa0a0">1</font>, <font color="#ffa0a0">'Haplorrhini'</font> );

<font color="#ffff60">insert</font> <font color="#ffff60">into</font> `infraorder` ( suborder_id, name ) <font color="#ffff60">values</font> ( <font color="#ffa0a0">1</font>, <font color="#ffa0a0">'Lemuriformes'</font> );
<font color="#ffff60">insert</font> <font color="#ffff60">into</font> `infraorder` ( suborder_id, name ) <font color="#ffff60">values</font> ( <font color="#ffa0a0">1</font>, <font color="#ffa0a0">'Chiromyiformes'</font> );
<font color="#ffff60">insert</font> <font color="#ffff60">into</font> `infraorder` ( suborder_id, name ) <font color="#ffff60">values</font> ( <font color="#ffa0a0">1</font>, <font color="#ffa0a0">'Lorisiformes'</font> );
<font color="#ffff60">insert</font> <font color="#ffff60">into</font> `infraorder` ( suborder_id, name ) <font color="#ffff60">values</font> ( <font color="#ffa0a0">2</font>, <font color="#ffa0a0">'Tarsiiformes'</font> );
<font color="#ffff60">insert</font> <font color="#ffff60">into</font> `infraorder` ( suborder_id, name ) <font color="#ffff60">values</font> ( <font color="#ffa0a0">2</font>, <font color="#ffa0a0">'Simiiformes'</font> );</pre>
<p>Query analysis is done using the <a href="http://dev.mysql.com/doc/refman/5.0/en/explain.html">EXPLAIN</a> statement.  Here&#8217;s an example of how to use it:</p>
<pre>
<font color="#ffff60">explain</font> extended
<font color="#ffff60">select</font> o.name, s.name, i.name
<font color="#ffff60">from</font> `<font color="#ffff60">order</font>` o, suborder s, infraorder i
<font color="#ffff60">where</font> o.id = s.order_id
<font color="#ffff60">and</font> s.id = i.suborder_id
<font color="#ffff60">and</font> i.name = <font color="#ffa0a0">'Simiiformes'</font>;</pre>
<p>The output looks like this:</p>
<pre>
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra       |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | o     | ALL  | PRIMARY       | NULL | NULL    | NULL |    1 |             |
|  1 | SIMPLE      | s     | ALL  | PRIMARY       | NULL | NULL    | NULL |    2 | Using where |
|  1 | SIMPLE      | i     | ALL  | NULL          | NULL | NULL    | NULL |    5 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
3 rows in set, 1 warning (0.01 sec)</pre>
<p>It lists the tables in the order that they are joined in.  The letters in the table column refer to the aliases that I declared in the query.  It reads a row from the first table, then recursively finds a matching row in the second table, the third table, etc &#8230;  In my example here it reads the one row from the order table first, then joins to the suborder table, and finally the infraorder table.  </p>
<p>The &#8216;type&#8217; column is probably the most important piece of data that is returned.  The possible values, from best to worst, are system, const, eq_ref, ref, range, index, all.  As you can see, our test query has returned nothing but &#8216;ALL&#8217;, the worst possible type of join in MySQL.  ALL is basically a sequential scan, the optimizer is planning on reading every row in each table.  </p>
<p>Let&#8217;s add a few indexes and see if we can&#8217;t improve this a little bit:</p>
<pre>
<font color="#ffff60">create</font> <font color="#ffff60">index</font> ix_subordid <font color="#ffff60">on</font> infraorder ( suborder_id );
<font color="#ffff60">create</font> <font color="#ffff60">index</font> ix_ordid <font color="#ffff60">on</font> suborder ( order_id );</pre>
<pre>
+----+-------------+-------+------+------------------+----------+---------+-------------+------+-------------+
| id | select_type | table | type | possible_keys    | key      | key_len | ref         | rows | Extra       |
+----+-------------+-------+------+------------------+----------+---------+-------------+------+-------------+
|  1 | SIMPLE      | o     | ALL  | PRIMARY          | NULL     | NULL    | NULL        |    1 |             |
|  1 | SIMPLE      | s     | ref  | PRIMARY,ix_ordid | ix_ordid | 4       | monkey.o.id |    1 |             |
|  1 | SIMPLE      | i     | ALL  | ix_subordid      | NULL     | NULL    | NULL        |    4 | Using where |
+----+-------------+-------+------+------------------+----------+---------+-------------+------+-------------+
3 rows in set, 1 warning (0.00 sec)</pre>
<p>The join on the suborder table is now type &#8216;ref&#8217;.  This is a good thing, it means that the query optimizer has chosen to use our index on the suborder table for every single row that gets returned from the query.  </p>
<p>However, our index on the infraorder table isn&#8217;t being used.  Why?  I&#8217;m not entirely certain.  I assume that it has to do with how little data there is in the table. If that&#8217;s the case, then the query optimizer assumes that returning all rows in order to satisfy the &#8220;i.name = &#8216;Simiiformes&#8217;&#8221; part of the where clause is more efficient than going to ix_subordid, and then only filtering on the names of those rows returned.  </p>
<p>Let&#8217;s drop that index and create a multiple-column index instead:</p>
<pre><font color="#ffff60">drop</font> <font color="#ffff60">index</font> ix_subordid <font color="#ffff60">on</font> infraorder;
<font color="#ffff60">create</font> <font color="#ffff60">index</font> ix_infra <font color="#ffff60">on</font> infraorder ( name, suborder_id );</pre>
<pre>
+----+-------------+-------+------+------------------+----------+---------+-------------------+------+--------------------------+
| id | select_type | table | type | possible_keys    | key      | key_len | ref               | rows | Extra                    |
+----+-------------+-------+------+------------------+----------+---------+-------------------+------+--------------------------+
|  1 | SIMPLE      | o     | ALL  | PRIMARY          | NULL     | NULL    | NULL              |    1 |                          |
|  1 | SIMPLE      | s     | ref  | PRIMARY,ix_ordid | ix_ordid | 4       | monkey.o.id       |    1 |                          |
|  1 | SIMPLE      | i     | ref  | ix_infra         | ix_infra | 26      | const,monkey.s.id |    2 | Using where; Using index |
+----+-------------+-------+------+------------------+----------+---------+-------------------+------+--------------------------+
3 rows in set, 1 warning (0.00 sec)</pre>
<p>Now we&#8217;re using both the indexes in both of those tables.  There&#8217;s an important caveat to using a <a href="http://dev.mysql.com/doc/refman/5.0/en/multiple-column-indexes.html">multiple-column index</a> though.  In the example that I&#8217;ve created here, if I were to construct and execute a query on the infraorder table that referenced the name column but not the suborder_id, it would still use the index.  The opposite is not true.  </p>
<p>If you have a multiple-column index that has 3 columns, let&#8217;s call them A, B, and C.  If your query uses all three, A and B, or just A, it can still utilize the index.  If it uses just B, or just C, or B and C, it can&#8217;t.  If it refers to A and C, then it will use the index for column A, but it will have to filter out the results that don&#8217;t match column C.  </p>
<p>I hope that makes sense.  I can&#8217;t think of any clearer way to explain the concept.  </p>
<p>There are a few rules of thumb when it comes to indexes.  These are almost universal, they don&#8217;t just apply to MySQL.  First of all, you want an index that has short keys.  This refers to the key_len column when we do an explain.  The shorter the keys, the higher the number that will be read from the disk in each block.  Another benefit is it means more of the index will be cached in memory.  </p>
<p>Another rule of thumb is that you want your indexes to be as unique as possible.  If you are indexing a column, you have a 100,000 rows in the table, and the column is comprised of four unique values, then your index isn&#8217;t much use.  </p>
<p>MySQL supports indexing only part of the field.  So, if you have a varchar(200), and the majority of the entries in the column are unique across the first 10 characters, you can index just those first 10 characters.  Now, instead of using 200 characters for each key in the index, it only uses 10.  </p>
<p>There is overhead associated with indexes.  As a result, you should add them sparingly.  This is especially true if you require any sort of performance when it comes to data modification queries (insert, update, delete).  You don&#8217;t just have to modify the data, the indexes have to be modified as well.  </p>
<p>What do you gain from using multiple-column indexes?  They are more unique, and MySQL will favor the index that the query plan assumes will return the fewest number of rows.  Another really cool thing, and most databases do this, if an index has all of the data necessary to return the answer without consulting the data, then the engine will use just the index.  Examples are queries where you are trying to find a count of rows, a min or a max, etc &#8230; These are called covered queries and there are more possibilities if your index refers to more columns.  Don&#8217;t go listing all the columns though.  Then you just end up duplicating the table in the index space.  Remember, small keys.  </p>
<p>Two other things before I go to bed.  I keep mentioning the query optimizer.  Optimizer&#8217;s are a fascinating subject.  They rely on statistical information about the database in order to make an educated guess about how to execute the query.  The &#8216;<a href="http://dev.mysql.com/doc/refman/5.0/en/analyze-table.html">ANALYZE TABLE</a>&#8216; command analyzes the table and stores the key distribution information that the optimizer uses to make some of those decisions.  It needs to be run periodically, in some cases frequently, in order for the optimizer to function properly.  </p>
<p>Another important statement is &#8216;<a href="http://dev.mysql.com/doc/refman/5.0/en/table-optimization.html">OPTIMIZE TABLE</a>&#8216;.   If you use varchar, or any other variable length data type, then deleting data fragments your table.  The OPTIMIZE command is a defragmenter for your database.  It also sorts the index keys, which can improve performance.  </p>
<p>Wow &#8230; sleepy now.  If you are in the market for an effective sedative, then I recommend reading the <a href="http://dev.mysql.com/doc/refman/5.0/en/">MySQL 5.0 Reference Manual</a>. <img src='http://www.boyplankton.com/wp-includes/images/smilies/icon_wink.gif' alt=';-)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://www.boyplankton.com/2007/03/25/mysql-indexes/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Ready for some bad news?</title>
		<link>http://www.boyplankton.com/2007/03/12/ready-for-some-bad-news/</link>
		<comments>http://www.boyplankton.com/2007/03/12/ready-for-some-bad-news/#comments</comments>
		<pubDate>Tue, 13 Mar 2007 00:39:41 +0000</pubDate>
		<dc:creator>Boy Plankton</dc:creator>
				<category><![CDATA[Administration]]></category>

		<guid isPermaLink="false">http://www.boyplankton.com/2007/03/12/ready-for-some-bad-news/</guid>
		<description><![CDATA[From the Energy Policy Act of 2005:
SEC. 110. DAYLIGHT SAVINGS.
(a) AMENDMENT.—Section 3(a) of the Uniform Time Act of 1966 15 U.S.C. 260a(a)) is amended—
(1) by striking ‘‘first Sunday of April’’ and inserting ‘‘second sunday of March’’; and
(2) by striking ‘‘last Sunday of October’’ and inserting ‘‘first Sunday of November’’.
(b) EFFECTIVE DATE.—Subsection (a) shall take effect [...]]]></description>
			<content:encoded><![CDATA[<p>From the <a href="http://www.doi.gov/iepa/EnergyPolicyActof2005.pdf">Energy Policy Act of 2005</a>:</p>
<blockquote><p>SEC. 110. DAYLIGHT SAVINGS.<br />
(a) AMENDMENT.—Section 3(a) of the Uniform Time Act of 1966 15 U.S.C. 260a(a)) is amended—<br />
(1) by striking ‘‘first Sunday of April’’ and inserting ‘‘second sunday of March’’; and<br />
(2) by striking ‘‘last Sunday of October’’ and inserting ‘‘first Sunday of November’’.<br />
(b) EFFECTIVE DATE.—Subsection (a) shall take effect 1 year after the date of enactment of this Act or March 1, 2007, whichever is later.<br />
(c) REPORT TO CONGRESS.—Not later than 9 months after the effective date stated in subsection (b), the Secretary shall report to Congress on the impact of this section on energy consumption in the United States.<br />
<strong><em>(d) RIGHT TO REVERT.—Congress retains the right to revert the Daylight Saving Time back to the 2005 time schedules once the Department study is complete.</em></strong></p></blockquote>
<p>If these <a href="http://abcnews.go.com/Technology/print?id=2938715">two economists are right</a>, then there&#8217;s a good chance we get to do the last few weeks over again in order to put everything back.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.boyplankton.com/2007/03/12/ready-for-some-bad-news/feed/</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Locking down scp &#8230;</title>
		<link>http://www.boyplankton.com/2007/02/24/locking-down-scp/</link>
		<comments>http://www.boyplankton.com/2007/02/24/locking-down-scp/#comments</comments>
		<pubDate>Sat, 24 Feb 2007 18:36:50 +0000</pubDate>
		<dc:creator>Boy Plankton</dc:creator>
				<category><![CDATA[Administration]]></category>

		<guid isPermaLink="false">http://www.boyplankton.com/2007/02/24/locking-down-scp/</guid>
		<description><![CDATA[The other day I needed a quick and dirty solution to lock down an account so that it could only use scp or sftp into the machine.  I could have installed something like rssh, but I needed something very quickly and wasn&#8217;t certain that I could get it installed/configured in time.  Instead I [...]]]></description>
			<content:encoded><![CDATA[<p>The other day I needed a quick and dirty solution to lock down an account so that it could only use scp or sftp into the machine.  I could have installed something like <a href="http://www.pizzashack.org/rssh/">rssh</a>, but I needed something very quickly and wasn&#8217;t certain that I could get it installed/configured in time.  Instead I added a shell to /etc/shells called /bin/noshell and I made it the users default shell.  Then I created a file called /bin/noshell and put the following code in it:</p>
<blockquote>
<pre><font color="#80a0ff">#!/bin/bash</font>

<font color="#ffff60">if</font> <font color="#ffff60">[</font> <font color="#ffff60">-t</font> <font color="#ffa0a0">0</font> <font color="#ffff60">]</font><font color="#ffff60">;</font> <font color="#ffff60">then</font>
        <font color="#ffff60">echo</font><font color="#ffa0a0"> </font><font color="#ffff60">'</font><font color="#ffa0a0">You are not allowed to login to this shell.</font><font color="#ffff60">'</font>
<font color="#ffff60">else</font>
        /bin/bash <font color="#ff80ff">${</font><font color="#ff80ff">1</font><font color="#ff80ff">}</font> <font color="#ffff60">"</font><font color="#ff80ff">${</font><font color="#ff80ff">2</font><font color="#ff80ff">}</font><font color="#ffff60">"</font>
<font color="#ffff60">fi</font>
<font color="#ffff60">exit</font> <font color="#ffa0a0">0</font></pre>
</blockquote>
<p>After making the script executable, it has the following results:</p>
<blockquote>
<pre>&gt; scp filename.txt username@machine:
username@machine's password: filename.txt
100% 9695     9.5KB/s   00:00
&gt; ssh username@machine
username@machine's password:
You are not allowed to login to this shell.
Connection to machine closed.</pre>
</blockquote>
<p>The reason that this works is that both scp and sftp work by initializing an ssh connection and starting scp or sftp on the server side.  Basically, the command</p>
<blockquote>
<pre>scp filename username@machine:</pre>
</blockquote>
<p>is, initially at least, the same as sending the command</p>
<blockquote>
<pre>ssh username@machine ${SHELL} -c "scp -t ."</pre>
</blockquote>
<p>By changing the user&#8217;s shell to my script,  I am effectively running that test &#8220;-t 0&#8243; before giving them the opportunity to start the command that they are sending over.  The test checks to see if the command is being run on a terminal or not.</p>
<p>Now, there are several issues with the approach that I took.  The first is that a user who understands what I&#8217;m doing could very easily get around this.  There&#8217;s nothing to stop them from sending their own commands instead of scp or sftp.  This could be fixed rather easily though, all that someone would need to do is test ${2} to make certain that the string begins with either scp or sftp.  The other issue that I have is that a user could easily write to files outside of their home directory.  After testing to see if the command is scp or sftp all someone would have to do is replace ${2} with the appropriate command followed by a &#8221; -t .&#8221;  That would effectively close those two holes.</p>
<p>Here&#8217;s a <a href="http://www.snailbook.com/faq/scp-wrapper.txt">perl version</a> that I just found from the Snail Book.   The author addresses the first issue that I have with my bash version in basically the same way I described above.</p>
]]></content:encoded>
			<wfw:commentRss>http://www.boyplankton.com/2007/02/24/locking-down-scp/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

<!-- Dynamic Page Served (once) in 0.434 seconds -->
