10 things you might not know about MySQLJorge Bernal <[email protected]>
Version 0.1
1Query cache
Beforemysql> SELECT AVG(Population) FROM City_huge;+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| AVG(Population) |+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| 354359.9948 | +‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+1 row in set (0.58 sec)
mysql> SELECT AVG(Population) FROM City_huge;+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| AVG(Population) |+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| 354359.9948 | +‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+1 row in set (0.56 sec)
Aftermysql> SELECT AVG(Population) FROM City_huge;+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| AVG(Population) |+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| 354359.9948 | +‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+1 row in set (0.56 sec)
mysql> SELECT AVG(Population) FROM City_huge;+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| AVG(Population) |+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| 354359.9948 | +‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+1 row in set (0.00 sec)
The magicSET global query_cache_size=8 * 1024 * 1024;mysql> SHOW GLOBAL VARIABLES LIKE 'query_cache%';+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+| Variable_name | Value |+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+| query_cache_limit | 1048576 | | query_cache_min_res_unit | 4096 | | query_cache_size | 8388608 | | query_cache_type | ON | | query_cache_wlock_invalidate | OFF | +‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+5 rows in set (0.00 sec)
mysql> SHOW GLOBAL STATUS LIKE 'Qc%';+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+| Variable_name | Value |+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+| Qcache_free_blocks | 1 | | Qcache_free_memory | 8378312 | | Qcache_hits | 1 | | Qcache_inserts | 1 | | Qcache_lowmem_prunes | 0 | | Qcache_not_cached | 0 | | Qcache_queries_in_cache | 1 | | Qcache_total_blocks | 4 | +‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+8 rows in set (0.00 sec)
2Life beyond MyISAM/
InnoDB
Archivemysql> SELECT ENGINE, (INDEX_LENGTH+DATA_LENGTH) AS Size FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'City_huge';+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐+| ENGINE | Size |+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐+| MyISAM | 63203585 | +‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐+
mysql> SELECT ENGINE, (INDEX_LENGTH+DATA_LENGTH) AS Size FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = 'City_huge';+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐+| ENGINE | Size |+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐+| ARCHIVE | 13520399 | +‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐+
ARCHIVE MyISAM
80% compression
But...
• Only INSERT and SELECT
• No indexing
CSVmysql> ALTER TABLE City ENGINE=CSV;Query OK, 0 rows affected (0.05 sec)Records: 0 Duplicates: 0 Warnings: 0
root@warhol:/usr/local/mysql/data/world$ head City.CSV 1,"Kabul","AFG","Kabol",17800002,"Qandahar","AFG","Qandahar",2375003,"Herat","AFG","Herat",1868004,"Mazar‐e‐Sharif","AFG","Balkh",1278005,"Amsterdam","NLD","Noord‐Holland",7312006,"Rotterdam","NLD","Zuid‐Holland",5933217,"Haag","NLD","Zuid‐Holland",4409008,"Utrecht","NLD","Utrecht",2343239,"Eindhoven","NLD","Noord‐Brabant",20184310,"Tilburg","NLD","Noord‐Brabant",193238
Mergemysql> SHOW CREATE TABLE allweek\G[...]Create Table: CREATE TABLE `allweek` ( `ID` int(11) NOT NULL DEFAULT '0', `Name` char(35) NOT NULL DEFAULT '', `CountryCode` char(3) NOT NULL DEFAULT '', `District` char(20) NOT NULL DEFAULT '', `Population` int(11) NOT NULL DEFAULT '0', `modtime` datetime DEFAULT NULL, KEY `modtime` (`modtime`)) ENGINE=MRG_MyISAM DEFAULT CHARSET=latin1 UNION=(`monday`,`tuesday`,`wednesday`,`thursday`,`citydate`)1 row in set (0.00 sec)
3AUTO_INCREMENT
woes
mysql> CREATE TABLE t ( ‐> id INT NOT NULL PRIMARY KEY AUTO_INCREMENT ‐> );Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO t (id) ‐> VALUES (NULL);Query OK, 1 row affected (0.04 sec)
mysql> SELECT * ‐> FROM t ‐> WHERE id IS NULL;+‐‐‐‐+| id |+‐‐‐‐+| 1 | +‐‐‐‐+
But...• id is AUTO_INCREMENT, so it’s 1
• let’s run that again
mysql> CREATE TABLE t ( ‐> id INT NOT NULL PRIMARY KEY AUTO_INCREMENT ‐> );Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO t (id) ‐> VALUES (NULL);Query OK, 1 row affected (0.04 sec)
mysql> SELECT * ‐> FROM t ‐> WHERE id IS NULL;+‐‐‐‐+| id |+‐‐‐‐+| 1 | +‐‐‐‐+
mysql> CREATE TABLE t ( ‐> id INT NOT NULL PRIMARY KEY AUTO_INCREMENT ‐> );Query OK, 0 rows affected (0.00 sec)
mysql> INSERT INTO t (id) ‐> VALUES (NULL);Query OK, 1 row affected (0.04 sec)
mysql> SELECT * ‐> FROM t ‐> WHERE id IS NULL;+‐‐‐‐+| id |+‐‐‐‐+| 1 | +‐‐‐‐+mysql> SELECT * ‐> FROM t ‐> WHERE id IS NULL;Empty set (0.00 sec)
mysql> SELECT * FROM t;+‐‐‐‐+| id |+‐‐‐‐+| 1 | +‐‐‐‐+1 row in set (0.00 sec)
WTF???
• WHERE ID IS NULL acts as
WHERE ID = LAST_ISERT_ID()
• But only the first time
• Brought to you by some weird ODBC compatibility decision
4How to get random
data
SELECT name FROM randomORDER BY RAND()LIMIT 1
SELECT name FROM randomORDER BY RAND()LIMIT 1X
Wrong!
There is a better waySELECT nameFROM random JOIN( SELECT CEIL( RAND() * (SELECT MAX(id) FROM random)) AS id) AS r2USING (id);
Really?
Really?
0,10
1,00
10,00
100,00
1.000,00
10.000,00
100 1000 10000 100000
Seco
nds
Table Size
ORDER BY RAND() Subquery
So...
Every time you use ORDER BY RAND()...God kills a kitten
Please, think of the kittens
5Prefix indexes
There’s no need to index the whole
column
Name is CHAR(52)mysql> SHOW CREATE TABLE Country\G*************************** 1. row *************************** Table: CountryCreate Table: CREATE TABLE `Country` ( `Code` char(3) NOT NULL DEFAULT '', `Name` char(52) NOT NULL DEFAULT '', `Continent` enum('Asia','Europe','North America','Africa','Oceania','Antarctica','South America') NOT NULL DEFAULT 'Asia', `Region` char(26) NOT NULL DEFAULT '', `SurfaceArea` float(10,2) NOT NULL DEFAULT '0.00', `IndepYear` smallint(6) DEFAULT NULL, `Population` int(11) NOT NULL DEFAULT '0', `LifeExpectancy` float(3,1) DEFAULT NULL, `GNP` float(10,2) DEFAULT NULL, `GNPOld` float(10,2) DEFAULT NULL, `LocalName` char(45) NOT NULL DEFAULT '', `GovernmentForm` char(45) NOT NULL DEFAULT '', `HeadOfState` char(60) DEFAULT NULL, `Capital` int(11) DEFAULT NULL, `Code2` char(2) NOT NULL DEFAULT '', PRIMARY KEY (`Code`)) ENGINE=MyISAM DEFAULT CHARSET=latin11 row in set (0.02 sec)
mysql> SELECT COUNT(*) AS Total, COUNT(DISTINCT Name) AS Diff, COUNT(*) ‐ COUNT(DISTINCT Name) AS Dupes FROM Country;+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Total | Diff | Dupes |+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| 239 | 239 | 0 | +‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (1.04 sec)mysql> SELECT COUNT(*) AS Total, COUNT(DISTINCT LEFT(Name,50)) AS Diff, COUNT(*) ‐ COUNT(DISTINCT LEFT(Name,50)) AS Dupes FROM Country;+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Total | Diff | Dupes |+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| 239 | 239 | 0 | +‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (2.02 sec)
mysql> SELECT COUNT(*) AS Total, COUNT(DISTINCT LEFT(Name,20)) AS Diff, COUNT(*) ‐ COUNT(DISTINCT LEFT(Name,20)) AS Dupes FROM Country;+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Total | Diff | Dupes |+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| 239 | 239 | 0 | +‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (0.93 sec)
mysql> SELECT COUNT(*) AS Total, COUNT(DISTINCT Name) AS Diff, COUNT(*) ‐ COUNT(DISTINCT Name) AS Dupes FROM Country;+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Total | Diff | Dupes |+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| 239 | 239 | 0 | +‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (1.04 sec)mysql> SELECT COUNT(*) AS Total, COUNT(DISTINCT LEFT(Name,50)) AS Diff, COUNT(*) ‐ COUNT(DISTINCT LEFT(Name,50)) AS Dupes FROM Country;+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Total | Diff | Dupes |+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| 239 | 239 | 0 | +‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (2.02 sec)
mysql> SELECT COUNT(*) AS Total, COUNT(DISTINCT LEFT(Name,20)) AS Diff, COUNT(*) ‐ COUNT(DISTINCT LEFT(Name,20)) AS Dupes FROM Country;+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Total | Diff | Dupes |+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| 239 | 239 | 0 | +‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (0.93 sec)
Got it? Cool! Now...
There is a better wayCREATE DEFINER=`root`@`localhost` PROCEDURE `pref_index`(t_name CHAR(255), c_name CHAR(255))BEGIN
DECLARE plength INT DEFAULT 1;SET @q = CONCAT('SELECT CHARACTER_MAXIMUM_LENGTH INTO @maxlen FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = "world2" AND TABLE_NAME = "', t_name,'" AND COLUMN_NAME = "', c_name, '"');PREPARE q FROM @q;EXECUTE q;DEALLOCATE PREPARE q;
REPEAT SET @qq = CONCAT('SELECT COUNT(*) ‐ COUNT(DISTINCT ',c_name,') into @dupe FROM ',t_name); SET @pq = CONCAT('SELECT COUNT(*) ‐ COUNT(DISTINCT LEFT(',c_name,',',plength,')) into @pdupe FROM ',t_name);
PREPARE qs FROM @qq; EXECUTE qs; DEALLOCATE PREPARE qs;
PREPARE ps FROM @pq; EXECUTE ps; DEALLOCATE PREPARE ps;
SET plength = plength + 1;UNTIL plength >= @maxlen OR @pdupe = @dupeEND REPEAT;SELECT plength, @pdupe, @dupe;END
mysql> CALL pref_index('Country', 'Name');+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| plength | @pdupe | @dupe |+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| 18 | 0 | 0 | +‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (1.18 sec)
Query OK, 0 rows affected (1.18 sec)
mysql> SELECT COUNT(*) AS Total, COUNT(DISTINCT LEFT(Name,18)) AS Diff, COUNT(*) ‐ COUNT(DISTINCT LEFT(Name,18)) AS Dupes FROM Country;+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Total | Diff | Dupes |+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| 239 | 239 | 0 | +‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (0.00 sec)
mysql> ALTER TABLE Country ADD KEY (Name(18));Query OK, 239 rows affected (1.48 sec)Records: 239 Duplicates: 0 Warnings: 0
mysql> CALL pref_index('Country', 'Name');+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| plength | @pdupe | @dupe |+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| 18 | 0 | 0 | +‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (1.18 sec)
Query OK, 0 rows affected (1.18 sec)
mysql> SELECT COUNT(*) AS Total, COUNT(DISTINCT LEFT(Name,18)) AS Diff, COUNT(*) ‐ COUNT(DISTINCT LEFT(Name,18)) AS Dupes FROM Country;+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Total | Diff | Dupes |+‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+| 239 | 239 | 0 | +‐‐‐‐‐‐‐+‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (0.00 sec)
mysql> ALTER TABLE Country ADD KEY (Name(18));Query OK, 239 rows affected (1.48 sec)Records: 239 Duplicates: 0 Warnings: 0
We just saved 34 bytes per row!
6InnoDB clustered index
Primary key
Leaf nodes
Index
Data stored in PK order
Indexes point to PK instead of actual data
So what?
Choose your PK wisely
Load data in ordermysql> load data infile '/tmp/city_order.txt' into table City_huge;Query OK, 818027 rows affected (15.86 sec)Records: 818027 Deleted: 0 Skipped: 0 Warnings: 0
mysql> load data infile '/tmp/city_rand.txt' into table City_huge;Query OK, 818027 rows affected (33 min 23.57 sec)Records: 818027 Deleted: 0 Skipped: 0 Warnings: 0
7Profiling
mysql> select * from v_client_portfolio_high;+‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| client_id | client_first_name | client_last_name | portfolio_value |+‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| 5 | ABNER | ROSSELLETT | 1252115.50 || 500 | CANDICE | BARTLETT | 1384877.50 |+‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+2 rows in set (4.01 sec)
mysql> select * from v_client_portfolio_high;+‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| client_id | client_first_name | client_last_name | portfolio_value |+‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| 5 | ABNER | ROSSELLETT | 1252115.50 || 500 | CANDICE | BARTLETT | 1384877.50 |+‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+2 rows in set (4.01 sec) Why?
mysql> set profiling=1;Query OK, 0 rows affected (0.00 sec)
mysql> select * from v_client_portfolio_high;+‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| client_id | client_first_name | client_last_name | portfolio_value |+‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| 5 | ABNER | ROSSELLETT | 1252115.50 || 500 | CANDICE | BARTLETT | 1384877.50 |+‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+2 rows in set (4.01 sec)
mysql> show profiles;+‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| Query_ID | Duration | Query |+‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+| 1 | 0.00007600 | set profiling=1 || 2 | 4.01965600 | select * from v_client_portfolio_high |+‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+
mysql> select min(seq) seq,state,count(*) numb_ops, ‐> round(sum(duration),5) sum_dur, round(avg(duration),5) avg_dur, ‐> round(sum(cpu_user),5) sum_cpu, round(avg(cpu_user),5) avg_cpu ‐> from information_schema.profiling ‐> where query_id = 2 ‐> group by state ‐> order by seq;+‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+| seq | state | numb_ops | sum_dur | avg_dur | sum_cpu | avg_cpu |+‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+| 0 | (initialization) | 1 | 0.00004 | 0.00004 | 0.00000 | 0.00000 || 1 | Opening tables | 1 | 0.00023 | 0.00023 | 0.00000 | 0.00000 || 2 | System lock | 1 | 0.00001 | 0.00001 | 0.00000 | 0.00000 || 3 | Table lock | 1 | 0.00001 | 0.00001 | 0.00000 | 0.00000 || 4 | checking permissions | 1 | 0.00010 | 0.00010 | 0.00000 | 0.00000 || 5 | optimizing | 4 | 0.00004 | 0.00001 | 0.00000 | 0.00000 || 6 | statistics | 4 | 0.00007 | 0.00002 | 0.00100 | 0.00025 || 7 | preparing | 4 | 0.00005 | 0.00001 | 0.00000 | 0.00000 || 8 | Creating tmp table | 1 | 0.00003 | 0.00003 | 0.00000 | 0.00000 || 9 | executing | 37352 | 0.16631 | 0.00000 | 0.05899 | 0.00000 || 10 | Copying to tmp table | 1 | 0.00006 | 0.00006 | 0.00000 | 0.00000 || 15 | Sending data | 37353 | 3.85151 | 0.00010 | 3.72943 | 0.00010 || 74717 | Sorting result | 1 | 0.00112 | 0.00112 | 0.00100 | 0.00100 || 74719 | removing tmp table | 2 | 0.00003 | 0.00001 | 0.00000 | 0.00000 || 74721 | init | 1 | 0.00002 | 0.00002 | 0.00000 | 0.00000 || 74727 | end | 1 | 0.00001 | 0.00001 | 0.00000 | 0.00000 || 74728 | query end | 1 | 0.00000 | 0.00000 | 0.00000 | 0.00000 || 74729 | freeing items | 1 | 0.00002 | 0.00002 | 0.00000 | 0.00000 || 74730 | closing tables | 2 | 0.00001 | 0.00001 | 0.00000 | 0.00000 || 74733 | logging slow query | 1 | 0.00000 | 0.00000 | 0.00000 | 0.00000 |+‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐‐‐+
But...
• Only in 5.0
• 5.1 implementation under review
• http://forge.mysql.com/wiki/Testing_Show_Profiles_5_1
8Know what’s going on
Usage statistics
SHOW STATUSmysql> SHOW STATUS LIKE 'Com_select';+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Variable_name | Value |+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Com_select | 13 | +‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (1.65 sec)
mysql> SELECT 1;+‐‐‐+| 1 |+‐‐‐+| 1 | +‐‐‐+1 row in set (0.00 sec)
mysql> SHOW STATUS LIKE 'Com_select';+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Variable_name | Value |+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Com_select | 14 | +‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (0.00 sec)
SHOW STATUSmysql> SHOW STATUS LIKE 'Com_select';+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Variable_name | Value |+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Com_select | 14 | +‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (0.00 sec)
mysql> FLUSH STATUS;Query OK, 0 rows affected (0.00 sec)
mysql> SHOW STATUS LIKE 'Com_select';+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Variable_name | Value |+‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+| Com_select | 0 | +‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐+‐‐‐‐‐‐‐+1 row in set (0.00 sec)
External tools
• mysqlsla
http://hackmysql.com/mysqlsla
mysqlsla parses, filters, analyzes and sorts MySQL slow, general, binary and microslow patched logs in order to create a customizable report of the queries and their meta-property values.
External tools• mysqlidxchk
http://hackmysql.com/mysqlidxchk
mysqlidxchk (MySQL Index Checker) checks MySQL databases/tables for unused indexes. Given one or more slow, general, or "raw" log files, mysqlidxchk reports which indexes in the database schema are not used by the queries in the log files.
9Understanding
REPLACE
mysql> CREATE TABLE fk_relations ( ‐> key1 INT NOT NULL PRIMARY KEY, ‐> key2 INT NOT NULL UNIQUE ‐> );Query OK, 0 rows affected (0.02 sec)
mysql> INSERT INTO fk_relations VALUES (1,1), (2,2);Query OK, 2 rows affected (0.00 sec)Records: 2 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM fk_relations;+‐‐‐‐‐‐+‐‐‐‐‐‐+| key1 | key2 |+‐‐‐‐‐‐+‐‐‐‐‐‐+| 1 | 1 | | 2 | 2 | +‐‐‐‐‐‐+‐‐‐‐‐‐+2 rows in set (0.00 sec)
mysql> REPLACE INTO fk_relations VALUES (1,3);Query OK, 2 rows affected (0.03 sec)
mysql> REPLACE INTO fk_relations VALUES (1,3);Query OK, 2 rows affected (0.03 sec)
mysql> SELECT * FROM fk_relations;+‐‐‐‐‐‐+‐‐‐‐‐‐+| key1 | key2 |+‐‐‐‐‐‐+‐‐‐‐‐‐+| 1 | 3 | | 2 | 2 | +‐‐‐‐‐‐+‐‐‐‐‐‐+2 rows in set (0.00 sec)
mysql> REPLACE INTO fk_relations VALUES (1,2);Query OK, 3 rows affected (0.06 sec)
???
mysql> REPLACE INTO fk_relations VALUES (1,2);Query OK, 3 rows affected (0.06 sec)
mysql> SELECT * FROM fk_relations;+‐‐‐‐‐‐+‐‐‐‐‐‐+| key1 | key2 |+‐‐‐‐‐‐+‐‐‐‐‐‐+| 1 | 2 | +‐‐‐‐‐‐+‐‐‐‐‐‐+1 row in set (0.00 sec)
+‐‐‐‐‐‐+‐‐‐‐‐‐+| key1 | key2 |+‐‐‐‐‐‐+‐‐‐‐‐‐+| 1 | 3 | | 2 | 2 | +‐‐‐‐‐‐+‐‐‐‐‐‐+
mysql> REPLACE INTO fk_relations VALUES (1,2);‐‐ Equals...
+‐‐‐‐‐‐+‐‐‐‐‐‐+| key1 | key2 |+‐‐‐‐‐‐+‐‐‐‐‐‐+| 1 | 3 | | 2 | 2 | +‐‐‐‐‐‐+‐‐‐‐‐‐+
mysql> REPLACE INTO fk_relations VALUES (1,2);‐‐ Equals...‐‐ key1 is PKmysql> DELETE FROM fk_relations WHERE key1 = 1;
+‐‐‐‐‐‐+‐‐‐‐‐‐+| key1 | key2 |+‐‐‐‐‐‐+‐‐‐‐‐‐+| 2 | 2 | +‐‐‐‐‐‐+‐‐‐‐‐‐+
mysql> REPLACE INTO fk_relations VALUES (1,2);‐‐ Equals...‐‐ key1 is PKmysql> DELETE FROM fk_relations WHERE key1 = 1;‐‐ key2 is Uniquemysql> DELETE FROM fk_relations WHERE key2 = 2;
+‐‐‐‐‐‐+‐‐‐‐‐‐+| key1 | key2 |+‐‐‐‐‐‐+‐‐‐‐‐‐+| 1 | 2 | +‐‐‐‐‐‐+‐‐‐‐‐‐+
mysql> REPLACE INTO fk_relations VALUES (1,2);‐‐ Equals...‐‐ key1 is PKmysql> DELETE FROM fk_relations WHERE key1 = 1;‐‐ key2 is Uniquemysql> DELETE FROM fk_relations WHERE key2 = 2;mysql> INSERT INTO fk_relations VALUES (1,2);
That’s whymysql> REPLACE INTO fk_relations VALUES (1,2);Query OK, 3 rows affected (0.06 sec)
mysql> SELECT * FROM fk_relations;+‐‐‐‐‐‐+‐‐‐‐‐‐+| key1 | key2 |+‐‐‐‐‐‐+‐‐‐‐‐‐+| 1 | 2 | +‐‐‐‐‐‐+‐‐‐‐‐‐+1 row in set (0.00 sec)
That’s whymysql> REPLACE INTO fk_relations VALUES (1,2);Query OK, 3 rows affected (0.06 sec)
mysql> SELECT * FROM fk_relations;+‐‐‐‐‐‐+‐‐‐‐‐‐+| key1 | key2 |+‐‐‐‐‐‐+‐‐‐‐‐‐+| 1 | 2 | +‐‐‐‐‐‐+‐‐‐‐‐‐+1 row in set (0.00 sec)
2 deletes + 1 insert
10The good and bad
about temporary tables
Performance
• Be careful with complex queries
• Extremely slow on large datasets
• Use summary tables instead
Too complexselect d.dept_name, SUM(salary) from departments d LEFT JOIN dept_emp de USING (dept_no) LEFT JOIN salaries s USING (emp_no) where s.from_date >= '2000‐01‐01' and s.to_date < '2001‐01‐01' group by dept_no;
Instead, split in twoselect d.dept_name, SUM(salary) from departments d LEFT JOIN dept_emp de USING (dept_no) LEFT JOIN salaries s USING (emp_no) where s.from_date >= '2000‐01‐01' and s.to_date < '2001‐01‐01' group by dept_no;
CREATE TEMPORARY TABLE salaries2000 SELECT * FROM salaries s WHERE s.from_date >= '2000‐01‐01' AND s.to_date < '2001‐01‐01';
SELECT d.dept_name, SUM(salary) FROM departments d LEFT JOIN dept_emp de USING (dept_no) LEFT JOIN salaries2000 s USING (emp_no) GROUP BY dept_no;
Big performance gain(If indexes are in their place)
Big performance gain(If indexes are in their place)
I’ve seen 10X!
one more thing...
one more thing...I had to do it ;-)
Shit happensmysql> select * from t;+‐‐‐‐‐‐+| id |+‐‐‐‐‐‐+| 2 | +‐‐‐‐‐‐+1 row in set (0.00 sec)
mysql> drop table t;Query OK, 0 rows affected (0.04 sec)
mysql> drop table t;Query OK, 0 rows affected (0.00 sec)
mysql> drop table t;ERROR 1051 (42S02): Unknown table 't'
WTF?!?
Some seconds ago...
mysql> select * from t;+‐‐‐‐+| id |+‐‐‐‐+| 1 | +‐‐‐‐+1 row in set (0.00 sec)
mysql> create temporary table t(id int);Query OK, 0 rows affected (0.08 sec)
mysql> select * from t;Empty set (0.00 sec)
mysql> insert into t values (2);Query OK, 1 row affected (0.05 sec)
mysql> select * from t;+‐‐‐‐‐‐+| id |+‐‐‐‐‐‐+| 2 | +‐‐‐‐‐‐+1 row in set (0.00 sec)
WTF?!?...again
• Temporary tables exist per session
• Be careful using connection pools
• They overlap current tables
There’s a reason for everythingmysql> select * from t;+‐‐‐‐‐‐+| id |+‐‐‐‐‐‐+| 2 | +‐‐‐‐‐‐+1 row in set (0.00 sec)
mysql> drop table t;Query OK, 0 rows affected (0.04 sec)
mysql> select * from t;+‐‐‐‐+| id |+‐‐‐‐+| 1 | +‐‐‐‐+1 row in set (0.00 sec)
mysql> drop table t;Query OK, 0 rows affected (0.00 sec)
mysql> drop table t;ERROR 1051 (42S02): Unknown table 't'
temporary
regular
• MySQL Proxy
https://launchpad.net/mysql-proxy
• MySQL Sandbox
https://launchpad.net/mysql-sandbox
• MySQL Random Query Generator
https://launchpad.net/randgen
What else?
Questions?Probably out of time at this point, but it’s the standard
Thanks!