MySQL offers a variety of storage engines giving you a lot of flexibility in managing your storage and data access needs. Still I encounter customers who are not using this flexibility when they should, because they lack information about the advantages of Non-MyISAM storage engines or which are using storage engines like InnoDB as if they were using MyISAM.
This is the introductory article in a series of texts that will hopefully once become an InnoDB tutorial. A german version of this article is available
in my german language blog.
An InnoDB Tutorial
The InnoDB storage engine is an engine that can be operated ACID compliant, does transactions and foreign key constraints. It is useful for all applications that do online transaction processing or have a high rate of concurrent write accesses for other reasons.
Is InnoDB enabled in my server?
The SQL command SHOW ENGINES will return a list of known engines and tell you if they are enabled and operational or not. If the engine is included in your server and operational it is shown with "YES". If it is included, but not operational, "DISABLED" is shown. If it is not even part of your servers codebase, a "NO" is shown.
CODE:
root on mysql.sock [(none)]> show engines;
+------------+---------+----------------------------------------------------------------+
| Engine | Support | Comment |
+------------+---------+----------------------------------------------------------------+
| MyISAM | DEFAULT | Default engine as of MySQL 3.23 with great performance |
| MEMORY | YES | Hash based, stored in memory, useful for temporary tables |
| InnoDB | YES | Supports transactions, row-level locking, and foreign keys |
| BerkeleyDB | NO | Supports transactions and page-level locking |
| BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) |
| EXAMPLE | YES | Example storage engine |
| ARCHIVE | YES | Archive storage engine |
| CSV | YES | CSV storage engine |
| ndbcluster | NO | Clustered, fault-tolerant, memory-based tables |
| FEDERATED | YES | Federated MySQL storage engine |
| MRG_MYISAM | YES | Collection of identical MyISAM tables |
| ISAM | NO | Obsolete storage engine |
+------------+---------+----------------------------------------------------------------+
12 rows in set (0.00 sec)
An InnoDB minimum configuration
All other examples in this tutorial assume that you MySQL server has operational InnoDB support. The minimum configuration shown below is not suitable for production, but should be sufficient to go from DISABLED to YES.
- Use SHOW GLOBAL VARIABLES LIKE 'datadir' to locate the data directory of your MySQL instance. Within this directory there may be two files ib_logfile0 and ib_logfile1 and one ibdata1. If not, no problem - they will be created in the next steps.
CODE:
root on mysql.sock [(none)]> show global variables like 'datadir';
+---------------+------------------------------+
| Variable_name | Value |
+---------------+------------------------------+
| datadir | /export/data/rootforum/data/ |
+---------------+------------------------------+
1 row in set (0.00 sec)
root on mysql.sock [(none)]> quit
Bye
linux:/export/data/rootforum # ls -lh /export/data/rootforum/data/ib*
-rw-rw---- 1 mysql mysql 5M Jan 9 17:51 /export/data/rootforum/data/ib_logfile0
-rw-rw---- 1 mysql mysql 5M Jan 9 17:51 /export/data/rootforum/data/ib_logfile1
-rw-rw---- 1 mysql mysql 10M Dec 13 14:34 /export/data/rootforum/data/ibdata1
- Shut down the server.
- Delete the above three files if they are present in your server. WARNING! This will delete all data stored in InnoDB tables in your server. Do this only if your engine was shown as DISABLED before.
- Find your servers my.cnf or my.ini file and look for the configuration statement skip-innodb in the [mysqld] section of your server. If it is in there, remove it.
- In place of skip-innodb put in the following configuration statements. They will be explained later.
CODE:
innodb
innodb_file_per_table = 1
innodb_flush_log_at_trx_commit = 2
This is just a minimal setup for testing and is not suitable for performance. See the rest of the tutorial for more informastion on performance. - Restart your server. It will create new ibdata1, ib_logfile0 an ib_logfile1 files and put appropriate messages in your log. After the server completes startup, SHOW ENGINES lists InnoDB as "YES".
- If this does not work, connect a Freenode IRC server and join the channel #mysql. Ask for help in there.
Creating InnoDB tables
When writing a CREATE TABLE statement you may add an ENGINE clause at the end of the statement. This clause determines which storage engine will be used for this particular table. It can be different for each and every table in your server. The engine used for a table can be changed later at any time using the ALTER TABLE statement without data loss.
CODE:
root on mysql.sock [(none)]> create database innodemo;
Query OK, 1 row affected (0.32 sec)
root on mysql.sock [(none)]> use innodemo;
Database changed
root on mysql.sock [innodemo]> create table kris (
id integer unsigned not null primary key auto_increment,
d varchar(20) not null
) engine = innodb;
Query OK, 0 rows affected (0.36 sec)
root on mysql.sock [innodemo]> insert into kris ( d ) values ( "eins"), ("zwei"), ("drei");
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
root on mysql.sock [innodemo]> select * from kris;
+----+------+
| id | d |
+----+------+
| 1 | eins |
| 2 | zwei |
| 3 | drei |
+----+------+
3 rows in set (0.01 sec)
You can use SHOW CREATE TABLE or SHOW TABLE STATUS to see what storage engine is being used for a table.
CODE:
root on mysql.sock [innodemo]> show create table kris\G
*************************** 1. row ***************************
Table: kris
Create Table: CREATE TABLE `kris` (
`id` int(10) unsigned NOT NULL auto_increment,
`d` varchar(20) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
root on mysql.sock [innodemo]> show table status like "kris"\G
*************************** 1. row ***************************
Name: kris
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 3
Avg_row_length: 5461
Data_length: 16384
Max_data_length: 0
Index_length: 0
Data_free: 0
Auto_increment: 4
Create_time: 2008-01-09 18:04:00
Update_time: NULL
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options:
Comment: InnoDB free: 148480 kB
1 row in set (0.00 sec)
The output of the above SHOW TABLE STATUS will show you a few "kinks" specific to InnoDB. For example, the average row length shown is way off for tables with few records. We know that the average row length in our table is approximately 4 (integer) + 5 (1 length byte + 4 characters) plus some administrative overhead (10-12 byte per row). InnoDB is managing space in pages of 16K, though, and consequently Data_length is shown as 16384. The Average_row_length is not a statistic maintained by InnoDB and so MySQL fakes it by calculating Data_length/Rows. For small numbers of rows this is a bad estimate.
We also see that the Index_length for out table is 0, even if we have defined a primary key. We will address this later in more detail. For the moment it is enough that the primary key in InnoDB is part of the data, and also somewhat magical.
Data_free in InnoDB is always 0, unlike MyISAM.
Changing existing tables to InnoDB
Use the ALTER TABLE command to change an existing table from InnoDB to MyISAM or back. Data will not be lost.
Here for reference the output of SHOW TABLE STATUS for the same table being MyISAM and InnoDB. We can see how MyISAM counts the primary key as an Index and not data, how the MyISAM index is page based, but with smaller pages (1K instead of 16K) and how the data is not page based.
CODE:
root on mysql.sock [innodemo]> alter table kris engine=myisam;
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0
root on mysql.sock [innodemo]> select * from kris;
+----+------+
| id | d |
+----+------+
| 1 | eins |
| 2 | zwei |
| 3 | drei |
+----+------+
3 rows in set (0.00 sec)
root on mysql.sock [innodemo]> show table status like "kris"\G
*************************** 1. row ***************************
Name: kris
Engine: MyISAM
Version: 10
Row_format: Dynamic
Rows: 3
Avg_row_length: 20
Data_length: 60
Max_data_length: 281474976710655
Index_length: 2048
Data_free: 0
Auto_increment: 4
Create_time: 2008-01-09 18:11:57
Update_time: 2008-01-09 18:11:57
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.00 sec)
We may change our table back to InnoDB. We even can change an existing InnoDB table to InnoDB again. And this is even useful - it is what OPTIMIZE TABLE does when you apply it to InnoDB tables.
CODE:
root on mysql.sock [innodemo]> alter table kris engine=innodb;
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
root on mysql.sock [innodemo]> alter table kris engine=innodb;
Query OK, 3 rows affected (0.02 sec)
Records: 3 Duplicates: 0 Warnings: 0
root on mysql.sock [innodemo]> select * from kris;
+----+------+
| id | d |
+----+------+
| 1 | eins |
| 2 | zwei |
| 3 | drei |
+----+------+
3 rows in set (0.00 sec)
Using ALTER TABLE t ENGINE=... will create a copy of the original table in the new engine as a temporary table. Then the original table will be dropped and the temporary table will we installed in its place. This has a number of consequences:
- The operation cannot lose data. If it fails or is stopped by an operator the temporary table will be dropped and the original table is unchanged.
- At some point in time both versions of the table exist at once. This may use a lot of storage. Note that InnoDB does not pack data as tightly as MyISAM does - it will use 1.6 to 2.2 times the space that MyISAM uses..
- During the ALTER TABLE both tables will be locked. You can avoid that with a sequence of
- CREATE TABLE b LIKE a;
- ALTER TABLE b ENGINE=InnoDB
- INSERT INTO b SELECT * FROM a;
This will put only a read lock on a for the duration of the copy process. Also, you may use a LIMIT clause on your INSERT ... SELECT to create only a limited subset of data for experimentation.