Wednesday, April 18, 2007

MySQL Stored routines at the Users Conference

MySQl Users Conference and Expo
The Users Conference and Expo is approaching fast. As a last minute assignment, I will be speaking about Stored routines for MySQL administration. The session will cover the basics of stored routines and their specifics for administration.


Coincidentally, today was published a review of an excellent book about MySQL Stored Procedures programming by Guy Harrison and Steven Feuerstein. I warmly recommend this book to anyone using stored routines with MySQL.

Thursday, April 12, 2007

Mentoring a Summer of Code project



It's official.
I am now appointed mentor of a Google Summer of Code project.
Congratulations to Charlie Cahoon, who has submitted an intriguing proposal for improving our code coverage and testing tools. The abstract doesn't do justice to the project. The juicy part is in the details. More about it later.
More information on Kaj's announcement.

Thanks, Charlie, for proposing this project!

Thanks, Google, for promoting this great infrastructure!

Wednesday, April 11, 2007

Handling multiple data sets in stored procedures from Perl

MySQL stored procedures can use SELECT statements in their body, returning a data set to the caller. The manual also explains that multiple data sets can be returned from such a stored procedure.
For example, assume you have this stored procedure:
CREATE PROCEDURE p1(x INT, y INT)
DETERMINISTIC
BEGIN
SELECT
x ;
SELECT
x AS first_param,
y AS second_param;
SELECT
x,
y,
x + y AS sum_xy,
x * y AS prod_xy;
SELECT * FROM t1;
END

There are four SELECT statements, with different headers, and a variable number of rows returned.
Early versions of DBD::mysql could not handle multiple data sets. Actually, prior to version 3.0004 they could not even handle one dataset from a stored procedure. Starting with version 4.001, single and multiple datasets from SPs are handled gracefully.
The trick is to call $sth->more_results after retrieving each dataset.
See the complete example at Multiple data sets in MySQL stored procedures

MySQl Users Conference and Expo
For those interested, the MySQL Users Conference and Expo includes a BoF about DBD::mysql on April 24th at 7:30pm.

Monday, April 09, 2007

Logs on demand, a DBA's prayer come true

MySQL Conference and Expo
Several months ago I wrote about logs on demand in MySQL 5.1.
Now that 5.1 is approaching its maturity stage, I am happy to report that this feature has proven itself very handy and useful.
Petr Chardin will talk about this very feature at the MySQL Conference and Expo.

While reviewing the material for this session, I remembered a usability report that I wrote when the feature was announced. Among other things I wrote that you can create ad hoc log files for separate needs, but you can't do the same thing for tables. This reminds me of a general rule of technology: "If a respected scientist says a thing is possible, you can be almost certain he's right. If an established, respected scientist tells you that something is impossible, he's almost certainly wrong." I was wrong. It can be done. To know how, attend the aforementioned session, or wait until I post an a follow-up to this post after the conference.

Tuesday, April 03, 2007

When Community strikes

A few days ago a bug report was filed to the MySQL bugs system. For a few hours it was just one among the many, although it looked hard to reproduce.
Then, suddenly, two experienced contributors, Martin Friebe and Alexander Fomichev, joined the discussion, offering similar cases, explanations, a workaround, and even a patch.

The public intervention took place within 11 hours after the MySQL Engineer asked for clarifications, and within one day the solution was served!

Thanks guys!
We love it when bugs are solved this way.

Wednesday, March 21, 2007

Rush to register for the MySQL Summer of Code (extended deadline!)



MySQL has joined Google
Summer of Code 2007 and has launched its own Summer of Code branch.

Are you a student and a skilled programmer? Do you use MySQL? Do you have ideas on how to improve it? (Do you want to use this cool logo in your blog? :) )
Here's your chance to participate. Rush to read the announcement and the full instructions in MySQL Forge and then apply!
Time is short. The deadline is Saturday, March 24, 2007 Tuesday, March 27 2007 (extended!)!

Tuesday, March 06, 2007

Federated, Blackhole, and Contributors at the MySQL Conference and Expo 2007


I will be involved in two sessions at the MySQL Users Conference and Expo 2007.
This year conference is going to be more user-oriented than ever before. Many sessions by MySQL employees will be co-presented by external users.
Some examples:
In short, there is a lot to see and do, (and not enough space in this blog). Visit the official site and judge for yourself.

Sunday, March 04, 2007

Test driving SHOW PROFILES

SHOW PROFILES is finally available! It has been a long struggle, but finally this important community patch by Jeremy Cole has been integrated into a publicly distributed MySQL source tree (thanks to Chad Miller excellent integration work).
What's SHOW PROFILES? Is a feature to let you peek inside MySQL internals, which tell you what your queries were doing during their execution.
This feature is also remarkable because it makes a significant difference between Community and Enterprise releases.
The binaries will be available soon, but in the meantime, you can build the source code and give it a try.

Building

To build from source, follow the instructions at Installing from the Development Source Tree using the Community tree.
bkf clone bk://mysql.bkbits.net/mysql-5.0-community mysql-5.0
And since this is a community feature, the documentation can be provided by the community as well. I posted a documentation stub in MySQL Forge. Feel free to add to it.

Test driving

Once you have built your community distribution (or after downloading the binaries, if you are reading this when they are already available), it's time to see what SHOW PROFILE can do.
To use this addition, you need to enable it. The session variable profiling is used to enable and disable it. By default, it is disabled, so the first order of business is: turn it on!

select @@profiling;
+-------------+
| @@profiling |
+-------------+
| 0 |
+-------------+

set profiling = 1;

select @@profiling;
+-------------+
| @@profiling |
+-------------+
| 1 |
+-------------+
Cool. Now what?
Now we play around with the system, and see how profiles works.

drop table if exists t1;
create table t1 (id int not null);
insert into t1 values (1), (2), (3);
select * from t1;

#
# AND FINALLY!
#

show profiles;
+----------+----------+-------------------------------------+
| Query_ID | Duration | Query |
+----------+----------+-------------------------------------+
| 0 | 0.000045 | set profiling = 1 |
| 1 | 0.000071 | select @@profiling |
| 2 | 0.000087 | drop table if exists t1 |
| 3 | 0.031741 | create table t1 (id int not null) |
| 4 | 0.000615 | insert into t1 values (1), (2), (3) |
| 5 | 0.000198 | select * from t1 |
+----------+----------+-------------------------------------+
Here is the first glimpse. Basically, it tells you the duration of each query. Not very exciting so far, but let's keep going. The Query_ID field is a reference for us, so that we can ask more about a specific query. In this list, the table creation is query no. 3, so let's ask some specifics about this one:

SHOW PROFILE FOR QUERY 3;
+----------------------+----------+
| Status | Duration |
+----------------------+----------+
| checking permissions | 0.000040 |
| creating table | 0.000035 |
| After create | 0.031432 |
| query end | 0.000221 |
| freeing items | 0.000005 |
| logging slow query | 0.000006 |
| cleaning up | 0.000002 |
+----------------------+----------+
That's wonderful! Now we know more about the single tasks this query was performing. But it gets even better. Look at this:
SHOW PROFILE CPU FOR QUERY 3;
+----------------------+----------+----------+------------+
| Status | Duration | CPU_user | CPU_system |
+----------------------+----------+----------+------------+
| checking permissions | 0.000040 | 0.000000 | 0.000000 |
| creating table | 0.000035 | 0.000000 | 0.000000 |
| After create | 0.031432 | 0.001000 | 0.000000 |
| query end | 0.000221 | 0.000000 | 0.000000 |
| freeing items | 0.000005 | 0.000000 | 0.000000 |
| logging slow query | 0.000006 | 0.000000 | 0.000000 |
| cleaning up | 0.000002 | 0.000000 | 0.000000 |
+----------------------+----------+----------+------------+
If you are debugging code, you may like the information provided by the following option:
SHOW PROFILE source FOR QUERY 3;
+----------------------+----------+-----------------------+--------------+-------------+
| Status | Duration | Source_function | Source_file | Source_line |
+----------------------+----------+-----------------------+--------------+-------------+
| checking permissions | 0.000040 | check_access | sql_parse.cc | 5256 |
| creating table | 0.000035 | mysql_create_table | sql_table.cc | 1737 |
| After create | 0.031432 | mysql_create_table | sql_table.cc | 1768 |
| query end | 0.000221 | mysql_execute_command | sql_parse.cc | 5085 |
| freeing items | 0.000005 | mysql_parse | sql_parse.cc | 5973 |
| logging slow query | 0.000006 | log_slow_statement | sql_parse.cc | 2178 |
| cleaning up | 0.000002 | dispatch_command | sql_parse.cc | 2143 |
+----------------------+----------+-----------------------+--------------+-------------+
It tells you how much time was spent, and which lines of code you should look at if you want to improve it!
More options available in the documentation stub.
Rather than trying all of them here, I will just give you a simple example of what can be hidden behind a query. Check this out:
connect;
set profiling=1;
use information_schema;
select count(*) from columns where table_schema='mysql';
+----------+
| count(*) |
+----------+
| 147 |
+----------+

show profiles;
+----------+----------+---------------------------------------------------------+
| Query_ID | Duration | Query |
+----------+----------+---------------------------------------------------------+
| 0 | 0.000194 | set profiling=1 |
| 1 | 0.000006 | SELECT DATABASE() |
| 2 | 0.005928 | select count(*) from columns where table_schema='mysql' |
+----------+----------+---------------------------------------------------------+

show profile for query 2;
+----------------------+----------+
| Status | Duration |
+----------------------+----------+
| Opening tables | 0.000051 |
| System lock | 0.000281 |
| Table lock | 0.000002 |
| init | 0.000006 |
| optimizing | 0.000020 |
| statistics | 0.000009 |
| preparing | 0.000012 |
| executing | 0.000011 |
| checking permissions | 0.000039 |
| Opening tables | 0.000136 |
| checking permissions | 0.000010 |
| Opening tables | 0.000185 |
| checking permissions | 0.000008 |
| Opening tables | 0.000478 |
| checking permissions | 0.000007 |
| Opening tables | 0.000107 |
| checking permissions | 0.000006 |
| Opening tables | 0.000096 |
| checking permissions | 0.000006 |
| Opening tables | 0.000052 |
| checking permissions | 0.000006 |
| Opening tables | 0.000051 |
| checking permissions | 0.000007 |
| Opening tables | 0.000145 |
| checking permissions | 0.000006 |
| Opening tables | 0.000445 |
| checking permissions | 0.000007 |
| Opening tables | 0.000395 |
| checking permissions | 0.000007 |
| Opening tables | 0.000189 |
| checking permissions | 0.000007 |
| Opening tables | 0.000195 |
| checking permissions | 0.000005 |
| Opening tables | 0.000053 |
| checking permissions | 0.000006 |
| Opening tables | 0.000055 |
| checking permissions | 0.000005 |
| Opening tables | 0.000058 |
| checking permissions | 0.000005 |
| Opening tables | 0.000064 |
| checking permissions | 0.000005 |
| Opening tables | 0.000123 |
| checking permissions | 0.000007 |
| Sending data | 0.000898 |
| end | 0.001215 |
| query end | 0.000007 |
| freeing items | 0.000003 |
| closing tables | 0.000010 |
| removing tmp table | 0.000002 |
| closing tables | 0.000420 |
| logging slow query | 0.000003 |
| cleaning up | 0.000002 |
+----------------------+----------+
52 rows in set (0.00 sec)
Now it's your turn to experiment and share your experiences!

Tuesday, February 27, 2007

What every developer would like to see

A few days ago, MySQL launched the Quality Contribution Program. The main purpose of that program is to improve the quality of submissions from active users. We published the rules, and soon we saw more reports coming our way, with better contents than usual.
The commotion happened yesterday evening, when I noticed a bug report that looked interesting. At a quick glance, it was. A clear description of the problem. Cool. An attached test case. Great! And a result file! Even better. And the closing remark, "See patch", was just too much to believe. So, there was a bug report telling us about a problem, including a clean way of reproducing it, and even saying where the problem is in the code and how to fix it!
It was already late in the day, but I told myself that it wouldn't take me too long to verify the claim. So I used the latest 5.0.38 build in my laptop and I ran the test case. Bingo! The problem was there, just as described. After inspecting the resulting data, I was satisfied that the claim was justified, and I noted that much in the test report. Then I alerted Chad Miller, my colleague in the Community-Engineering team, who in turn informed the leader of the team involved in that particular problem.
One hour later, the bug went from verified to patch pending, meaning that a developer had examined the patch, and it was considered up to company standards, ready for review.
Within two hours from its submission, the problem was virtually fixed!
The author of this exploit is Martin Friebe, who, not by chance, is leading the contributors list in our program.
Our goal in launching the Quality Contribution Program was exactly this: getting better bug reports, and Martin has shown the way.
Thanks Martin! Keep up the good work!

Sunday, February 25, 2007

Launching the Quality Contribution Program

Quality Contribution ProgramAfter a long period of preparation, my pet project is out! The Quality Contribution Program is here!
MySQL wants to reward its most active users who are contributing to the improvement of its products.
This is not a lottery, where you submit some contributions, and if you are chosen you get the prize. In this program, you start contributing, and when you reach a given level, you (and everybody else in the same position) will get a free subscription to MySQL Enterprise. The project rules say how much you should contribute to get a Basic, Silver, Gold, or even Platinum subscription.

Thursday, January 18, 2007

Building MySQL 5.1 from a source tree on Mac OS X

The objective of this exercise is compiling MySQL from a source tree (BitKeeper) on Mac OS X.

I bought a new laptop, a MacBook running Mac OS X Tiger. I was captivated by the user interface, and I was willing to try this new experience, after a long and successful relationship with Linux (I gave up Windows many years ago). Everything went well, until the moment I tried to compile MySQL from source.
Using the default Xcode tools, I was able to compile the source packages provided with the GA release (5.0.x). But with 5.1 it was a different story. I was not even able to complete the compile part. something was breaking quite soon during the process.

After unsuccessfully trying all the tools made by the MySQL Build Department, I finally decided to take things into my own hands, and to create a reliable compiling environment.

I - Isolating the environment

I decided that the sanest course of action was to get the most recent build tools and then I checked whether I could get some of them via Fink or apt-get. Simply running "fink selfupdate" did most of the necessary work. After that I had a very recent version of gcc/g++.
The GNU tools for building were not available through the standard interface, and then I had to compile them from scratch. I was a bit reluctant, because I could disrupt some existing dependency, and hinder the functionality of the Mac OS X development tools. Thus, I set up a limited environment for building.
  • create a directory $HOME/usr/local/bin
  • change the PATH to include the new binary directory at the start.
export PATH=$HOME/usr/local/bin:$PATH

Now whenever I start a build task, programs in the new path will take precedence over the built-in ones. This way I can still use the development tools to make Mac specific applications, and at the same time I can use the most recent tools for my job related purposes.

II - Making the building tools

Once the path is set, it's time to build the tools.
  • compile the new tools in the following order:
    • autoconf-2.61 http://ftp.gnu.org/gnu/autoconf/autoconf-2.61.tar.gz
    • libtool-1.5.22 http://ftp.gnu.org/gnu/libtool/libtool-1.5.22.tar.gz
    • automake-1.10 http://ftp.gnu.org/gnu/automake/automake-1.10.tar.gz
    • byacc-20050813 ftp://invisible-island.net/byacc/byacc.tar.gz
    • bison-2.3 http://ftp.gnu.org/gnu/bison/bison-2.3.tar.gz
For each tool, I issued this command:
./configure --prefix=$HOME/usr/local && make && make check && make install
automake took almost 40 minutes (it has a huge regression test suite) but in the end, I managed to get all the tools in working shape. It's important to do the installation in the above order. Testing of automake fails if autoconf and libtool were not already compiled and installed. I know because I tried to build all of them in parallel and it failed.

III - Using them

Now that I have the tools, I can do something practical.
$ bk clone username@bk_repository/mysql-5.1 51
$ cd 51
$ BUILD/compile-dist
$ make test
$ make dist
And it works!

BTW, BUILD/compile-dist and make dist are the commands used by the MySQL build team to create a source distribution. You can do that as well from a development source tree.

Caveat

This material is my personal experience (which is quite limited with Mac OS X). The reason for installing so many packages from source is because the ones that are provided natively fail sooner or later during the task that I needed to perform.
If any Mac OS X expert has a better solution for this particular problem, I will listen.

Update

Being a newbie at Mac OS X, I missed the simple point that I could have installed the needed tools with Darwin Ports. I would have saved time and it would have taken care of the dependency issues. Good to know for next time!

Thursday, January 11, 2007

What is a bug?

It happens sometimes, when I report a bug, that I have an argument with someone at the receiving end of the reporting chain. The raw happens over the definition of a bug. For instance, there is a new implementation of a consolidated tool. The new tool does almost all the old one did, except X. Therefore I file a bug report saying that X is missing. The ensuing argument runs along the lines of:
- This is not a bug, says the Verifier. You can't say it's a bug because it does not do what you want. It should be downgraded to feature request.
- This is the recommended replacement of the old tool, I retort, and as such it should do at least what the old tool did, plus the new stuff. I insist it is a bug.
- The manual does not mention feature X for the new tool, and then it is not a bug. It's a feature request"
The Verifier's reasoning is technically correct, and it is within the boundaries of his allowed action, so I see no point in arguing much more.
However, I can start a campaign of educating people on bug evaluation, in hope that these notes reach the ones in charge of writing the guidelines, so that this kind of requests is met more kindly next time.

So, what exactly is a Bug?

I start by borrowing some definitions from Ron Patton's excellent book on Software testing. According to that, it's a bug when ...
  1. The software doesn’t do something that the product specification says it should do.
  2. The software does something that the product specification says it shouldn’t do.
  3. The software does something that the product specification doesn’t mention.
  4. The software doesn’t do something that the product specification doesn’t mention but should.
  5. The software is difficult to understand, hard to use, slow, or—in the software tester’s eyes—will be viewed by the end user as just plain not right.
(There is an online sample chapter, The realities of software testing containing the above definition)
Let's comment on each point.

Broken promise (positive)

The software doesn’t do something that the product specification says it should do.
This is a no brainer. If you said that the editor would save my file when I hit the "save" button, and it doesn't, then it's a bug.

Broken promise (negative)

The software does something that the product specification says it shouldn’t do.
This is as simple as the previous one. If you say that the program won't overwrite my painfully composed setup file and instead it silently restores the defaults, then it's a bug.

Collateral damage

The software does something that the product specification doesn’t mention.
Here we enter in tricky territory. There is an extra feature not mentioned in the manual. Sometimes you should be just happy and say thank you, for instance if your word processor can handle simple spreadsheet-like calculations in table cells. But if the same word processor makes a hidden copy of every document you edit for backup purposes, you may get quite angry. Either way, is anything of this sort happens, then it's a bug.

Forgotten specs

The software doesn’t do something that the product specification doesn’t mention but should.
At this point, we start bickering loudly. If your word processor's manual does not mention the "save" option, and such option is nowhere to be found in the menu and task bars, anyone faintly computer-literate would complain that this omission is inexcusable. If such a missing feature happens, then it's a bug.

Catch-all clause

The software is difficult to understand, hard to use, slow, or—in the software tester’s eyes—will be viewed by the end user as just plain not right.

And now the bickering becomes open fight. You, the user, have the right to comfortable usage. The specifications warns you that you may get unpleasant side effects when using a killer application. You understand it and run it anyway, but it happens that the mentioned side effects are causing real damage. Guess what? It's a bug. No matter if it was in the docs. It should have been under the "known bugs" section. For instance, the docs may warn you that a system monitor, to keep you informed of the health of a database, will poll the server using a non-existing user. That sounds fine, but later, when your intrusion detection system rings you at 3am saying that the number of failed login attempts has reached the critical level and as a safety measure the database server was shut down, then, I can't help it, it's a bug.

This latest rule must not be used as an excuse to pass any request as a bug report. It's hard to set a clear boundary, but sometimes, without this fifth rule, many critical bugs would remain latent.

Thursday, December 28, 2006

MySQL Quality Assurance forum

As preparation for the upcoming Quality Contribution Program, a new MySQL forum was created today.
The new forum is dedicated to Quality Assurance matters. It is not the place where to submit bugs (there is already the bug reporting system for that purpose). It is rather a place where to discuss quality assurance problems, such as:
  • How do I report this particular kind of bug?
  • How do I make a test case for this specific situation?
  • What is the best strategy to report a nasty cluster of bugs?
  • Improving testing techniques;

Everything related to Quality assurance can be discussed there. If you have an idea on how to make better test cases, go there and launch the challenge. If you want to experiment a new technique for bug hunting, let's hear it!

Thursday, November 30, 2006

The hidden risks of SQL MODE

MySQL 5.0 introduces improved SQL modes, which can fine tune the way your server behaves. If you are a long term MySQL user, you may be familiar with the speed for accuracy trade-off. MySQL has a default for each field, and guesses a value when you don't provide an appropriate one when inserting or updating. If this behavior is not acceptable to you, you can now tell the server to be less permissive. Check out an article by Robin Schumacher, where this concept is explained thoroughly.

If you look at the manual, though, you will see that the SQL modes are quite a few, and you may be tempted to combine some of them to control every tiny part of the server input.
Beware, though. There are some pitfalls that you should be aware of. Let's walk through an example.

SET SQL_MODE='';
select @@sql_mode, cast(1 as unsigned) - cast(2 as unsigned);
+------------+-------------------------------------------+
| @@sql_mode | cast(1 as unsigned) - cast(2 as unsigned) |
+------------+-------------------------------------------+
| | 18446744073709551615 |
+------------+-------------------------------------------+
What is this? It is a subtraction between two unsigned values. There is an overflow in the result, and then the result of 1-2 becomes the highest BIGINT value minus one.
We can control this behavior, and use a specific SQL_MODE, NO_UNSIGNED_SUBTRACTION, to tell the server that it should not allow a subtraction between unsigned values, and treat them as signed instead.
SET SQL_MODE='NO_UNSIGNED_SUBTRACTION';
select @@sql_mode, cast(1 as unsigned) - cast(2 as unsigned);
+-------------------------+-------------------------------------------+
| @@sql_mode | cast(1 as unsigned) - cast(2 as unsigned) |
+-------------------------+-------------------------------------------+
| NO_UNSIGNED_SUBTRACTION | -1 |
+-------------------------+-------------------------------------------+
Fine. Now we know that we can take control of subtractions. But there is something more to know. The SQL MODE sticks to each procedure, function, or trigger, meaning that each routine is executed using the SQL mode that was active at creation time. This could lead to surprising results.
set sql_mode='';
drop function if exists subtraction;
create function subtraction(x int unsigned, y int unsigned)
returns int
deterministic
return x - y;

SET SQL_MODE='NO_UNSIGNED_SUBTRACTION';

select @@sql_mode, subtraction(1,2), cast(1 as unsigned) - cast(2 as unsigned);
+-------------------------+------------------+-------------------------------------------+
| @@sql_mode | subtraction(1,2) | cast(1 as unsigned) - cast(2 as unsigned) |
+-------------------------+------------------+-------------------------------------------+
| NO_UNSIGNED_SUBTRACTION | 2147483647 | -1 |
+-------------------------+------------------+-------------------------------------------+
Look here. We set the SQL_MODE to NO_UNSIGNED_SUBTRACTION, because we want to avoid that unpleasant effect, but the subtraction function was created with a different SQL_MODE.
Therefore, the operations inside such function will be affected by the stored SQL_MODE, regardless of the one that is active at the moment.

Whenever your result depends on a specific SQL_MODE, always check which mode is associated with the stored routines or triggers that you are using.
SELECT
routine_name,. sql_mode
FROM
information_schema.routines
WHERE routine_schema='test'
AND routine_name='subtraction'
AND routine_type='function';
+--------------+----------+
| ROUTINE_NAME | SQL_MODE |
+--------------+----------+
| subtraction | |
+--------------+----------+

There is also a more complex example involving triggers.

Monday, November 13, 2006

MySQL testing techniques: comparing tables

This is the first tutorial of a series dedicated to testing techniques.
Soon all this material will find a home in a more appropriate place. In the meantime, enjoy the lecture!

While testing large tables, it is useful to test if two tables have the same contents. For example, if you want to compare performance between two tables using different storage engines, or tables created on different filesystems, you must be sure that both tables have the same content. Having loaded both from the same source is not a guarantee that the contents are the same: a mistake or different SQL modes used during the load may result in substantial differences.

General concepts

Then, you need to compare two, possibly very large tables. There are several methods available. One is to run a query with a LEFT OUTER JOIN. However, this method is likely to take very long or even exhaust your system resources if your tables are really large.
One method that I have been advocating for long time is to run a global CRC on both tables and then compare the results.
And, I hear you asking, how do you get a global table CRC?
There is no predefined SQL feature for this task. Recent MyISAM tables have a built-in CRC, but you can't get it from a SELECT statement, and besides, if you need to compare the contents of such a table with one using a different engine, you are out of luck. Then, we need to use something more general, which can be applied to any table.
The first step to get a global CRC is to get a list of the columns that we can then pass to a CRC function such as SHA1 or MD5.

This list is a string made of the name of the columns, which we will pass to a CONCAT_WS function. However, if you know how SQL functions work, you will know that any NULL value in the list will nullify the whole expression. Therefore, we need to make sure that every nullable column is properly handled by a COALESCE function. The result of this operation, which we delegate to a stored function, is a safe list of column.
The second step towards a global table CRC is to calculate a CRC for each record. We use the above list of columns to create a SELECT statement returning a SHA1 for each record in the table. But, what to do with it? There is no aggregate SQL function available for SHA or MD5. Thus, we need to process the result and calculate our CRC manually.
As noted in a previous post, we can do that in two ways, using cursors or using a blackhole table. Some benchmarks show that the blackhole table is much faster than the cursor, and this is what we do.

We start with an empty CRC. For each row, we compute a CRC of the whole record, plus the existing CRC. Since we are using a SELECT statement, we need to get rid of the output, because we are only interested in the calculation stored in the user variable. For this purpose, a black hole table is very well suited. At the end of the SELECT + INSERT operation, we have in hand two variables, one showing the count and one holding the global CRC for the table.
Repeating this process for the second table we need to compare, we can then compare two simple values, and determine at a glance if we are dealing with comparable data sets.

Implementation

Let's put the concepts together with a few stored routines.

delimiter //

drop function if exists get_safe_column_list //
create function get_safe_column_list
(
p_db_name varchar(50),
p_table_name varchar(50),
p_null_text varchar(20)
)
returns varchar(10000)
reads sql data
begin
if ( @@group_concat_max_len < 10000 ) then
set group_concat_max_len = 10000;
end if;
return (
select
group_concat( if(is_nullable = 'no', column_name,
concat("COALESCE(",column_name, ", '", p_null_text,"')") ))
from
information_schema.columns
where
table_schema= p_db_name
and
table_name = p_table_name
);
end //
The first function returns a safe list of column names.

drop function if exists get_primary_key //
create function get_primary_key (
p_db_name varchar(50),
p_table_name varchar(50)
)
returns varchar(10000)
begin
if ( @@group_concat_max_len < 10000 ) then
set group_concat_max_len = 10000;
end if;
return (
select
group_concat(column_name order by ORDINAL_POSITION)
from information_schema.KEY_COLUMN_USAGE
where
table_schema=p_db_name
and table_name = p_table_name
and constraint_name = 'PRIMARY' );
end //
The second routine returns a table primary key, as a list of column.
drop procedure if exists table_crc //
create procedure table_crc (
IN p_db_name varchar(50),
IN p_table_name varchar(50),
OUT p_table_crc varchar(100)
)
reads sql data
main_table_crc:
begin
declare pk varchar(1000);
declare column_list varchar(10000);
set pk = get_primary_key(p_db_name, p_table_name);
set column_list = get_safe_column_list(p_db_name, p_table_name, 'NULL');
if (column_list is null) then
set p_table_crc = null;
leave main_table_crc;
end if;
set @q = concat(
'INSERT INTO bh SELECT @tcnt := @tcnt + 1, ',
'@tcrc := SHA1(CONCAT(@tcrc, CONCAT_WS("#",', column_list, ')))',
' FROM ', p_db_name, '.', p_table_name,
if (pk is null, '', concat(' ORDER BY ', pk))
);
drop table if exists bh;
create table bh (counter int, tcrc varchar(50)) engine = blackhole;
set @tcrc= '';
set @tcnt= 0;
prepare q from @q;
execute q;
set p_table_crc = concat(@tcnt,'-',@tcrc);
deallocate prepare q;
end //
The third procedure returns the global CRC of a given table.

drop procedure if exists table_compare //
create procedure table_compare (
IN p_db_name1 varchar(50),
IN p_table_name1 varchar(50),
IN p_db_name2 varchar(50),
IN p_table_name2 varchar(50),
OUT same_contents boolean
)
begin
declare crc1 varchar(100);
declare crc2 varchar(100);
call table_crc(p_db_name1,p_table_name1, crc1);
call table_crc(p_db_name2,p_table_name2, crc2);
select concat(p_db_name1, '.', p_table_name1) as table_name, crc1 as crc
union
select concat(p_db_name2, '.', p_table_name2) as table_name, crc2 as crc ;
set same_contents = (crc1 = crc2);
select crc1=crc2 as 'same contents';
end //

delimiter ;
The final routine puts all pieces together, returning a boolean value telling if the two tables have the same contents.

Testing

After loading the above code in our database, we can call a "table_crc" procedure to get our coveted value. Let's take the famous world database and let's give it a try.

mysql> use world;
Database changed
mysql> create table City2 like City;
Query OK, 0 rows affected (0.03 sec)

mysql> alter table City2 ENGINE = InnoDB;
Query OK, 0 rows affected (0.09 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> insert into City2 select * from City order by District, population;
Query OK, 4079 rows affected (0.33 sec)
Records: 4079 Duplicates: 0 Warnings: 0

First of all, we create another table, with the same structure of City, but using a different engine, and storing data in a bizarre order to see if our routine is robust enough. In fact our routine will calculate the CRC after sorting the data by primary key, so that there won't be any surprise.

mysql> call table_crc(schema(), 'City', @city_crc);
Query OK, 0 rows affected, 1 warning (0.16 sec)

mysql> select @city_crc;
+-----------------------------------------------+
| @city_crc |
+-----------------------------------------------+
| 4079-407840fbf812b81eee55d3a438cf953f81c63bc0 |
+-----------------------------------------------+
1 row in set (0.00 sec)

mysql> call table_crc(schema(), 'City2', @city2_crc);
Query OK, 0 rows affected (0.13 sec)

mysql> select @city2_crc;
+-----------------------------------------------+
| @city2_crc |
+-----------------------------------------------+
| 4079-407840fbf812b81eee55d3a438cf953f81c63bc0 |
+-----------------------------------------------+
1 row in set (0.01 sec)

When we compare the CRC, we can easily see that the two tables are the same. If all these statements are tedious to write, we can use a shortcut:

mysql> call table_compare(schema(), 'City',schema(), 'City2', @same);
+-------------+-----------------------------------------------+
| table_name | crc |
+-------------+-----------------------------------------------+
| world.City | 4079-407840fbf812b81eee55d3a438cf953f81c63bc0 |
| world.City2 | 4079-407840fbf812b81eee55d3a438cf953f81c63bc0 |
+-------------+-----------------------------------------------+
2 rows in set (0.24 sec)

+---------------+
| same contents |
+---------------+
| 1 |
+---------------+
1 row in set (0.24 sec)
Le'ts make it fail, to see if it is true:

mysql> update City2 set population = population + 1 where id = 1;
Query OK, 1 row affected (0.05 sec)
Rows matched: 1 Changed: 1 Warnings: 0
Now the two tables will have at least one difference:
mysql> call table_compare(schema(), 'City',schema(), 'City2', @same);
+-------------+-----------------------------------------------+
| table_name | crc |
+-------------+-----------------------------------------------+
| world.City | 4079-407840fbf812b81eee55d3a438cf953f81c63bc0 |
| world.City2 | 4079-b3b613b20570024be727ef0454053a96cfc36633 |
+-------------+-----------------------------------------------+
2 rows in set (0.23 sec)

+---------------+
| same contents |
+---------------+
| 0 |
+---------------+
1 row in set (0.23 sec)
And our routine finds it. QED.

Sunday, October 29, 2006

Speaking at the Open Source Database Conference 2006

I will be a speaker at the Open Source Database Conference, which is held in Frankfurt from 6th to 8th November 2006, parallel to the International PHP conference.
I will present two sessions, one on Advanced Replication Techniques in MySQL 5 and the other on The MySQL General Purpose Stored Routines Library.
I submitted both proposals long before I started considering joining MySQL, so I will go there with the blessing of my current employer, but I will speak under my former affiliation, to avoid attributing to my current company what are my personal ideas.

Thursday, October 19, 2006

Contributing to MySQL QA - Ideas wanted

MySQL has recently started a campaign of open contribution, inviting the community to participate to the MySQL project in many ways.

The next target, also considering the higer stakes coming from the MySQL Enterprise challenge, will be Quality Assurance.

Quality Assurance

Now what is Quality Assurance (QA)? If you think that it's just bug hunting, then you have a simplistic view of the software generation lifecycle. QA deals with all the steps in the software lifecycle, and at each steps there are actions that can affect the quality of the final outcome. QA components include (but are not limited to) failure testing, statistical control, process and performance control, best practice adoption.

MySQL AB has its own QA department. Those of you who attended the MySQL Users Conference may have had a chance to attend a presentation by senior QA manager Omer BarNir about Internal QA in Open Source Development, where he explains the challenge of being a QA professional in such a dynamic company (presentation slides ).

Software testing

Since MySQL is a software company, finding bugs is important, of course, and software testing is one of the main branches of QA, but the crucial part of finding bugs is not how many you can find, but when you find them. The earlier you find a bug, the less costly will be to fix it.

Looking for bugs as soon as a feature pops up in the development lifecycle is just part of the task. The way you look for bugs is different from person to person, depending on who you are:
  • a developer with an intimate knowledge of the code may find low level bugs that nobody else can catch, with tools like code peer reviews and walkthroughs;
  • a professional tester, even without development experience, may find functional bugs by testing the application with a methodical approach;
  • the final user, who does not have to know any of the above, may find bugs by simply using the application and comparing the results with earlier expectations.

Of all these three methods of finding bugs, the first one is the most effective. Bugs found at that stage are the least expensive to fix. But of course one can't find all the bugs with code inspections. And then there are the other two levels, which catch bugs that will cost more effort to fix.

Obviously, the bugs that affect the final users are the ones for which you care most, and it would be of mutual benefit to find these bugs as early as possible.

Community involvement

From the above explanation, you can see what the problem is. No matter how skilled are the professionals at this job, finding all the bugs is impossible. You can take any book on this subject, and every one of them will tell you the same postulate: You will not find all bugs. One thought is especially discouraging for us. Although the QA professionals find a lot of bugs, those which you neve hear of, because they are fixed before you hit the download button to get the application, there are still the ones that affect you, the final user, the most important party involved.

Therefore, here is the idea. We want to involve the community of users in our Quality Assurance activities.

How can we do that? To tell you the truth, we have plenty of ideas on what we can do and how to promote it. After all, until one month ago I was still just one of the many community members, and I have some ideas.And there are many brilliant people in house who came up with promising ideas.

But at this stage I don't want to tell you what I think it should be done, but rather listening to what users propose.

The questions to answer are:
  • how can MySQL involve the users in earlier bug finding?
  • how can MySQL involve the users in its testing process?
  • what else can community members do for QA? (hints: bugs verification, performance testing, standard compliance testing)
  • What kind of incentive would make you (more) willing to cooperate? (hints: public recognition, free services, discount on services, contests with prizes)

If you have an idea related to this topic, even if it is not an answer to the above questions, write a comment to this post, or drop me a line (giuseppe {mind the spelling} at mysql dot com).

Thanks in advance!

Also published at O'Reilly Databases

Tuesday, October 03, 2006

Take the MySQL Certification in five steps

I recently took two certification MySQL 5 exams. At that time, I was on vacation, but now that I am back I would like to share with the community some advice on how to pass the exams.
Son't worry, it is not about cheating. But read on, and you'll decide if it was worth listening.

1. read the book

So much for the ones who thought I was teaching some tricks. Nothing like that. Let me tell you why you should read the book.
  • You could read the (free) online manual instead. No questions about that. If you read the whole manual, you will know all is needed to pass the exam. But you will have to read twice as much as the book (about 1300 pages of user manual instead of 672 pages of certification book).

  • The book will tell you what is important for the exam and what isn't. True, even if the certification exam does not mention it, it could be important (and usually is), but you really want to pass, don't you? So, the book is better.

  • The book is organized by exams. There are four of them, and the book covers their subjects nicely in a very organized way. In the manual, you either read it all, or you will never be sure that you covered everything.

Notice that, although I am now a MySQL employee, I don't get any share from the book sales. What I am saying here is what I honestly believe. I bought the book long before an employment with MySQL was even faintly suggested. Actually, I should add that everything in this article is my personal opinion, and it is not the official take of MySQL AB.
Summing up this item: Read the book, because it will save you time.

2. Get some hands-on experience

Reading the book (or the manual, or both) is not enough. Even if you commit the whole beast to memory, it won't be enough to pass the exam. To pass it you need to apply your knowledge to some real world problems. I can't tell you the questions you are going to get in the exam, but I can tell you the gist of it.
During the exam you won't get questions like "what does the book say about this matter?"
Instead, you will get questions like "given this problem with this set of conditions, which of the following actions is most likely to solve it?"
If, in addition to reading the book, you have some practical experience, you will be able to apply what you learned and answer the questions. If you have a prodigious memory and remember every word of the user's manual but have never tried some of that stuff in the wild, chances are that you won't pass the exam.

3. answer the sample questions from the book

After you get some experience, then try to answer the sample questions from the book. Be aware that the book ask questions in a way that is different from what you get in the exam. The book my ask you to "list all the methods to solve a given problem," while at the exam you get questions like "which of the following methods will solve the given problem?" and you get a multilple-choices-list. So the questions from the book are actually more difficult than those in the exam itself. That's fair. If you answer all the questions from the book, the ones in the exam will look a lot easier.

4. Participate to a forum and answer questions

When it comes to practice questions, you can't get enough of them. After you answer all the questions from the book, you still feel that some more exercise could do you good. There's an easy way of practicing. Subscribe to a mailing list, a newsgroup, a forum dedicated to MySQL, and read through the questions that people ask every day. Even better answer some question yourself! Start with the easy questions, and then try tackling the though ones. You may not know all the answers, but you can find out, because you have read the book and you know where to look in the manual. If you don't, it's a good moment for starting. The real trick is this: whenever you answer a question about something that you know only in theory, spend a few minutes to do it in practice. This way, you will be sure that your answer is correct (and you'll avoid some embarrassment) and you will add some more experience to your bag of tricks. This whole process will boost your confidence a lot. After a few weeks of answering at least one question per day, you will be a celebrity in that forum of your choice, you will have made somebody happy, and many people will have thanked you. What better way of studying?

5. Play chess

Now, wait a minute! What has this to do with the exam? Don't worry. I am not out of my mind, and I will explain shortly what I mean.
During the exam, you will have to answer 70 questions in 90 minutes (the upgrade exams has a different timing, but if you go for it you will have already taken an exam, and you know already what I am talking about). This is a great source of stress. Having a clock that clicks your time away can have a negative influence on your answers. Talking to some other candidates who took the exam, the greatest concern was that time restriction. But you know what? It was not a problem at all for me. And the reason is that I am a chess player, and therefore I am used to taking decision with a clock ticking at my side, and telling me that my time is near exhaustion. In competition chess games, you are given a double clock with two buttons. When it's your time to move, your button is up and your time is running. When you have decided your move, you make it, and push the button. Then your clock stops and your opponent's start ticking.
If you are used to this stressful way of taking decisions in rapid (30 minutes for the whole game) or blitz games (five minutes!), a simple clock giving you 90 minutes for 70 "moves" looks like a joke.
So, if you play chess, resume your chess club card, or play some Internet game, and get some practice at time management. If you don't play chess, answer the above mentioned questions with a clock that rings after a given time.

Then, get a good night's sleep and take the exam

As a last piece of advice, remember that a certification exam is a stressful experience, no matter how well you have prepared. So you need all your strenght and energy for it. Go to the exam well rested and fresh. If you have to take more than one exams, don't do them in a row. Put at least a few hours before the next one, and in between take a walk, read a book, or do anything to recharge your spirits.

Good luck!

Monday, October 02, 2006

Are logins before download any good?

If you are used to open source products, chances are you have gone through this routine more than once. Search for what you need, find a suitable product, go to its web site, download it, test it. Then, if you like it, you start using it right away, otherwise you dump it without a second thought.
The whole process takes less than one minute for small packages. But anyway, even for larger packages, the total time that this whole business requires your attention is very low. Even if it requires a huge download, it can be left unattended and you can resume the testing task when you feel like it. The bottom line is that we got used to a quick try-and-use process of open source products.

Sometimes, though, while performing the above routine, there is a unexpected obstacle. The product maker requires a free login. You don't have to pay anything, but you have to go through the motions of filling a form that asks you everything about your precious self, your company, education, employment history, financial health, and so forth.
Filling these forms is really annoying for several reasons:

  • You got used to the quick download-and-try business, and this sudden stop is not welcome
  • You can't see any added value in this form filling. Actually, you are sure that your level of spam (both by email and by regular mail) will increase;
  • You think at the waste of time this form is, especially considering that you may be throwing the whole product away after ten minutes.
  • This is contrary to the whole open source spirit, where you achieve success by providing a good product. The register-bedore-downloading strategy, instead, tries to cheat into a let's-grab-a-potential-customer-as-soon-as-he-shows-up utterly losing attitude.
After this problem has bitten you once or twice, you start developing a strong defense strategy. The next time some site asks you for a registration before downloading, you start filling the form with fake information, using a temporary but legitimate email address, claiming to run a multi-million dollar business, and presto! you get away with the download, never to be seen to that site again, unless that product is really a earth-shakening tool (which seldom is, in those cases).

And so here are two reasons not to impose a registration before downloading an open source product:
  • It's useless. If you want to cheat the unfair system, they can't do nothing to prevent it.
  • It's damaging. If they want to propose an open source product, imposing a registration is like screaming: "Hey! We want to play the open source game, but we are totally and hopelessly unaware of how to play the game. Cheat us!"
Which I usually do.

Also published at ITToolbox

Friday, September 29, 2006

Log Buffer #12: a Carnival of the Vanities for DBAs

I fell for it. I commented on the Log Buffer and shortly after that I was offered to host an issue. Here we go, then. Welcome to Log Buffer #12!

Confessions of an IT Hitman by Dratz features a strong message: Don't build a data warehouse, arguing that most of the times a DW is built, it's just because a clueless customers was either listening to buzzwords from a salesman or following the latest trend. The key message, for the ones who missed the build-up of the data warehousing movement during the past decade, is DW is a business solution, not a technology solution. OTOH, there are projects that would really need a data warehouse, and don't get one. But this is a different story.

Mats Kindahl in his MySQL Musings talks about Replication and the disappearing statements, i.e. the risks and gotchas of replicating data in MySQL while limiting the databases involved in the replication process. It is something that the manual states quite clearly, but it bites back quite often nonetheless. Mats explains some unusual points in this old issue. Querying a table with a fully qualified name may result in more than overkill. You may be the next victim of the infamous disappearing statement.

Brian Aker is always a volcano of ideas. His Brian 'Krow' Aker's Idle Thoughts don't show much idleness. Rather, he's often producing some hack at great speed, or he's pondering on a new hack. This one, Firefox, yum, sounds interesting, because the suggestion came from MySQL CEO, who is also a man of vision. It looks like something will come out of this clash between dreamers.

Vadim Tkachenko from MySQL Performance Blog, which he runs together with Peter Zaitsev goes int a wrestiling with the latest beta of SolidDB for MySQL.

Test Drive of Solid is a comparision between the behavior of the SOlid engine compared to InnoDB, to check if a migration from InnoDB to Solid could be done easily. Vadim finds several differences and a few bugs. SolidDB doesn't seem to be solid enough in this beta, but it's getting closer.

A charming set of slides from Lars Thalmann in his Lars Thalmann's Blog talking about the joy and the shock of Being a MySQL Developes. If you thought that a developer for an open source company had an easier job, think again. Go through Lars's slides and discover a new dimension of cooperative work.

Good news from Markus Popp (db4free.net blog). For almost one year I have waited for MySQL status and variables to be available as INFORMATION_SCHEMA tables. Now it seems that MySQL 5.1.12 offers New information_schema views that do exactly that. I built MySQL from source a few days ago, and the new features were not there. After seeing Markus's post, I rebuilt it, and spent some time toying with the fresh additions. For example, calculating the key cache efficiency is one task that previously you had to perform with the help of a programming language. Now you can do it in SQL.

A glimpse into the future from Mikael Ronstrom, senior software architect at MySQL AB. The State of MySQL partitioning seems to be close to bug-free, and he's already fiddling with new enhancements in the next version of MySQL. It's almost a pity that I had to submit a new bug for partitioning just hours after his post!

Matt Asay from Open Sources examines The spirit of winning, taking into account the elements tha make a winning team in athletics, and finds that the same elements apply to open source competitiveness. Food for thought.

Bob's World by Bob Field is usually full of interesting concepts. This one, Advanced Features as Crutches is no exception. Advanced features, Bob warns against using an advanced feature just because it's there. Everything has its place, but it is no mandatory to use all the frills a DBMS engine offers. If you do so, security and simplicity may suffer. I fully agree.

In The Oracle Base Blog, Timothy S. Hall turns a joke into a lesson. So many people ask Where's the 'Go faster' switch, and they won't understand the hard work necessary to actually improve the performance. So he makes a surprising proposal. Read on.

Craig Mullings's dbazine.com features two entries that appeal to common sense. Intelligent Database Management lists the tasks of a DBA, stressing the effort that has to be put into the job. Only through intelligent automation of the tasks it is possible to keep up with the chore of managing database systems at a high professional level. Choosing the best DBMS is an old question that does not get a straight answer. Craig goes explains what you need to take into account to reach a sensible decision.

If you liked the previous entries, be aware that Craig writes in different places. DB2PORTAL Blog is one, and there he wrote Sequence Objects and Identity Columns. It is not another chapter of the natural vs surrogate keys saga, but a lucid explanation of what are the differences between identity columns and sequence objects, when each of them is needed and how to handle them. Even if DB2 is not your database of choice, his explanation can help you choose the right solution.

This entry gives a deja vu feeling after you read the one by Craig about Intelligent Database Management. One day in the life of a DBA, as seen by Jeff Hunter is what you get from reading What do you do all day? (a piece of So What Co-Operative). Not a common DBA, mind you, but a very organized one. As seen previously, the good DBA job is a balanced mixture of experience, organization, and an educated choice of technology. That way, even long lasting tasks can be approached with a quiet frame of mind. Jeff's tasks include such things as monitoring the databases, insuring that backups are done, checking for critical conditions. He sounds less worried than the average DBA, because he merges experience and knowledge with the right tools for the job.

Raven Zachary is a contributor to The 451 Group. In Open source database poll highlights barriers to adoption he comments on a survey launched by SolidDB about the acceptance of open source database. It seems that most of the IT professionals attending a recent Linux Expo were timid about adopting open source database.

A different angle is offered by another research, as reported by Zack Urlocker in his The Open Force blog. Linux Database Attach Rate says that 85% of RedHat customers show a strong interest in open source databases. I guess that this research and the survey in the previous item come from different sources. Zack also reports on the Zmanda Recovery Manager, an open source (dual licensed) tool dedicated to backup and recovery of MySQL databases.

Paddy Sreenivasan covers the same topic in Selective recovery of MySQL databases at the O'Reilly database weblogs. Paddy highlights the common needs in a backup solution and explains how Zmanda covers them.

If you jump to Sheeri Kritzer weblog, its title "My-ess-queue-ell vs. My-see-quel" will give away her main area of expertise. One intriguing entry in her blog, Choosing datatypes for fields is a clever hack to improve the quality of data uploads into MySQL tables. So simple, and yet so good. To choose the best datatype, first use a bad one, and then let the system tell you which ones you can have. Well done! If you know Sheeri (I had the pleasure of seeing her at work during the MySQL Users Conference, but reading her blog is enough to get the idea) you will see that she doesn't accept anything blindly, but she needs to explore and dig deeply every concept. In Isolation & Concurrency she takes Jim Starkey's assertion that MVCC could not be serialized as a starting point to an enthusiastic explanation of why you can't do it.

DBMS2 features a piece by Curt Monash, delving into the intricacies of data warehousing terms.
Data warehouse and mart uses - a tentative taxonomy is a quick introduction to the world of business intelligence by comparing the features that each different technology supports or requires. It won't replace a good book, but it sets the record straight in less than a page. Really commendable.

The scintillating Roland Bouman has done that again. He saw an intriguing problem with some clever solutions, and found a way of doing the same job in pure SQL. The matter at hand was Finding Redundant Indexes using the MySQL Information Schema. Others have done this before, using different methodologies. Roland points to the language-independent solution, and he delivers, as he usually does. Browsing Roland Bouman's blog you'll find several witty examples of his analytic approach.

A storm of advice for Oracle developers from ... a DBA. Andy Campbell in his Oracle Stuff I Should Have Known ! gives sensible advice to developers. If Only ... more developers used some nifty features that andy explains in full, their life (and the DBA's as well) would be much easier. This kind of advice (use application metadata to say which parts are being executed) would be good for any DBMS. If only ...

Thomas Kyte grabs the suggestion in The Tom Kyte's Blog and elaborates on his own about Action, Module, Program ID and V$SQL.... This entry, like the previous one, generated quite a stir, with good and bad vibrations on both sides of the Developers-DBA divide.

And finally, a personal closing note. I mentioned in one of my previous posts that I joined MySQL AB as QA developer. A few days in the job, and now I am experiencing the thrill of working in a virtual company