Archive

Archive for the ‘MySQL 5.1’ Category

innodb_stats_on_metadata / innodb_adaptive_hash_index

May 13th, 2009

It occurred to me at the MySQL UC that I hadn’t blogged about either innodb_stats_on_metadata or innodb_adaptive_hash_index - Morgan Tocker convinced me to do a lightning talk at the end of the Percona conference about them - so I thought I’d do a quick write up on them here as well.
 
First - what’s the point in them? Being the guy that wrote the originating patches for both of them, I have a little background:

innodb_stats_on_metadata

InnoDB is fairly light weight when it gathers table and index statistics. In the default compiled in InnoDB for most users it will do 8 random(ish) dives in to the index and samples the page to try to ascertain the cardinality (selectivity) of the index for use by the optimizer. This being the case, not only is this done when you run ANALYZE TABLE on a table, it is also done during various other points:

The first is easy to digest.

The second and third both occur for each row change within InnoDB. row_update_statistics_if_needed() is called from row_insert_for_mysql()row_update_cascade_for_mysql(), and row_update_for_mysql().

The last in the list is interesting. In order to get an accurate(ish) count of the rows in the table, or the cardinality results etc. for the metadata commands, InnoDB runs the same procedure as an ANALYZE TABLE for all of the tables, because it doesn’t maintain accurate statistics in a table or index header somewhere like MyISAM (being MVCC getting an accurate row count is far more difficult for example).

This is great, if you run these commands periodically to check on their output yourself. Once you start trying to read these tables very often in a monitoring solution of any kind though, you will likely hit two distinct problems:

  1. God Damn that INFORMATION_SCHEMA access is SLLLOOOWWWWW
  2. If you are monitoring your queries, and their EXPLAIN plans, you may start to see “plan instability” - the optimizer randomly choosing different access paths for the queries

Behind door #1 there’s a couple of issues - we traverse the directories and table files in the  data directory for a lot of the information used in the output, as well as the ANALYZE that is done for InnoDB. We’ve done work in Worklog #3732 to optimize certain accesses to these INFORMATION_SCHEMA tables, but this didn’t solve the statistics issue.

The second issue is a little more subtle. You may have benchmarked your application statements in Dev and QA environments - but data sets change when they are in production. If table and index statistics are constantly being recalculated, there is a high probability that the optimizer will in fact choose different access paths for certain queries and tables where the rows that need to be scanned could be satisfied by more than one index.

Unless you are closely monitoring individual statements for their run time, access paths (EXPLAIN) etc. this is far more difficult to catch (often you will be seeing the “good” EXPLAIN plan a lot, and the “bad” one only surfaces every now and then too - so interactive debugging is made harder as well).

So, at the request of a pretty large SaaS customer, who was having both problems, innodb_stats_on_metadata was born - originally in Bug #26598. If you see any of the situations above, give it a try, and see how it works for you, as well.

Originally it was created as a non-dynamic variable, you could only set it on the command line or in an option file. It was altered by the InnoDB guys to be dynamic, and settable by SUPER privileged users. It’s documented in the InnoDB plugin documentation under the “Changes for Flexibility, Ease of Use and Reliability” chapter.

I’m on the fence about allowing this dynamically, as it still allows tools to change the setting as well (which often have the SUPER privilege to get at things like SHOW INNODB STATUS), when the DBA may have wanted this explicitly not allowed for all users.

Be good citizens tool vendors, and respect this setting please.

innodb_adaptive_hash_index

InnoDB has a really unique feature in the database world called “Adaptive Hash Indexes“. It monitors which rows are being accessed via index searches, and dynamically decides to cache those that are used the most often, by taking the BTREE index for the table the rows are from and converting it to a HASH index in memory, for quick row lookups.  Historically, this has performed really well (and still does today in most workloads).

Prior to the fix for the infamous Bug #15815 (the true start of our multi-core scalability jaunt, imho), it just hummed along with most people not noticing it around. Once we had the fix though, we started to see mutex contention in other places, in certain cases leading to InnoDB getting locked up, and stopping the server due to long semaphore waits - this happened within the code for adaptive hash indexes as well, and was reported as Bug #20358.

This has hit a pretty large number of users - many Support customers as well, and has had many engineer hours in trying to reproduce it reliably (without result, the bug is still “Open”, not “Verified”). As this started to hit a number of customers, I wrote a pretty simple patch for innodb_adaptive_hash_index to allow you to switch off the use of adaptive hash indexes altogether within InnoDB.

Now, for some this comes as a performance trade off - if you hit the above bug, it does allow you to work around it, but your application may benefit from Adaptive Hash Indexes too. If you favor reliability over performance, then use it - it fixes the problem (in a somewhat hackish way).

Yasufumi Kinoshita did a lot of great work, in Bug #29560 and Bug #26442, and there have been other issues found and fixed by the InnoDB team - Bug #39483 (closed at the time of writing) - and Mark over at Google - Bug #32149 (still verified at the time of writing). We hardly ever see Bug#20358 any more though, unless it’s on older versions.

This variable also gets a good mention in the InnoDB plugin documentation, under the ”Changes for Flexibility, Ease of Use and Reliability” again, and in it’s own section “Controlling Adaptive Hash Indexing“, because the variable has a nice side effect as well - it can also help to scale, specifically because of the kind of mutex contention that also lead to Bug #20358. 

The InnoDB manual mentions that you can monitor the contention via the SEMAPHORES section of SHOW INNODB STATUS by looking for “many threads waiting on an RW-latch created in btr0sea.c” - quite correctly.

You can also check out how much memory the hash index is using, and how well it’s being utilized, in the INSERT BUFFER AND ADAPTIVE HASH INDEX section:

-------------------------------------
INSERT BUFFER AND ADAPTIVE HASH INDEX
-------------------------------------
Ibuf: size 1, free list len 5, seg size 7,
16104 inserts, 16104 merged recs, 15379 merges
Hash table size 2212699, used cells 846306, node heap has 1574 buffer(s)
0.03 hash searches/s, 0.63 non-hash searches

The last two rows apply to the Adaptive Hash index. “Hash Table size” and “used cells” are the number of cells (array slots) assigned to the hash table (always a prime number), and the number of those which are used, respectively. “used cells” was removed as part of Bug #36941 because of it having perform issues by scanning the hash table to find the used cells - in 5.1.28 - so you may not see this on your server. 

“node heap has 1574 buffer(s)” is where you can find out how much memory the hash index is using - the number here is the number of pages that are allocated to the hash index (and a page is 16K in InnoDB by default). 

The last row gives the number of searches both in the adaptive hash index, as well as those not satisfied by it - which can give you a hit ratio for hash read efficiency. 

I’ve made a few graphs for MEM to track these, so they can be monitored over time to get a better picture of adaptive hash index usage, to give you a better tool to judge for yourself what the impact may be for disabling it (see below). I’ll push these up today, and after some testing on a number of other servers, you should hopefully see them in a new advisor update soon.

InnoDB Adaptive Hash Index graphs for MEM

InnoDB Adaptive Hash Index graphs for MEM

MySQL 5.1 ,

Monitoring OS statistics with INFORMATION_SCHEMA plugins

April 29th, 2007

With all the talk lately of the new INFORMATION_SCHEMA plugin API, I thought I’d have a go at making a couple. I’ve now made three different pluggable INFORMATION_SCHEMA tables - one that works from df -k - which will only work on UNIX like systems (other than AIX or HPUX) - and two which integrate the SIGAR library available from Hyperic.

I’ll post the df -k plugin in a couple of days, however I wanted to get the two I have created based on the SIGAR library ‘out to the world’ for some feedback on them! They are currently a very rough prototype (they need a little more work on return checking etc.!) - however they are currently functional.

There are two INFORMATION_SCHEMA tables within the plugin library - INFORMATION_SCHEMA.OS_STATUS and INFORMATION_SCHEMA.OS_VARIABLES - much like MySQL’s current (well, within 5.1) INFORMATION_SCHEMA.GLOBAL|SESSION_STATUS and INFORMATION_SCHEMA.GLOBAL|SESSION_VARIABLES tables.

Here’s some sample output:


mysql> install plugin os_status soname 'libos_stats_info_schema.so';
Query OK, 0 rows affected (0.00 sec)

mysql> install plugin os_variables soname 'libos_stats_info_schema.so';
Query OK, 0 rows affected (0.00 sec)

mysql> use information_schema;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> select * from os_variables;
+---------------------------+-------------------+----------------------------+---------------+
| VARIABLE_NAME | VARIABLE_INSTANCE | VARIABLE_VALUE | VARIABLE_TYPE |
+---------------------------+-------------------+----------------------------+---------------+
| Cpu_vendor | cpu0 | Intel | VARCHAR |
| Cpu_model | cpu0 | Xeon | VARCHAR |
| Cpu_mhz | cpu0 | 3401 | INTEGER |
| Cpu_cache_size | cpu0 | 1024 | INTEGER |
| Fs_name | / | /dev/md0 | VARCHAR |
| Fs_location | / | local | VARCHAR |
| Fs_type | / | ext3 | VARCHAR |
| Fs_name | /boot | /dev/cciss/c0d0p1 | VARCHAR |
| Fs_location | /boot | local | VARCHAR |
| Fs_type | /boot | ext3 | VARCHAR |
| Fs_name | /data0 | /dev/md10 | VARCHAR |
| Fs_location | /data0 | local | VARCHAR |
| Fs_type | /data0 | ext3 | VARCHAR |
| Fs_name | /users | production:/usersnfs | VARCHAR |
| Fs_location | /users | remote | VARCHAR |
| Fs_type | /users | nfs | VARCHAR |
| Fs_name | /nfstmp1 | nfssrva:/nfstmp1 | VARCHAR |
| Fs_location | /nfstmp1 | remote | VARCHAR |
| Fs_type | /nfstmp1 | nfs | VARCHAR |
| Rlimit_cpu_cur | | 4294967295 | INTEGER |
| Rlimit_cpu_max | | 4294967295 | INTEGER |
| Rlimit_file_size_cur | | 4294967295 | INTEGER |
| Rlimit_file_size_max | | 4294967295 | INTEGER |
| Rlimit_pipe_size_cur | | 8 | INTEGER |
| Rlimit_pipe_size_max | | 8 | INTEGER |
| Rlimit_data_cur | | 4294967295 | INTEGER |
| Rlimit_data_max | | 4294967295 | INTEGER |
| Rlimit_stack_cur | | 10240 | INTEGER |
| Rlimit_stack_max | | 4294967295 | INTEGER |
| Rlimit_core_cur | | 0 | INTEGER |
| Rlimit_core_max | | 4294967295 | INTEGER |
| Rlimit_memory_cur | | 4294967295 | INTEGER |
| Rlimit_memory_max | | 4294967295 | INTEGER |
| Rlimit_processes_cur | | 16383 | INTEGER |
| Rlimit_processes_max | | 16383 | INTEGER |
| Rlimit_open_files_cur | | 1024 | INTEGER |
| Rlimit_open_files_max | | 1024 | INTEGER |
| Rlimit_virtual_memory_cur | | 4294967295 | INTEGER |
| Rlimit_virtual_memory_max | | 4294967295 | INTEGER |
| Os_name | | Linux | VARCHAR |
| Os_version | | 2.6.9-22.ELsmp | VARCHAR |
| Os_architecture | | i686 | VARCHAR |
| Os_machine | | i686 | VARCHAR |
| Os_description | | Red Hat Enterprise Linux 4 | VARCHAR |
| Os_patch_level | | unknown | VARCHAR |
| Os_vendor | | Red Hat | VARCHAR |
| Os_vendor_version | | Enterprise Linux 4 | VARCHAR |
| Os_vendor_name | | Linux | VARCHAR |
| Os_vendor_code_name | | Nahant Update 2 | VARCHAR |
+---------------------------+-------------------+----------------------------+---------------+
49 rows in set (0.01 sec)

mysql> select * from os_status;
+-------------------------+-------------------+----------------------+---------------+
| VARIABLE_NAME | VARIABLE_INSTANCE | VARIABLE_VALUE | VARIABLE_TYPE |
+-------------------------+-------------------+----------------------+---------------+
| Memory_total | | 1059471360 | INTEGER |
| Memory_used | | 682172416 | INTEGER |
| Memory_free | | 377298944 | INTEGER |
| Swap_total | | 1002676224 | INTEGER |
| Swap_used | | 121491456 | INTEGER |
| Swap_free | | 881184768 | INTEGER |
| Cpu_user_total | | 695644430 | INTEGER |
| Cpu_sys_total | | 273173250 | INTEGER |
| Cpu_nice_total | | 14360 | INTEGER |
| Cpu_idle_total | | 46569287330 | INTEGER |
| Cpu_wait_total | | 809380330 | INTEGER |
| Cpu_total_total | | 48347499700 | INTEGER |
| Cpu_user | cpu0 | 695644430 | INTEGER |
| Cpu_sys | cpu0 | 273173250 | INTEGER |
| Cpu_nice | cpu0 | 14350 | INTEGER |
| Cpu_idle | cpu0 | 46569287330 | INTEGER |
| Cpu_wait | cpu0 | 809380330 | INTEGER |
| Cpu_total | cpu0 | 48347499690 | INTEGER |
| Load_average | 0 | 0.05 | DECIMAL |
| Load_average | 1 | 0.04 | DECIMAL |
| Load_average | 2 | 0.01 | DECIMAL |
| Fs_size | / | 9618148 | INTEGER |
| Fs_free | / | 5002764 | INTEGER |
| Fs_used | / | 4615384 | INTEGER |
| Fs_avail | / | 4514188 | INTEGER |
| Fs_files | / | 1221600 | INTEGER |
| Fs_disk_reads | / | 20785383 | INTEGER |
| Fs_disk_read_bytes | / | 176143946752 | INTEGER |
| Fs_disk_writes | / | 625659222 | INTEGER |
| Fs_disk_write_bytes | / | 363676917760 | INTEGER |
| Fs_disk_queue | / | 0 | INTEGER |
| Fs_size | /boot | 98747 | INTEGER |
| Fs_free | /boot | 87464 | INTEGER |
| Fs_used | /boot | 11283 | INTEGER |
| Fs_avail | /boot | 82365 | INTEGER |
| Fs_files | /boot | 25584 | INTEGER |
| Fs_disk_reads | /boot | 2053 | INTEGER |
| Fs_disk_read_bytes | /boot | 6922240 | INTEGER |
| Fs_disk_writes | /boot | 350 | INTEGER |
| Fs_disk_write_bytes | /boot | 361472 | INTEGER |
| Fs_disk_queue | /boot | 18446744073709551615 | INTEGER |
| Fs_size | /data0 | 19228180 | INTEGER |
| Fs_free | /data0 | 18804340 | INTEGER |
| Fs_used | /data0 | 423840 | INTEGER |
| Fs_avail | /data0 | 17827596 | INTEGER |
| Fs_files | /data0 | 2443200 | INTEGER |
| Fs_disk_reads | /data0 | 13058321 | INTEGER |
| Fs_disk_read_bytes | /data0 | 485011117056 | INTEGER |
| Fs_disk_writes | /data0 | 68639632 | INTEGER |
| Fs_disk_write_bytes | /data0 | 281147932672 | INTEGER |
| Fs_disk_queue | /data0 | 0 | INTEGER |
| Fs_size | /users | 373057344 | INTEGER |
| Fs_free | /users | 43121216 | INTEGER |
| Fs_used | /users | 329936128 | INTEGER |
| Fs_avail | /users | 24170944 | INTEGER |
| Fs_files | /users | 47382528 | INTEGER |
| Fs_size | /nfstmp1 | 307583488 | INTEGER |
| Fs_free | /nfstmp1 | 179642368 | INTEGER |
| Fs_used | /nfstmp1 | 127941120 | INTEGER |
| Fs_avail | /nfstmp1 | 164017952 | INTEGER |
| Fs_files | /nfstmp1 | 39075840 | INTEGER |
| Mysqld_threads | mysqld | 10 | INTEGER |
| Mysqld_mem_size | mysqld | 128045056 | INTEGER |
| Mysqld_mem_resident | mysqld | 26636288 | INTEGER |
| Mysqld_mem_share | mysqld | 5382144 | INTEGER |
| Mysqld_mem_minor_faults | mysqld | 8926 | INTEGER |
| Mysqld_mem_major_faults | mysqld | 7 | INTEGER |
| Mysqld_mem_page_faults | mysqld | 8933 | INTEGER |
| Mysqld_cpu_user | mysqld | 440 | INTEGER |
| Mysqld_cpu_sys | mysqld | 180 | INTEGER |
| Mysqld_cpu_total | mysqld | 620 | INTEGER |
| Uptime | | 24173401.250000 | DECIMAL |
+-------------------------+-------------------+----------------------+---------------+
72 rows in set (0.01 sec)

If you have multiple CPUs then you will have multiple returns for variables such as ‘Cpu_user’, ‘Cpu_nice’ - labelled cpu0, cpu1 etc. - just like there is with the ‘Fs*’ variables - specific to each CPU.

You can download a copy of the plugin code from here. Build and installation instructions are within the INSTALL file.

All feedback welcome!

MySQL, MySQL 5.1

Dynamic Logging Variables

May 12th, 2006

Finally!

ChangeSet
1.2396 06/05/12 12:50:50 gluh@eagle.intranet.mysql.r18.ru +12 -0
WL#3015: Logging Improvements - No Restarts(ver N3)
Added slow_query_log & general_log global upadatable variables.
Added slow-query-log & general-log startup options.
Added log_output, log_path, log_slow_queries_path global updatable variables.

http://lists.mysql.com/commits/6279

Now we can do things like “SET GLOBAL general_log = ON;” and “SET GLOBAL log_path=’/log/tmp.log’;”.

This has been something long requested so it’s great to see this slip in to the tree in time for 5.1!

General, MySQL, MySQL 5.1

MySQL User Conference

April 19th, 2006

Long time no speak! Hey non-avid readers!

I’ve been keeping my head down lately working away in our support group, and haven’t had much time to get any tips down on my blog or even any thoughts in general.

Over the past few weeks I’ve been polishing up my presentation - MySQL for Oracle DBAs, which is on the last day - next Thursday, April 27th.

http://www.mysqluc.com/cs/mysqluc2006/view/e_sess/8465

If you’re reading this - and around at our user conference next week - seek me out and say “Hi!”.

I’ll work on a number of blog posts after the conference, which will give some of the information available in my presentation as well.

Look forward to saying “Hi!” to as many of you as possible!

General, MySQL, MySQL 4.1, MySQL 5.0, MySQL 5.1, Oracle

Stripping Digits - The benefits of Benchmarking and Profiling

August 18th, 2005

Carsten Pedersen (Certification Manager of at MySQL AB) read my entry about stripping digits, and has done a good write up here on how this function could be improved, a great deal.

Have a read of Carstens link, and my previous entry if you didn’t catch it, before we progress.

As you see, I mentioned that “it performed fairly well”, but I admit given the nature of the request (a quick IRC question) I didn’t run a benchmark on it compared to something like Carsten’s REPLACE solution. I actually went with the REGEXP as the original question was actually something along the lines of “If I have a string such as “joe123″ how I can I strip the digits from the end to return just “joe”. I wrote the quick function as an example of what you can do in 5.0, as the user was still 4.x anyway, really the answer would have been a little more along the lines of using REVERSE on the string and walking along the digits until you hit the first alpha (or [^0-9]), and then doing a substring up until that location.

However, Carsten was spot on when he picked up on this! This only goes to reinforce the fact that things should not be assumed on first glance, and should be benchmarked and tested rigorously. Carsten provides a great example of how to run a quick benchmark of function calls with the BENCHMARK function, which is the first logical step when testing any new stored functions that you may create. Thanks Carsten for the write up!

To take this one step further - I also read a great write up on “Benchmarking and Profiling” from “Pro MySQL” written by Mike Kruckenberg and Jay Pipes, which has generously been made available on the MySQL AB website here.

This gives some great information on using readily available open source benchmarking tools that allow would allow you to benchmark new functions within a number of different situations, with different data sets etc. It also gives some good information on profiling and diagnostics techniques, all of which is fantastic information.

This seems as good a time as any to introduce what I do for a living. I work for a company that sells and supports database management tools, primarily in the Oracle and SQLServer market place. If you read the section on profiling in the chapter above, a lot of it is exactly the kind of thing that we are putting together at the moment at Cool-Tools for the monitoring tool that we currently distribute - NORAD Surveillance. We’ve seen a huge upsurge of MySQL being picked up in the enterprise space, and we would quite frankly be foolish to sit back and ignore this. So we’re some way down the line of an early beta version of a plugin that Cool-Tools will provide that will monitor MySQL, as well as the underlying OS.

Here’s a quick sneak pic of the “Global Health Window“.. Although it’s a little boring on my little work desktop machine with no activity (that could really do with upgrading!), and still needs a few more variables to fill it out - it’s good enough to give an idea of what we are trying to do.. I ran a couple of the sys-bench scripts (test-insert and test-select) from a remote machine (the 192.168.3.50 host) to give it at least a little bit to look at..

I’ll post a full overview as and when - if there’s anybody out there that would like to try a beta copy of it as well, when it’s ready, feel free to fire me an email - mark at cool hyphen tools dot co dot uk - and I’ll see about getting it out to a few of you. We won’t consider people sending from email addresses such as hotmail ;)

MySQL, MySQL 4.1, MySQL 5.0, MySQL 5.1

Stripping digits

August 17th, 2005

We had a question in #mysql on freenode yesterday, asking if there was a function to strip digits from a string. The answer is of course - not natively.

I’ve been playing around with Functions and Stored Procedures a bit lately though, trying to familiarise myself with the MySQL implementation fully, and wrote this quick function which does the job, although only in 5.0:

DELIMITER //

DROP FUNCTION strip_digits //

CREATE FUNCTION strip_digits (str VARCHAR(50))
RETURNS VARCHAR(50)
BEGIN
DECLARE sub_start INT DEFAULT 0;
DECLARE res VARCHAR(50) DEFAULT '';

WHILE sub_start < LENGTH(str)+1 DO

IF SUBSTRING(str,sub_start,1) REGEXP '[[:alpha:]]' THEN
SET res = CONCAT(res,SUBSTRING(str,sub_start,1));
END IF;

SET sub_start = sub_start + 1;

END WHILE;
RETURN res;
END;
//

Here’s a couple of examples of it in action:

mysql> select strip_digits('foop123')//
+-------------------------+
| strip_digits('foop123') |
+-------------------------+
| foop |
+-------------------------+
1 row in set (0.00 sec)

mysql> select strip_digits('foop123a')//
+--------------------------+
| strip_digits('foop123a') |
+--------------------------+
| foopa |
+--------------------------+
1 row in set (0.00 sec)

mysql> select strip_digits('foop123ahgh63263jsdj')//
+--------------------------------------+
| strip_digits('foop123ahgh63263jsdj') |
+--------------------------------------+
| foopahghjsdj |
+--------------------------------------+
1 row in set (0.00 sec)

It even performs fairly well!

More to come in the “MySQL 5.0″ collection of blogs on Functions and Stored Procedures when I get time to sit down and do it justice! Even though I’ll never win a mug any more.. Hohum..

MySQL, MySQL 5.0, MySQL 5.1

MySQL 5.0 - INFORMATION_SCHEMA (cont’d)

June 18th, 2005
Comments Off

I mentioned “fuzzy foreign key” matches in my post the other day, and thought that I’d give it a crack tonight. The aim of the fuzzy match is to try and find relationships when a) They are InnoDB and haven’t been created or b) Foreign keys are not avilable to the storage engine that is being used.

Most DBA’s will know their schemas pretty well, this however should be pretty handy for consultants or anyone starting fresh at a new job / on a new database etc.

It should be noted that this will only “fuzzy” match against a schema that follows strict naming conventions - Where the tablename and “id” is used for the primary key column, and the same name is used for the foreign key in the child table. For instance, given the following example tables:


mysql> desc company; +--------------+--------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +--------------+--------------+------+-----+---------+----------------+ | company_id | int(11) | NO | PRI | NULL | auto_increment | | company_name | varchar(100) | YES | MUL | NULL | | +--------------+--------------+------+-----+---------+----------------+ mysql> desc contacts; +-------------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------------+-------------+------+-----+---------+----------------+ | contact_id | int(11) | NO | PRI | NULL | auto_increment | | name | varchar(50) | YES | | NULL | | | company_id | int(11) | YES | MUL | NULL | | | direct_line | varchar(20) | YES | | NULL | | +-------------+-------------+------+-----+---------+----------------+

Here’s what I came up with:


SELECT t1.table_name as parent_table,
t1.column_name as primary_key,
t2.table_name as child_table,
t2.column_name as foreign_key
FROM information_schema.columns t1
JOIN information_schema.columns t2 USING (column_name,data_type,table_schema)
WHERE t1.table_name != t2.table_name
AND t1.column_key = ‘PRI’
AND t1.table_schema NOT IN (’information_schema’,'mysql’)
ORDER BY t1.table_name

+—————–+————-+————-+————-+
| parent_table | primary_key | child_table | foreign_key |
+—————–+————-+————-+————-+
| company | company_id | contacts | company_id |

…..

You can change the table_schema WHERE clause as required, or swap it out for “AND t1.table_name LIKE ‘wp%’” fpr example..
This makes use of the INFORMATION_SCHEMA.COLUMNS view.

Of course, there’s no guarentee this will work in all cases. People seem to have an in-built urge to use some of the most outrageously stupid names for their objects in so many cases…

Edit 21-06-05 Removed extra comma in second query (left from my own testing where I excluded extra db names) - Thanks inviso

MySQL, MySQL 5.0, MySQL 5.1

Oracle Buys Times Ten

June 13th, 2005

http://www.oracle.com/timesten/index.html

This seems like an interesting move from Oracle. Times Ten’s Cache is an in-memory cache type database that works very much like MySQL Cluster - with read/write functionality - along with the ability to “divert” to a backend (Oracle) database when the data needed is not memory resident.

It will be interesting over the next few months to see what Oracle does with this. On the surface it’s quite obvious that it’s an attempt to get their foot in the door with the likes of telcos and financial services companies, for their “frontline” systems, where they are rather lacking due to the cumbersome nature of the beast for “real time” data.

So you may have read this through the MySQL category on my blog, and wondered “How the hell does this relate to MySQL”? As any seasoned MySQL user would know, MySQL Cluster has been around for a while now, and whilst it’s great for “smaller” apps - that require very fast “real time” access, but don’t necessarily store huge amounts of data - many have noted the serious lack of any disk based “second stage” storage for older data that may not be used very often. MySQL Cluster is already geared towards the areas where Oracle is trying to get to right now through acquisitions. MySQL Cluster however has fallen down on the VLDB back end requirements of some of these systems.

Until 5.1 it seems. Today I saw a commit to the internals email list from Pekka Nousiainen that refers to “Pgman” - which seems to handle buffer pool caching and flushing from/to disk based storage - for the NDB storage engine. I’ll save you all the code, but this comment seems to sum it up fairly well for now:

+ * PGMAN
+ *
+ * PAGE ENTRIES AND REQUESTS
+ *
+ * Central structure is “page entry”. It corresponds to a disk page
+ * identified by file and page number (file_no, page_no).
+ *
+ * A page entry is created by first request for the disk page.
+ * Subsequent requests are queued under the same page entry.
+ *
+ * A new entry must wait to be “bound” to an available buffer page
+ * (called “real page” here). If the disk page is not “new” (empty),
+ * the entry must also wait to be “mapped” via “pagein” from disk.
+ *
+ * Entries are released on demand when page requests arrive for unknown
+ * pages. Release candidates are entries which point to a disk page
+ * which is “clean and not used”. They are ordered by some variant of
+ * least recently used (LRU).
+ *
+ * A background clean-up process makes “dirty” pages clean via “pageout”
+ * to disk. UNDO log entries are first flushed up to the maximum log
+ * sequence number (LSN) of the page. This is called write ahead
+ * logging (WAL). The clean-up process prefers “dirty and not used”
+ * pages and lower LSN values.
+ *
+ * A local check point (LCP) performs complete pageout of dirty pages
+ * since given LSN. It needs a stable entry list to iterate over.
+ *
+ * Page entries are put on ordered lists accordingly:
+ *
+ * The “main” list contains all entries. It is ordered by its unique
+ * key (file_no, page_no). It is used to look up page entries. LCP
+ * iterates over it to cover all candidate pages.
+ *
+ * Each entry also belongs to exactly one “sublist”. These drive page
+ * processing. The sublist is determined by page “state”. The state
+ * contains page properties such as discussed above.

Great!

MySQL, MySQL 5.1, Oracle