[+/-]
This section discusses recent InnoDB enhancements to performance and scalability, covering the performance features in MySQL 5.5 and higher, and in the InnoDB Plugin for MySQL 5.1. This information is useful to any DBA or developer who is concerned with performance and scalability. Although some of the enhancements do not require any action on your part, knowing this information can still help you diagnose performance issues more quickly and modernize systems and applications that rely on older, inefficient behavior.
InnoDB has always been highly efficient, and includes several unique architectural elements to assure high performance and scalability. The latest InnoDB storage engine includes new features that take advantage of advances in operating systems and hardware platforms, such as multi-core processors and improved memory allocation systems. In addition, new configuration options let you better control some InnoDB internal subsystems to achieve the best performance with your workload.
Starting with MySQL 5.5 and InnoDB 1.1, the built-in InnoDB
storage engine within MySQL is upgraded to the full feature set
and performance of the former InnoDB Plugin. This change makes
these performance and scalability enhancements available to a much
wider audience than before, and eliminates the separate
installation step of the InnoDB Plugin. After learning about the
InnoDB performance features in this section, continue with
Chapter 8, Optimization to learn the best practices for
overall MySQL performance, and Section 8.5, “Optimizing for InnoDB Tables”
in particular for InnoDB tips and guidelines.
When a transaction is known to be read-only,
InnoDB can avoid the overhead associated with
setting up the isolation
level for a
transaction that might
perform arbitrary sequences of read and write operations.
Currently, InnoDB detects this condition and
applies the optimization automatically when the following
conditions are met:
The autocommit setting is turned on, so that the transaction is guaranteed to be a single statement.
The single statement making up the transaction is a
“non-locking”
SELECT statement, that is, one
that does not use the FOR UPDATE or
LOCK IN SHARED MODE clause.
Thus, for a read-intensive application such as a report generator,
you can improve performance for InnoDB queries
by turning on the autocommit setting before
running a sequence of SELECT queries. If other
sessions are making changes concurrently in the database, each
query sees the very latest committed data, similar to the
READ COMMITTED transaction
isolation level.
Because these optimized transactions are kept out of certain
internal InnoDB data structures, they are not
listed in SHOW
ENGINE INNODB STATUS output.
This feature optionally moves the InnoDB
undo log out of the
system tablespace
into one or more separate
tablespaces. The I/O
patterns for the undo log make these new tablespaces good
candidates to move to SSD storage, while keeping the system
tablespace on hard disk storage. Users cannot drop the separate
tablespaces created to hold InnoDB undo logs,
or the individual segments
inside those tablespaces.
Because these files handle I/O operations formerly done inside the system tablespace, we broaden the definition of system tablespace to include these new files.
The undo logs are also known as the rollback segments.
This feature involves the following new or renamed configuration options:
innodb_rollback_segments becomes
innodb_undo_logs. The old
name is still available for compatibility.
To use this feature, follow these steps:
Decide on a path on a fast storage device to hold the undo
logs. You will specify that path as the argument to the
innodb_undo_directory option
in your MySQL configuration file or startup script.
Decide on a non-zero starting value for the
innodb_undo_logs option. You
can start with a relatively low value and increase it over
time to examine the effect on performance.
Decide on a non-zero value for the
innodb_undo_tablespaces
option. The multiple undo logs specified by the
innodb_undo_logs value are divided up
between this many separate tablespaces (represented by
.ibd
files). This value is fixed for the life of the MySQL
instance, so if you are uncertain about the optimal value,
estimate on the high side.
Set up an entirely new MySQL instance for testing, using the values you chose in the configuration file or in your MySQL startup script. Use a realistic workload with data volume similar to your production servers.
Benchmark the performance of I/O intensive workloads.
Periodically increase the value of
innodb_undo_logs and re-do the performance
tests. Find the value where you stop experiencing gains in I/O
performance.
Deploy a new production instance using the ideal settings for these options. Set it up as a slave server in a replication configuration, or transfer data from an earlier production instance.
Keeping the undo logs in separate files allows the MySQL team to
implement I/O and memory optimizations related to this
transactional data. For example, because the undo data is written
to disk and then rarely used (only in case of crash recovery), it
does not need to be kept in the filesystem memory cache, in turn
allowing a higher percentage of system memory to be devoted to the
InnoDB buffer
pool.
The typical SSD best practice of keeping the
InnoDB system tablespace on a hard drive and
moving the per-table tablespaces to SSD, is assisted by moving the
undo information into separate tablespace files.
The physical tablespace files are named
undo, where
NN is the space ID, including leading
zeros.
Currently, MySQL instances containing separate undo tablespaces cannot be downgraded to earlier releases such as MySQL 5.5 or 5.1.
The benefits of the InnoDB
file-per-table setting
come with the tradeoff that each
.ibd
file is extended as the data inside the table grows. This I/O
operation can be a bottleneck for busy systems with many
InnoDB tables. When all
InnoDB tables are stored inside the
system tablespace,
this extension operation happens less frequently, as space freed
by DELETE or TRUNCATE
operations within one table can be reused by another table.
MySQL 5.6 improves the concurrency of the extension operation, so
that multiple .ibd files can be extended
simultaneously, and this operation does not block read or write
operations performed by other threads.
The code that detects
deadlocks in
InnoDB
transactions has been
modified to use a fixed-size work area rather than a recursive
algorithm. The resulting detection operation is faster as a
result. You do not need to do anything to take advantage of this
enhancement.
Under both the old and new detection mechanisms, you might
encounter a search too deep error that is not a
true deadlock, but requires you to re-try the transaction the same
way as with a deadlock.
You can enable the configuration option
innodb_checksum_algorithm=crc32
configuration setting to change the
checksum algorithm to a
faster one that scans the block 32 bits at a time rather than 8
bits at a time. When the CRC32 algorithm is enabled, data blocks
that are written to disk by InnoDB contain
different values in their checksum fields than before. This
process could be gradual, with a mix of old and new checksum
values within the same table or database.
For maximum downward compatibility, this setting is off by default. MySQL 5.6.3 and up recognizes either the new or old checksum values for the block as correct when reading the block from disk, ensuring that old data blocks are compatible even when the new algorithm is enabled. If data written with new checksum values is processed by an older level of MySQL, it could be reported as corrupted.
When you set up a new MySQL instance, and can be sure that all the
InnoDB data is created using the CRC32 checksum
algorithm, you can use the setting
innodb_checksum_algorithm=strict_crc32,
which can be faster than the crc32 setting
because it does not do the extra checksum calculations to support
both old and new values.
The innodb_checksum_algorithm
option has other values that allow it to replace the
innodb_checksums option.
innodb_checksum_algorithm=none is the same as
innodb_checksums=OFF.
innodb_checksum_algorithm=innodb is the same as
innodb_checksums=ON. To avoid conflicts, remove
references to innodb_checksums from your
configuration file and MySQL startup scripts. The new option also
accepts values strict_none and
strict_innodb, again offering better
performance in situations where all InnoDB data
in an instance is created with the same checksum algorithm.
The following table illustrates the difference between the
none, innodb, and
crc32 option values, and their
strict_ counterparts. none,
innodb, and crc32 write the
specified type checksum value into each data block, but for
compatibility accept any of the other checksum values when
verifying a block during a read operation. The
strict_ form of each parameter only recognizes
one kind of checksum, which makes verification faster but requires
that all InnoDB data files in an instance be
created under the identical
innodb_checksum_algorithm value.
Table 14.6. Allowed Settings for innodb_checksum_algorithm
| Value | Generated checksum (when writing) | Allowed checksums (when reading) |
|---|---|---|
| none | A constant number. | Any of the checksums generated by none,
innodb, or crc32. |
| innodb | A checksum calculated in software, using the original algorithm from
InnoDB. |
Any of the checksums generated by none,
innodb, or crc32. |
| crc32 | A checksum calculated using the crc32 algorithm,
possibly done with a hardware assist. |
Any of the checksums generated by none,
innodb, or crc32. |
| strict_none | A constant number | Only the checksum generated by none. |
| strict_innodb | A checksum calculated in software, using the original algorithm from
InnoDB. |
Only the checksum generated by innodb. |
| strict_crc32 | A checksum calculated using the crc32 algorithm,
possibly done with a hardware assist. |
Only the checksum generated by crc32. |
After you restart a busy server, there is typically a
warmup period with steadily
increasing throughput, as disk pages that were in the
InnoDB buffer
pool are brought back into memory as the same data is
queried, updated, and so on. Once the buffer pool holds a similar
set of pages as before the restart, many operations are performed
in memory rather than involving disk I/O, and throughput
stabilizes at a high level.
This feature shortens the warmup period by immediately reloading disk pages that were in the buffer pool before the restart, rather than waiting for DML operations to access the corresponding rows. The I/O requests can be performed in large batches, making the overall I/O faster. The page loading happens in the background, and does not delay the database startup.
In addition to saving the buffer pool state at shutdown and restoring it at startup, you can also save or restore the state at any time. For example, you might save the state of the buffer pool after reaching a stable throughput under a steady workload. You might restore the previous buffer pool state after running reports or maintenance jobs that bring data pages into the buffer pool that are only needed during the time period for those operations, or after some other period with a non-typical workload.
Although the buffer pool itself could be many gigabytes in size,
the data that InnoDB saves on disk to restore
the buffer pool is tiny by comparison: just the tablespace and
page IDs necessary to locate the appropriate pages on disk. This
information is derived from the
information_schema table
innodb_buffer_page_lru.
Because the data is cached in and aged out of the buffer pool the same as with regular database operations, there is no problem if the disk pages were updated recently, or if a DML operation involves data that has not yet been loaded. The loading mechanism skips any requested pages that no longer exist.
This feature involves the configuration variables:
and the status variables:
To save the current state of the InnoDB buffer
pool, issue the statement:
SET innodb_buffer_pool_dump_now=ON;
The underlying mechanism involves a background thread that is dispatched to perform the dump and load operations.
By default, the buffer pool state is saved in a file
ib_buffer_pool in the
InnoDB data directory.
Disk pages from compressed tables are loaded into the buffer pool in their compressed form. Uncompression happens as usual when the page contents are accessed in the course of DML operations. Because decompression is a CPU-intensive process, it is more efficient for concurrency to perform that operation in one of the connection threads rather than the single thread that performs the buffer pool restore operation.
Example 14.1. Examples of Dumping and Restoring the InnoDB Buffer Pool
Triggering a dump of the buffer pool manually:
SET innodb_buffer_pool_dump_now=ON;
Specifying that a dump should be taken at shutdown:
SET innodb_buffer_pool_dump_at_shutdown=ON;
Specifying that a dump should be loaded at startup:
mysql> SET innodb_buffer_pool_load_at_startup=ON;
Trigger a load of the buffer pool manually:
mysql> SET innodb_buffer_pool_load_now=ON;
Specify which filename to use for storing the dump to and loading the dump from:
mysql> SET innodb_buffer_pool_filename='filename';
Display progress of dump:
mysql> SHOW STATUS LIKE 'innodb_buffer_pool_dump_status';
or:
SELECT variable_value FROM information_schema.global_status WHERE variable_name = 'INNODB_BUFFER_POOL_DUMP_STATUS';
Outputs any of: not started, Dumping buffer pool 5/7, page 237/2873, Finished at 110505 12:18:02
Display progress of load:
SHOW STATUS LIKE 'innodb_buffer_pool_load_status';
or:
SELECT variable_value FROM information_schema.global_status WHERE variable_name = 'INNODB_BUFFER_POOL_LOAD_STATUS';
Outputs any of: not started, Loaded 123/22301 pages, Finished at 110505 12:23:24
Abort a buffer pool load:
SET innodb_buffer_pool_load_abort=ON;
The new configuration options
innodb_flush_neighbors and
innodb_lru_scan_depth let you
fine-tune certain aspects of the
flushing process for the
InnoDB buffer
pool. These options primarily help write-intensive
workloads. With heavy
DML activity, flushing can fall
behind if it is not aggressive enough, resulting in excessive
memory use in the buffer pool; or, disk writes due to flushing can
saturate your I/O capacity if that mechanism is too aggressive.
The ideal settings depend on your workload, data access patterns,
and storage configuration (for example, whether data is stored on
HDD or
SSD devices).
Plan stability is a desirable goal for your biggest and most important queries. InnoDB has always computed statistics for each InnoDB table to help the optimizer find the most efficient query execution plan. Now you can make these statistics persistent, so that the index usage and join order for a particular query is less likely to change.
The persistent statistics feature relies on the internally managed
tables in the mysql database, named
innodb_table_stats and
innodb_index_stats. These tables are set up
automatically in all install, upgrade, and build-from-source
procedures.
You turn on this feature by enabling the configuration option
innodb_analyze_is_persistent, and
then running the ANALYZE TABLE
statement for each applicable table. You control how much sampling
is done to collect the statistics by setting the configuration
options
innodb_stats_persistent_sample_pages
and
innodb_stats_transient_sample_pages.
With this feature enabled, statistics are only gathered for a
table by the ANALYZE TABLE
statement, not the first time the table is accessed after each
server restart. You might run this statement in your setup scripts
after representative data has been loaded into the table, and run
it periodically after DML operations significantly change the
contents of indexed columns, or on a schedule at times of low
activity.
Because statistics are not automatically gathered when a new
index is created, always run ANALYZE
TABLE after creating a new index when the persistent
statistics mode is enabled.
Formerly, these statistics were cleared on each server restart and after some other operations, and recomputed when the table was next accessed. The statistics are computed using a random sampling technique that could produce different estimates the next time, leading to different choices in the execution plan and thus variations in query performance.
Whenever statistics for a table or index are present in the
InnoDB system tables, they are used by the
optimizer. To revert to the previous method of collecting
statistics that are periodically erased, turn off the
innodb_analyze_is_persistent
setting, then remove the contents of the
innodb_table_stats
andinnodb_index_stats tables in the
mysql database.
In MySQL and InnoDB, multiple threads of execution access shared data structures. InnoDB synchronizes these accesses with its own implementation of mutexes and read/write locks. InnoDB has historically protected the internal state of a read/write lock with an InnoDB mutex. On Unix and Linux platforms, the internal state of an InnoDB mutex is protected by a Pthreads mutex, as in IEEE Std 1003.1c (POSIX.1c).
On many platforms, there is a more efficient way to implement mutexes and read/write locks. Atomic operations can often be used to synchronize the actions of multiple threads more efficiently than Pthreads. Each operation to acquire or release a lock can be done in fewer CPU instructions, and thus result in less wasted time when threads are contending for access to shared data structures. This in turn means greater scalability on multi-core platforms.
InnoDB implements mutexes and read/write locks with the
built-in
functions provided by the GNU Compiler Collection (GCC) for atomic
memory access instead of using the Pthreads approach
previously used. More specifically, an InnoDB that is compiled
with GCC version 4.1.2 or later uses the atomic builtins instead
of a pthread_mutex_t to implement InnoDB
mutexes and read/write locks.
On 32-bit Microsoft Windows, InnoDB has implemented mutexes (but not read/write locks) with hand-written assembler instructions. Beginning with Microsoft Windows 2000, functions for Interlocked Variable Access are available that are similar to the built-in functions provided by GCC. On Windows 2000 and higher, InnoDB makes use of the Interlocked functions. Unlike the old hand-written assembler code, the new implementation supports read/write locks and 64-bit platforms.
Solaris 10 introduced library functions for atomic operations, and InnoDB uses these functions by default. When MySQL is compiled on Solaris 10 with a compiler that does not support the built-in functions provided by the GNU Compiler Collection (GCC) for atomic memory access, InnoDB uses the library functions.
This change improves the scalability of InnoDB on multi-core systems. This feature is enabled out-of-the-box on the platforms where it is supported. You do not have to set any parameter or option to take advantage of the improved performance. On platforms where the GCC, Windows, or Solaris functions for atomic memory access are not available, InnoDB uses the traditional Pthreads method of implementing mutexes and read/write locks.
When MySQL starts, InnoDB writes a message to the log file
indicating whether atomic memory access is used for mutexes, for
mutexes and read/write locks, or neither. If suitable tools are
used to build InnoDB and the target CPU supports the atomic
operations required, InnoDB uses the built-in functions for
mutexing. If, in addition, the compare-and-swap operation can be
used on thread identifiers (pthread_t), then
InnoDB uses the instructions for read-write locks as well.
Note: If you are building from source, ensure that the build process properly takes advantage of your platform capabilities.
For more information about the performance implications of locking, see Section 8.10, “Optimizing Locking Operations”.
When InnoDB was developed, the memory allocators supplied with
operating systems and run-time libraries were often lacking in
performance and scalability. At that time, there were no memory
allocator libraries tuned for multi-core CPUs. Therefore, InnoDB
implemented its own memory allocator in the mem
subsystem. This allocator is guarded by a single mutex, which may
become a bottleneck. InnoDB
also implements a wrapper interface around the system allocator
(malloc and free) that is
likewise guarded by a single mutex.
Today, as multi-core systems have become more widely available,
and as operating systems have matured, significant improvements
have been made in the memory allocators provided with operating
systems. New memory allocators perform better and are more
scalable than they were in the past. The leading high-performance
memory allocators include Hoard,
libumem, mtmalloc,
ptmalloc, tbbmalloc, and
TCMalloc. Most workloads, especially those
where memory is frequently allocated and released (such as
multi-table joins), benefit from using a more highly tuned memory
allocator as opposed to the internal, InnoDB-specific memory
allocator.
You can control whether InnoDB uses its own memory allocator or an
allocator of the operating system, by setting the value of the
system configuration parameter
innodb_use_sys_malloc in the
MySQL option file (my.cnf or
my.ini). If set to ON or
1 (the default), InnoDB uses the
malloc and free functions of
the underlying system rather than manage memory pools itself. This
parameter is not dynamic, and takes effect only when the system is
started. To continue to use the InnoDB memory allocator, set
innodb_use_sys_malloc to
0.
When the InnoDB memory allocator is disabled, InnoDB ignores the
value of the parameter
innodb_additional_mem_pool_size.
The InnoDB memory allocator uses an additional memory pool for
satisfying allocation requests without having to fall back to
the system memory allocator. When the InnoDB memory allocator is
disabled, all such allocation requests are fulfilled by the
system memory allocator.
On Unix-like systems that use dynamic linking, replacing the
memory allocator may be as easy as making the environment
variable LD_PRELOAD or
LD_LIBRARY_PATH point to the dynamic library
that implements the allocator. On other systems, some relinking
may be necessary. Please refer to the documentation of the
memory allocator library of your choice.
Since InnoDB cannot track all memory use when the system memory
allocator is used
(innodb_use_sys_malloc is
ON), the section “BUFFER POOL AND
MEMORY” in the output of the SHOW ENGINE INNODB
STATUS command only includes the buffer pool
statistics in the “Total memory allocated”. Any
memory allocated using the mem subsystem or
using ut_malloc is excluded.
For more information about the performance implications of InnoDB memory usage, see Section 8.9, “Buffering and Caching”.
When INSERT, UPDATE, and
DELETE operations are done to a table, often
the values of indexed columns (particularly the values of
secondary keys) are not in sorted order, requiring substantial I/O
to bring secondary indexes up to date. InnoDB has an
insert buffer that
caches changes to secondary index entries when the relevant
page is not in the
buffer pool, thus
avoiding I/O operations by not reading in the page from the disk.
The buffered changes are merged when the page is loaded to the
buffer pool, and the updated page is later flushed to disk using
the normal mechanism. The InnoDB main thread merges buffered
changes when the server is nearly idle, and during a
slow shutdown.
Because it can result in fewer disk reads and writes, this feature is most valuable for workloads that are I/O-bound, for example applications with a high volume of DML operations such as bulk inserts.
However, the insert buffer occupies a part of the buffer pool, reducing the memory available to cache data pages. If the working set almost fits in the buffer pool, or if your tables have relatively few secondary indexes, it may be useful to disable insert buffering. If the working set entirely fits in the buffer pool, insert buffering does not impose any extra overhead, because it only applies to pages that are not in the buffer pool.
You can control the extent to which InnoDB performs insert
buffering with the system configuration parameter
innodb_change_buffering. You can
turn on and off buffering for inserts, delete operations (when
index records are initially marked for deletion) and purge
operations (when index records are physically deleted). An update
operation is represented as a combination of an insert and a
delete. In MySQL 5.5 and higher, the default value is changed from
inserts to all.
The allowed values of
innodb_change_buffering
are:
all
The default value: buffer inserts, delete-marking operations, and purges.
none
Do not buffer any operations.
inserts
Buffer insert operations.
deletes
Buffer delete-marking operations.
changes
Buffer both inserts and delete-marking.
purges
Buffer the physical deletion operations that happen in the background.
You can set the value of this parameter in the MySQL option file
(my.cnf or my.ini) or change
it dynamically with the SET GLOBAL command,
which requires the SUPER privilege. Changing
the setting affects the buffering of new operations; the merging
of already buffered entries is not affected.
For more information about speeding up INSERT,
UPDATE, and DELETE
statements, see Section 8.2.2, “Optimizing DML Statements”.
If a table fits almost entirely in main memory, the fastest way to perform queries on it is to use hash indexes rather than B-tree lookups. MySQL monitors searches on each index defined for an InnoDB table. If it notices that certain index values are being accessed frequently, it automatically builds an in-memory hash table for that index. Based on the observed pattern of searches, it builds a hash index using a prefix of the index key. The prefix of the key can be any length, and it may be that only some of the values in the B-tree appear in the hash index. Hash indexes are built on demand for those pages of the index that are often accessed.
This adaptive hash index mechanism allows InnoDB to take advantage of large amounts of memory, something typically done only by database systems specifically designed for databases that reside entirely in memory. Normally, the automatic building and use of adaptive hash indexes improves performance. However, sometimes, the read/write lock that guards access to the adaptive hash index may become a source of contention under heavy workloads, such as multiple concurrent joins.
You can monitor the use of the adaptive hash index and the
contention for its use in the “SEMAPHORES” section of
the output of the SHOW ENGINE INNODB STATUS
command. If you see many threads waiting on an RW-latch created in
btr0sea.c, then it might be useful to disable
adaptive hash indexing.
The configuration parameter
innodb_adaptive_hash_index
can be set to disable or enable the adaptive hash index. See
Section 14.2.6.4.4, “Dynamically Changing innodb_adaptive_hash_index”
for details.
For more information about the performance characteristics of hash indexes, see Section 8.3.8, “Comparison of B-Tree and Hash Indexes”.
InnoDB uses operating system threads to process requests from user transactions. (Transactions may issue many requests to InnoDB before they commit or roll back.) On modern operating systems and servers with multi-core processors, where context switching is efficient, most workloads run well without any limit on the number of concurrent threads. Scalability improvements in MySQL 5.5 and up reduce the need to limit the number of concurrently executing threads inside InnoDB.
In situations where it is helpful to minimize context switching between threads, InnoDB can use a number of techniques to limit the number of concurrently executing operating system threads (and thus the number of requests that are processed at any one time). When InnoDB receives a new request from a user session, if the number of threads concurrently executing is at a pre-defined limit, the new request sleeps for a short time before it tries again. A request that cannot be rescheduled after the sleep is put in a first-in/first-out queue and eventually is processed. Threads waiting for locks are not counted in the number of concurrently executing threads.
You can limit the number of concurrent threads by setting the
configuration parameter
innodb_thread_concurrency.
Once the number of executing threads reaches this limit,
additional threads sleep for a number of microseconds, set by the
configuration parameter
innodb_thread_sleep_delay,
before being placed into the queue.
Previously, it required experimentation to find the optimal value
for innodb_thread_sleep_delay, and the optimal
value could change depending on the workload. In MySQL 5.6.3 and
higher, you can set the configuration option
innodb_adaptive_max_sleep_delay
to the highest value you would allow for
innodb_thread_sleep_delay, and InnoDB
automatically adjusts innodb_thread_sleep_delay
up or down depending on the current thread-scheduling activity.
This dynamic adjustment helps the thread scheduling mechanism to
work smoothly during times when the system is lightly loaded and
when it is operating near full capacity.
The default value for
innodb_thread_concurrency
and the implied default limit on the number of concurrent threads
has been changed in various releases of MySQL and InnoDB.
Currently, the default value of
innodb_thread_concurrency is
0, so that by default there is no limit on the
number of concurrently executing threads, as shown in
Table 14.7, “Changes to innodb_thread_concurrency”.
Table 14.7. Changes to innodb_thread_concurrency
| InnoDB Version | MySQL Version | Default value | Default limit of concurrent threads | Value to allow unlimited threads |
|---|---|---|---|---|
| Built-in | Earlier than 5.1.11 | 20 | No limit | 20 or higher |
| Built-in | 5.1.11 and newer | 8 | 8 | 0 |
| InnoDB before 1.0.3 | (corresponding to Plugin) | 8 | 8 | 0 |
| InnoDB 1.0.3 and newer | (corresponding to Plugin) | 0 | No limit | 0 |
Note that InnoDB causes threads to sleep only when the number of
concurrent threads is limited. When there is no limit on the
number of threads, all contend equally to be scheduled. That is,
if innodb_thread_concurrency is
0, the value of
innodb_thread_sleep_delay is
ignored.
When there is a limit on the number of threads, InnoDB reduces
context switching overhead by permitting multiple requests made
during the execution of a single SQL statement to enter InnoDB
without observing the limit set by
innodb_thread_concurrency.
Since an SQL statement (such as a join) may comprise multiple row
operations within InnoDB, InnoDB assigns “tickets”
that allow a thread to be scheduled repeatedly with minimal
overhead.
When a new SQL statement starts, a thread has no tickets, and it
must observe
innodb_thread_concurrency.
Once the thread is entitled to enter InnoDB, it is assigned a
number of tickets that it can use for subsequently entering
InnoDB. If the tickets run out,
innodb_thread_concurrency
is observed again and further tickets are assigned. The number of
tickets to assign is specified by the global option
innodb_concurrency_tickets,
which is 500 by default. A thread that is waiting for a lock is
given one ticket once the lock becomes available.
The correct values of these variables depend on your environment and workload. Try a range of different values to determine what value works for your applications. Before limiting the number of concurrently executing threads, review configuration options that may improve the performance of InnoDB on multi-core and multi-processor computers, such as innodb_use_sys_malloc and innodb_adaptive_hash_index.
For general performance information about MySQL thread handling, see Section 8.11.5.1, “How MySQL Uses Threads for Client Connections”.
A read-ahead request is an I/O request to prefetch multiple pages in the buffer pool asynchronously, in anticipation that these pages will be needed soon. The requests bring in all the pages in one extent. InnoDB uses two read-ahead algorithms to improve I/O performance:
Linear read-ahead is a
technique that predicts what pages might be needed soon based on
pages in the buffer pool being accessed sequentially. You control
when InnoDB performs a read-ahead operation by adjusting the
number of sequential page accesses required to trigger an
asynchronous read request, using the configuration parameter
innodb_read_ahead_threshold.
Before this parameter was added, InnoDB would only calculate
whether to issue an asynchronous prefetch request for the entire
next extent when it read in the last page of the current extent.
The new configuration parameter
innodb_read_ahead_threshold
controls how sensitive InnoDB is in detecting patterns of
sequential page access. If the number of pages read sequentially
from an extent is greater than or equal to
innodb_read_ahead_threshold,
InnoDB initiates an asynchronous read-ahead operation of the
entire following extent. It can be set to any value from 0-64. The
default value is 56. The higher the value, the more strict the
access pattern check. For example, if you set the value to 48,
InnoDB triggers a linear read-ahead request only when 48 pages in
the current extent have been accessed sequentially. If the value
is 8, InnoDB would trigger an asynchronous read-ahead even if as
few as 8 pages in the extent were accessed sequentially. You can
set the value of this parameter in the MySQL
configuration file,
or change it dynamically with the SET GLOBAL
command, which requires the SUPER privilege.
Random read-ahead is a
technique that predicts when pages might be needed soon based on
pages already in the buffer pool, regardless of the order in which
those pages were read. If 13 consecutive pages from the same
extent are found in the buffer pool, InnoDB asynchronously issues
a request to prefetch the remaining pages of the extent. This
feature was initially turned off in MySQL 5.5. It is available
once again starting in MySQL 5.1.59 and 5.5.16 and higher, turned
off by default. To enable this feature, set the configuration
variable
innodb_random_read_ahead.
The SHOW ENGINE INNODB STATUS command displays
statistics to help you evaluate the effectiveness of the
read-ahead algorithm. With the return of random read-ahead in
MySQL 5.6, the SHOW ENGINE INNODB STATUS
command once again includes
Innodb_buffer_pool_read_ahead_rnd.
Innodb_buffer_pool_read_ahead keeps its current
name. (In earlier releases, it was listed as
Innodb_buffer_pool_read_ahead_seq.) See
Section 14.2.6.10, “More Read-Ahead Statistics” for more
information.
For more information about I/O performance, see
Section 8.5.7, “Optimizing InnoDB Disk I/O” and
Section 8.11.3, “Optimizing Disk I/O”.
InnoDB uses background threads
to service various types of I/O requests. You can configure the
number of background threads that service read and write I/O on
data pages, using the configuration parameters
innodb_read_io_threads and
innodb_write_io_threads. These
parameters signify the number of background threads used for read
and write requests respectively. They are effective on all
supported platforms. You can set the value of these parameters in
the MySQL option file (my.cnf or
my.ini); you cannot change them dynamically.
The default value for these parameters is 4 and
the permissible values range from 1-64.
The purpose of this change is to make InnoDB more scalable on high
end systems. Each background thread can handle up to 256 pending
I/O requests. A major source of background I/O is
theread-ahead
requests. InnoDB tries to balance the load of incoming requests in
such way that most of the background threads share work equally.
InnoDB also attempts to allocate read requests from the same
extent to the same thread to increase the chances of coalescing
the requests together. If you have a high end I/O subsystem and
you see more than 64 ×
innodb_read_io_threads pending
read requests in SHOW ENGINE INNODB STATUS, you
might gain by increasing the value of
innodb_read_io_threads.
For more information about InnoDB I/O performance, see
Section 8.5.7, “Optimizing InnoDB Disk I/O”.
Starting in InnoDB 1.1 with MySQL 5.5, the
asynchronous I/O
capability that InnoDB has had on Windows systems is now available
on Linux systems. (Other Unix-like systems continue to use
synchronous I/O calls.) This feature improves the scalability of
heavily I/O-bound systems, which typically show many pending
reads/writes in the output of the command SHOW ENGINE
INNODB STATUS\G.
Running with a large number of InnoDB I/O
threads, and especially running multiple such instances on the
same server machine, can exceed capacity limits on Linux systems.
In this case, you can fix the error:
EAGAIN: The specified maxevents exceeds the user's limit of available events.
by writing a higher limit to
/proc/sys/fs/aio-max-nr.
In general, if a problem with the asynchronous I/O subsystem in
the OS prevents InnoDB from starting, set the option
innodb_use_native_aio=0 in the
configuration file. This new configuration option applies to Linux
systems only, and cannot be changed once the server is running.
For more information about InnoDB I/O performance, see
Section 8.5.7, “Optimizing InnoDB Disk I/O”.
InnoDB, like any other ACID-compliant database engine, flushes the redo log of a transaction before it is committed. Historically, InnoDB used group commit functionality to group multiple such flush requests together to avoid one flush for each commit. With group commit, InnoDB issues a single write to the log file to perform the commit action for multiple user transactions that commit at about the same time, significantly improving throughput.
Group commit in InnoDB worked until MySQL 4.x, and works once again with MySQL 5.1 with the InnoDB Plugin, and MySQL 5.5 and higher. The introduction of support for the distributed transactions and Two Phase Commit (2PC) in MySQL 5.0 interfered with the InnoDB group commit functionality. This issue is now resolved.
The group commit functionality inside InnoDB works with the Two
Phase Commit protocol in MySQL. Re-enabling of the group commit
functionality fully ensures that the ordering of commit in the
MySQL binlog and the InnoDB logfile is the same as it was before.
It means it is totally safe to use the
MySQL Enterprise Backup product with InnoDB 1.0.4 (that
is, the InnoDB Plugin with MySQL 5.1) and above. When the binlog
is enabled, you typically also set the configuration option
sync_binlog=0, because group commit for the
binary log is only supported if it is set to 0.
Group commit is transparent; you do not need to do anything to take advantage of this significant performance improvement.
For more information about performance of
COMMIT and other transactional operations, see
Section 8.5.2, “Optimizing InnoDB Transaction Management”.
The master thread in InnoDB is a thread that performs various tasks in the background. Most of these tasks are I/O related, such as flushing dirty pages from the buffer pool or writing changes from the insert buffer to the appropriate secondary indexes. The master thread attempts to perform these tasks in a way that does not adversely affect the normal working of the server. It tries to estimate the free I/O bandwidth available and tune its activities to take advantage of this free capacity. Historically, InnoDB has used a hard coded value of 100 IOPs (input/output operations per second) as the total I/O capacity of the server.
The parameter
innodb_io_capacity
indicates the overall I/O capacity available to InnoDB. This
parameter should be set to approximately the number of I/O
operations that the system can perform per second. The value
depends on your system configuration. When
innodb_io_capacity is set,
the master threads estimates the I/O bandwidth available for
background tasks based on the set value. Setting the value to
100 reverts to the old behavior.
You can set the value of
innodb_io_capacity to any
number 100 or greater. The default value is
200, reflecting that the performance of typical
modern I/O devices is higher than in the early days of MySQL.
Typically, values around the previous default of 100 are
appropriate for consumer-level storage devices, such as hard
drives up to 7200 RPMs. Faster hard drives, RAID configurations,
and SSDs benefit from higher values.
You can set the value of this parameter in the MySQL option file
(my.cnf or my.ini) or change
it dynamically with the SET GLOBAL command,
which requires the SUPER privilege.
Formerly, the InnoDB master thread also
performed any needed purge
operations. In MySQL 5.6.5 and higher, those I/O operations are
moved to other background threads, whose number is controlled by
the innodb_purge_threads
configuration option.
For more information about InnoDB I/O performance, see
Section 8.5.7, “Optimizing InnoDB Disk I/O”.
InnoDB performs certain tasks in the background, including
flushing of
dirty pages (those
pages that have been changed but are not yet written to the
database files) from the
buffer pool, a task
performed by the master
thread. Currently, InnoDB aggressively flushes buffer pool
pages if the percentage of dirty pages in the buffer pool exceeds
innodb_max_dirty_pages_pct.
InnoDB uses a new algorithm to estimate the required rate of flushing, based on the speed of redo log generation and the current rate of flushing. The intent is to smooth overall performance by ensuring that buffer flush activity keeps up with the need to keep the buffer pool “clean”. Automatically adjusting the rate of flushing can help to avoid steep dips in throughput, when excessive buffer pool flushing limits the I/O capacity available for ordinary read and write activity.
InnoDB uses its log files in a circular fashion. Before reusing a
portion of a log file, InnoDB flushes to disk all dirty buffer
pool pages whose redo entries are contained in that portion of the
log file, a process known as a
sharp checkpoint. If
a workload is write-intensive, it generates a lot of redo
information, all written to the log file. If all available space
in the log files is used up, a sharp checkpoint occurs, causing a
temporary reduction in throughput. This situation can happen even
though innodb_max_dirty_pages_pct
is not reached.
InnoDB uses a heuristic-based algorithm to avoid such a scenario, by measuring the number of dirty pages in the buffer pool and the rate at which redo is being generated. Based on these numbers, InnoDB decides how many dirty pages to flush from the buffer pool each second. This self-adapting algorithm is able to deal with sudden changes in the workload.
Internal benchmarking has also shown that this algorithm not only maintains throughput over time, but can also improve overall throughput significantly.
Because adaptive flushing is a new feature that can significantly
affect the I/O pattern of a workload, a new configuration
parameter lets you turn off this feature. The default value of the
boolean parameter
innodb_adaptive_flushing is
TRUE, enabling the new algorithm. You can set
the value of this parameter in the MySQL option file
(my.cnf or my.ini) or change
it dynamically with the SET GLOBAL command,
which requires the SUPER privilege.
For more information about InnoDB I/O performance, see
Section 8.5.7, “Optimizing InnoDB Disk I/O”.
Synchronization inside InnoDB frequently involves the use of
spin loops: while waiting,
InnoDB executes a tight loop of instructions repeatedly to avoid
having the InnoDB process and
threads be rescheduled by the
operating system. If the spin loops are executed too quickly,
system resources are wasted, imposing a performance penalty on
transaction throughput. Most modern processors implement the
PAUSE instruction for use in spin loops, so the
processor can be more efficient.
InnoDB uses a PAUSE instruction in its spin
loops on all platforms where such an instruction is available.
This technique increases overall performance with CPU-bound
workloads, and has the added benefit of minimizing power
consumption during the execution of the spin loops.
You do not have to do anything to take advantage of this performance improvement.
For performance considerations for InnoDB locking operations, see Section 8.10, “Optimizing Locking Operations”.
Many InnoDB mutexes and rw-locks are reserved for a short time. On a multi-core system, it can be more efficient for a thread to continuously check if it can acquire a mutex or rw-lock for a while before sleeping. If the mutex or rw-lock becomes available during this polling period, the thread can continue immediately, in the same time slice. However, too-frequent polling by multiple threads of a shared object can cause “cache ping pong”, different processors invalidating portions of each others' cache. InnoDB minimizes this issue by waiting a random time between subsequent polls. The delay is implemented as a busy loop.
You can control the maximum delay between testing a mutex or
rw-lock using the parameter
innodb_spin_wait_delay. The
duration of the delay loop depends on the C compiler and the
target processor. (In the 100MHz Pentium era, the unit of delay
was one microsecond.) On a system where all processor cores share
a fast cache memory, you might reduce the maximum delay or disable
the busy loop altogether by setting
innodb_spin_wait_delay=0. On a system with
multiple processor chips, the effect of cache invalidation can be
more significant and you might increase the maximum delay.
The default value of
innodb_spin_wait_delay is
6. The spin wait delay is a dynamic global
parameter that you can specify in the MySQL option file
(my.cnf or my.ini) or change
at runtime with the command SET GLOBAL
innodb_spin_wait_delay=,
where delay is the
desired maximum delay. Changing the setting requires the
delaySUPER privilege.
For performance considerations for InnoDB locking operations, see Section 8.10, “Optimizing Locking Operations”.
Rather than using a strictly LRU algorithm, InnoDB uses a technique to minimize the amount of data that is brought into the buffer pool and never accessed again. The goal is to make sure that frequently accessed (“hot”) pages remain in the buffer pool, even as read-ahead and full table scans bring in new blocks that might or might not be accessed afterward.
Newly read blocks are inserted into the middle of the list
representing the buffer pool. of the LRU list. All newly read
pages are inserted at a location that by default is
3/8 from the tail of the LRU list. The pages
are moved to the front of the list (the most-recently used end)
when they are accessed in the buffer pool for the first time. Thus
pages that are never accessed never make it to the front portion
of the LRU list, and “age out” sooner than with a
strict LRU approach. This arrangement divides the LRU list into
two segments, where the pages downstream of the insertion point
are considered “old” and are desirable victims for
LRU eviction.
For an explanation of the inner workings of the InnoDB buffer pool
and the specifics of its LRU replacement algorithm, see
Section 8.9.1, “The InnoDB Buffer Pool”.
You can control the insertion point in the LRU list, and choose
whether InnoDB applies the same optimization to blocks brought
into the buffer pool by table or index scans. The configuration
parameter
innodb_old_blocks_pct
controls the percentage of “old” blocks in the LRU
list. The default value of
innodb_old_blocks_pct is
37, corresponding to the original fixed ratio
of 3/8. The value range is 5 (new pages in the
buffer pool age out very quickly) to 95 (only
5% of the buffer pool is reserved for hot pages, making the
algorithm close to the familiar LRU strategy).
The optimization that keeps the buffer pool from being churned by
read-ahead can avoid similar problems due to table or index scans.
In these scans, a data page is typically accessed a few times in
quick succession and is never touched again. The configuration
parameter innodb_old_blocks_time
specifies the time window (in milliseconds) after the first access
to a page during which it can be accessed without being moved to
the front (most-recently used end) of the LRU list. The default
value of innodb_old_blocks_time
is 0, corresponding to the original behavior of
moving a page to the most-recently used end of the buffer pool
list when it is first accessed in the buffer pool. Increasing this
value makes more and more blocks likely to age out faster from the
buffer pool.
Both the new parameters
innodb_old_blocks_pct and
innodb_old_blocks_time are
dynamic, global and can be specified in the MySQL option file
(my.cnf or my.ini) or
changed at runtime with the SET GLOBAL command.
Changing the setting requires the SUPER
privilege.
To help you gauge the effect of setting these parameters, the
SHOW ENGINE INNODB STATUS command reports
additional statistics. The BUFFER POOL AND
MEMORY section now looks like:
Total memory allocated 1107296256; in additional pool allocated 0 Dictionary memory allocated 80360 Buffer pool size 65535 Free buffers 0 Database pages 63920 Old database pages 23600 Modified db pages 34969 Pending reads 32 Pending writes: LRU 0, flush list 0, single page 0 Pages made young 414946, not young 2930673 1274.75 youngs/s, 16521.90 non-youngs/s Pages read 486005, created 3178, written 160585 2132.37 reads/s, 3.40 creates/s, 323.74 writes/s Buffer pool hit rate 950 / 1000, young-making rate 30 / 1000 not 392 / 1000 Pages read ahead 1510.10/s, evicted without access 0.00/s LRU len: 63920, unzip_LRU len: 0 I/O sum[43690]:cur[221], unzip sum[0]:cur[0]
Old database pages is the number of pages
in the “old” segment of the LRU list.
Pages made young and not
young is the total number of “old”
pages that have been made young or not respectively.
youngs/s and non-young/s
is the rate at which page accesses to the “old”
pages have resulted in making such pages young or otherwise
respectively since the last invocation of the command.
young-making rate and
not provides the same rate but in terms of
overall buffer pool accesses instead of accesses just to the
“old” pages.
Because the effects of these parameters can vary widely based on your hardware configuration, your data, and the details of your workload, always benchmark to verify the effectiveness before changing these settings in any performance-critical or production environment.
In mixed workloads where most of the activity is OLTP type with
periodic batch reporting queries which result in large scans,
setting the value of
innodb_old_blocks_time
during the batch runs can help keep the working set of the normal
workload in the buffer pool.
When scanning large tables that cannot fit entirely in the buffer
pool, setting
innodb_old_blocks_pct to a
small value keeps the data that is only read once from consuming a
significant portion of the buffer pool. For example, setting
innodb_old_blocks_pct=5 restricts this data
that is only read once to 5% of the buffer pool.
When scanning small tables that do fit into memory, there is less
overhead for moving pages around within the buffer pool, so you
can leave innodb_old_blocks_pct
at its default value, or even higher, such as
innodb_old_blocks_pct=50.
The effect of the
innodb_old_blocks_time
parameter is harder to predict than the
innodb_old_blocks_pct
parameter, is relatively small, and varies more with the workload.
To arrive at an optimal value, conduct your own benchmarks if the
performance improvement from adjusting
innodb_old_blocks_pct is
not sufficient.
For more information about the InnoDB buffer pool, see
Section 8.9.1, “The InnoDB Buffer Pool”.
A number of optimizations speed up certain steps of the recovery that happens on the next startup after a crash. In particular, scanning the redo log and applying the redo log are faster than in MySQL 5.1 and earlier, due to improved algorithms for memory management. You do not need to take any actions to take advantage of this performance enhancement. If you kept the size of your redo log files artificially low because recovery took a long time, you can consider increasing the file size.
For more information about InnoDB recovery, see
Section 14.2.4.14, “The InnoDB Recovery Process”.
Starting with InnoDB 1.1 with MySQL 5.5, you can profile certain internal InnoDB operations using the MySQL Performance Schema feature. This type of tuning is primarily for expert users, those who push the limits of MySQL performance, read the MySQL source code, and evaluate optimization strategies to overcome performance bottlenecks. DBAs can also use this feature for capacity planning, to see whether their typical workload encounters any performance bottlenecks with a particular combination of CPU, RAM, and disk storage; and if so, to judge whether performance can be improved by increasing the capacity of some part of the system.
To use this feature to examine InnoDB performance:
You must be running MySQL 5.5 or higher. You must build the
database server from source, enabling the Performance Schema
feature by building with the
--with-perfschema option. Since the
Performance Schema feature introduces some performance
overhead, you should use it on a test or development system
rather than on a production system.
You must be running InnoDB 1.1 or higher.
You must be generally familiar with how to use the
Performance Schema
feature, for example to query tables in the
performance_schema database.
Examine the following kinds of InnoDB objects by querying the
appropriate performance_schema tables. The
items associated with InnoDB all contain the substring
innodb in the NAME
column.
For the definitions of the *_instances
tables, see
Section 20.9.2, “Performance Schema Instance Tables”. For the
definitions of the *_summary_* tables, see
Section 20.9.7, “Performance Schema Summary Tables”. For the
definition of the thread table, see
Section 20.9.8, “Performance Schema Miscellaneous Tables”. For
the definition of the *_current_* and
*_history_* tables, see
Section 20.9.3, “Performance Schema Wait Event Tables”.
Mutexes in the
mutex_instances table. (Mutexes and
RW-locks related to the InnoDB buffer
pool are not included in this coverage; the same applies
to the output of the SHOW ENGINE INNODB
MUTEX command.)
RW-locks in the
rwlock_instances table.
RW-locks in the rwlock_instances table.
File I/O operations in the
file_instances,
file_summary_by_event_name, and
file_summary_by_instance tables.
Threads in the
PROCESSLIST table.
During performance testing, examine the performance data in
the events_waits_current and
events_waits_history_long tables. If you
are interested especially in InnoDB-related objects, use the
clause where name like "%innodb%" to see
just those entries; otherwise, examine the performance
statistics for the overall MySQL server.
You must be running MySQL 5.5, with the Performance Schema
enabled by building with the
--with-perfschema build option.
For more information about the MySQL Performance Schema, see Chapter 20, MySQL Performance Schema.
This performance enhancement is primarily useful for people with a
large buffer pool size,
typically in the multi-gigabyte range. To take advantage of this
speedup, you must set the new
innodb_buffer_pool_instances
configuration option, and you might also adjust the
innodb_buffer_pool_size value.
When the InnoDB buffer pool is large, many data requests can be satisfied by retrieving from memory. You might encounter bottlenecks from multiple threads trying to access the buffer pool at once. Starting in InnoDB 1.1 and MySQL 5.5, you can enable multiple buffer pools to minimize this contention. Each page that is stored in or read from the buffer pool is assigned to one of the buffer pools randomly, using a hashing function. Each buffer pool manages its own free lists, flush lists, LRUs, and all other data structures connected to a buffer pool, and is protected by its own buffer pool mutex.
To enable this feature, set the
innodb_buffer_pool_instances configuration
option to a value greater than 1 (the default) up to 64 (the
maximum). This option only takes effect when you set the
innodb_buffer_pool_size to a size of 1 gigabyte
or more. The total size you specify is divided up among all the
buffer pools. We recommend specifying a combination of
innodb_buffer_pool_instances and
innodb_buffer_pool_size so that each buffer
pool instance is at least 1 gigabyte.
For more information about the InnoDB buffer pool, see
Section 8.9.1, “The InnoDB Buffer Pool”.
Starting in InnoDB 1.1 with MySQL 5.5, the limit on concurrent transactions is greatly expanded, removing a bottleneck with the InnoDB rollback segment that affected high-capacity systems. The limit applies to concurrent transactions that change any data; read-only transactions do not count against that maximum.
The single rollback segment is now divided into 128 segments, each of which can support up to 1023 transactions that perform writes, for a total of approximately 128K concurrent transactions. The original transaction limit was 1023.
Each transaction is assigned to one of the rollback segments, and remains tied to that rollback segment for the duration. This enhancement improves both scalability (higher number of concurrent transactions) and performance (less contention when different transactions access the rollback segments).
To take advantage of this feature, you do not need to create any new database or tables, or reconfigure anything. You must do a slow shutdown before upgrading from MySQL 5.1 or earlier, or some time afterward. InnoDB makes the required changes inside the system tablespace automatically, the first time you restart after performing a slow shutdown.
If your workload was not constrained by the original limit of 1023
concurrent transactions, you can reduce the number of rollback
segments used within a MySQL instance or within a session by
setting the configuration option
innodb_rollback_segments.
For more information about performance of InnoDB under high
transactional load, see
Section 8.5.2, “Optimizing InnoDB Transaction Management”.
The purge operations (a type of garbage collection) that InnoDB performs automatically is now done in one or more separate threads, rather than as part of the master thread. This change improves scalability, because the main database operations run independently from maintenance work happening in the background.
To control this feature, increase the value of the configuration
option
innodb_purge_threads=.
If DML action is concentrated on a single table or a few tables,
keep the setting low so that the threads do not contend with each
other for access to the busy tables. If DML operations are spread
across many tables, increase the setting. Its maximum is 32.
n
There is another related configuration option,
innodb_purge_batch_size with a default of 20
and maximum of 5000. This option is mainly intended for
experimentation and tuning of purge operations, and should not be
interesting to typical users.
For more information about InnoDB I/O performance, see
Section 8.5.7, “Optimizing InnoDB Disk I/O”.
This is another performance improvement that comes for free, with no user action or configuration needed. The details here are intended for performance experts who delve into the InnoDB source code, or interpret reports with keywords such as “mutex” and “log_sys”.
The mutex known as the log sys
mutex has historically done double duty, controlling access to
internal data structures related to log records and the
LSN, as well as pages in the
buffer pool that are
changed when a
mini-transaction is
committed. Starting in InnoDB 1.1 with MySQL 5.5, these two kinds
of operations are protected by separate mutexes, with a new
log_buf mutex controlling writes to buffer pool
pages due to mini-transactions.
For performance considerations for InnoDB locking operations, see Section 8.10, “Optimizing Locking Operations”.
Starting with InnoDB 1.1 with MySQL 5.5, concurrent access to the buffer pool is faster. Operations involving the flush list, a data structure related to the buffer pool, are now controlled by a separate mutex and do not block access to the buffer pool. You do not need to configure anything to take advantage of this speedup; it is fully automatic.
For more information about the InnoDB buffer pool, see
Section 8.9.1, “The InnoDB Buffer Pool”.
The mutex controlling concurrent
access to the InnoDB kernel is now divided into
separate mutexes and rw-locks
to reduce contention. You do not need to configure anything to
take advantage of this speedup; it is fully automatic.
To ease the memory load on systems with huge numbers of tables,
InnoDB now frees up the memory associated with an opened table,
using an LRU algorithm to select tables that have gone the longest
without being accessed. To reserve more memory to hold metadata
for open InnoDB tables, increase the value of the
table_definition_cache
configuration option. InnoDB treats this value as a “soft
limit”. The actual number of tables with cached metadata
could be higher, because metadata for InnoDB system tables, and
parent and child tables in foreign key relationships, is never
evicted from memory.
This feature extends the file-per-table mode enabled by the
innodb_file_per_table
configuration option, allowing more flexibility in how the
.ibd files are placed, exported, and
restored. We characterize this as a performance enhancement
because it solves the common customer request to put data from
different tables onto different storage devices, for best
price/performance depending on the access patterns of the data.
For example, tables with high levels of random reads and writes
might be placed on an SSD device,
while less-often-accessed data or data processed with large
batches of sequential I/O might be placed on an
HDD device.
As discussed in Section 15.6, “Using MySQL with memcached”, the memcached daemon is frequently used as an in-memory caching layer in front of a MySQL database server. This technique allows the data to be stored in MySQL for reliability and consistency, while the latest data values can be retrieved by an application using a single API call, without the overhead of parsing and constructing query optimization plans that comes with using SQL.
This feature combines the best of both worlds:
Data that is written to the memcached cache can be transparently written to an InnoDB table, without going through the MySQL SQL layer. You can control the frequency of writes to achieve higher raw performance for updates to non-critical data, where you could afford to lose some number of recent updates in the event of a crash.
When requested data is missing from the cache, it can be transparently queried from an InnoDB table, without going through the MySQL SQL layer. Subsequent requests for the same data will be served from the cache.
InnoDB can handle composing and decomposing multiple column
values into a single memcached item value,
reducing the amount of string parsing and concatenation
required in your application. For example, you might store a
string value 2|4|6|8 in the
memcached cache, and InnoDB splits that
value based on a separator character, then stores the result
into four numeric columns.
This feature is a continuation of the “Fast Index
Creation” feature introduced in
Section 14.2.2.6, “Fast Index Creation in the InnoDB Storage Engine”. Now you can perform other
kinds of DDL operations on InnoDB
tables online: that is, with
minimal delay for operations on that table, and without rebuilding
the entire table. This enhancement improves responsiveness and
availability in busy production environments, where making a table
unavailable for seconds or minutes whenever its column definitions
change is not practical.
The DDL operations enhanced by this feature are these variations
on the ALTER TABLE statement:
Create secondary
indexes: CREATE INDEX
or
name ON
table
(col_list)ALTER TABLE
table ADD
INDEX name
(col_list)
Drop secondary
indexes: DROP INDEX
or name ON
table;ALTER
TABLE
table DROP INDEX
name
Creating and dropping secondary indexes on
InnoDB tables has avoided the table-copying
behavior since the days of MySQL 5.1 with the
InnoDB Plugin. Now, the table remains
available for read and write operations while the index is
being created or dropped. The CREATE
TABLE or DROP TABLE
statement only finishes after all transactions that are
modifying the table are completed, so that the initial state
of the index reflects the most recent contents of the table.
Previously, modifying the table while an index was being created or dropped typically resulted in a deadlock that cancelled the insert, update, or delete statement on the table.
Changing the
auto-increment
value for a column: ALTER TABLE
table
AUTO_INCREMENT=next_value;
Especially in a distributed system using replication or sharding, you sometimes reset the auto-increment counter for a table to a specific value. The next row inserted into the table uses the specified value for its auto-increment column. You might also use this technique in a data warehousing environment where you periodically empty all the tables and reload them, and you can restart the auto-increment sequence from 1.
Dropping a foreign
key constraint: ALTER TABLE
tbl DROP FOREIGN KEY
fk_name
Currently, online DDL only includes the
DROP operation for foreign keys, not
ADD to create foreign keys.
If you do not know the names of the foreign key constraints on
a particular table, issue the following statement and find the
constraint name in the CONSTRAINT clause
for each foreign key:
show create table table\G
Or, query the
information_schema.table_constraints
table and use the constraint_name and
constraint_type columns to identify the
foreign key names.
As a consequence of this enhancement, you can now also drop a foreign key and its associated index in a single statement, which previously required separate statements in a strict order:
ALTER TABLEtableDROP FOREIGN KEYconstraint, DROP INDEXindex;
Renaming a column: ALTER TABLE
tbl CHANGE
old_col_name
new_col_name
datatype
When you keep the same data type and only change the column name, this operation can always be performed online. As part of this enhancement, you can now rename a column that is part of a foreign key constraint, which was not allowed before.
As your database schema evolves with new columns, data types,
constraints, indexes, and so on, keep your
CREATE TABLE statements up to
date with the latest table definitions. Even with the
performance improvements of online DDL, it is more efficient to
create stable database structures at the beginning, rather than
creating part of the schema and then issuing
ALTER TABLE statements afterward.
The main exception to this guideline is for secondary indexes on tables with large numbers of rows. It is typically most efficient to create the table with all details specified except the secondary indexes, load the data, then create the secondary indexes.
Whatever sequence of CREATE
TABLE, CREATE INDEX,
ALTER TABLE, and similar
statements went into putting a table together, you can capture
the SQL needed to reconstruct the current form of the table by
issuing the statement SHOW CREATE TABLE
(uppercase
table\G\G required for tidy formatting). This output
shows clauses such as numeric precision, NOT
NULL, and CHARACTER SET that are
sometimes added behind the scenes, and you might otherwise leave
out when cloning the table on a new system or setting up foreign
key columns with identical type.
Avoiding a table copy during DDL improves several aspects of MySQL operation, such as performance, concurrency, availability, and scalability:
By avoiding the disk I/O and CPU cycles to rebuild the table, you minimize the overall load on the database and maintain good performance and high throughput during the DDL operation.
Because the DDL operation completes in less time, there is a shorter period when queries and DML operations on that table are blocked, making your application more responsive.
Because less data is read into the buffer pool, you avoid purging frequently accessed data from the memory cache, which typically causes a temporary performance dip after a DDL operation.
Because there is a shorter period when queries and DML operations queue up waiting for the DDL to finish, there is less locking and waiting for other resources all throughout the MySQL server. Reducing this type of overhead leads to greater scalability, even for operations not involving the table being altered.
To judge the relative performance of online DDL operations, you
can run such operations on a big InnoDB table
using current and earlier versions of MySQL. You can also run all
the performance tests under the latest MySQL version, simulating
the previous DDL behavior for the “before” results,
by setting the old_alter_table
system variable. Issue the statement set
old_alter_table=1 in the session, and measure DDL
performance to record the “before” figures. Then
set old_alter_table=0 to re-enable the newer,
faster behavior, and run the DDL operations again to record the
“after” figures.
For a basic idea of whether a DDL operation does its changes in-place or performs a table copy, look at the “rows affected” value displayed after the command finishes. For example, here are lines you might see after doing different types of DDL operations:
Changing the default value of a column (super-fast, does not affect the table data at all):
Query OK, 0 rows affected (0.07 sec)
Adding an index (takes time, but 0 rows
affected shows that the table is not copied):
Query OK, 0 rows affected (21.42 sec)
Changing the data type of a column (takes substantial time and does require rebuilding all the rows of the table):
Query OK, 1671168 rows affected (1 min 35.54 sec)
For example, before running a DDL operation on a big table, you might check whether the operation will be fast or slow as follows:
Clone the table structure.
Populate the cloned table with a tiny amount of data.
Run the DDL operation on the cloned table.
Check whether the “rows affected” value is zero or not. A non-zero value means the operation will require rebuilding the entire table, which might require special planning. For example, you might do the DDL operation during a period of scheduled downtime, or on each replication slave server one at a time.
For a deeper understanding of the reduction in MySQL processing,
examine the PERFORMANCE_SCHEMA and
INFORMATION_SCHEMA tables related to
InnoDB before and after DDL operations, to see
the number of physical reads, writes, memory allocations, and so
on.
You can specify the new “in-place” DDL mechanism, or
the old copy the table mechanism, for a
particular statement by using the ALGORITHM and
LOCK clauses of the ALTER
TABLE statement. For example:
To compare performance, you could run one statement with
ALGORITHM=INPLACE and another with
ALGORITHM=COPY, as an alternative to
setting the old_alter_table
configuration option.
To avoid the chance of tying up the server for a long time by
accidentally running an ALTER
TABLE that did not meet all the requirements for
online DDL, you could include
ALGORITHM=INPLACE so the statement halts
immediately if it cannot use the in-place mechanism.
To avoid accidentally making the table unavailable for reads,
writes, or both, you could specify a clause on the
ALTER TABLE statement such as
LOCK=NONE (allow both reads and writes) or
LOCK=SHARED (allow reads). The operation
halts immediately if the requested level of concurrency is not
available.
Historically, the MySQL server and InnoDB have
each kept their own metadata about table and index structures. The
MySQL server stores this information in
.frm files that are not
protected by a transactional mechanism, while
InnoDB has its own
data dictionary as
part of the system
tablespace. If a DDL operation was interrupted by a crash
or other unexpected event partway through, the metadata could be
left inconsistent between these two locations, causing problems
such as startup errors or inability to access the table that was
being altered. Now that InnoDB is the default
storage engine, addressing such issues is a high priority. These
enhancements to DDL operations reduce the window of opportunity
for such issues to occur.
Here are code examples showing some operations whose performance, concurrency, and scalability are improved by this new feature.
Example 14.2. Schema Setup Code for Online DDL Experiments
Here is the code that sets up the initial tables used in these demonstrations:
/* Setup code for the online DDL demonstration: - Set up some config variables. - Create 2 tables that are clones of one of the INFORMATION_SCHEMA tables that always has some data. The "small" table has a couple of thousand rows. For the "big" table, keep doubling the data until it reaches over a million rows. - Set up a primary key for the sample tables, since we are demonstrating InnoDB aspects. */ set autocommit = 0; set foreign_key_checks = 1; set global innodb_file_per_table = 1; set old_alter_table=0; prompt mysql: use test; \! echo "Setting up 'small' table:" drop table if exists small_table; create table small_table as select * from information_schema.columns; alter table small_table add id int unsigned not null primary key auto_increment; select count(id) from small_table; select count(id) from small_table; \! echo "Setting up 'big' table:" drop table if exists big_table; create table big_table as select * from information_schema.columns; show create table big_table\G insert into big_table select * from big_table; insert into big_table select * from big_table; insert into big_table select * from big_table; insert into big_table select * from big_table; insert into big_table select * from big_table; insert into big_table select * from big_table; insert into big_table select * from big_table; insert into big_table select * from big_table; insert into big_table select * from big_table; insert into big_table select * from big_table; commit; alter table big_table add id int unsigned not null primary key auto_increment; select count(id) from big_table; select count(id) from big_table;
Running this code gives this output, condensed for brevity and with the most important points bolded:
Setting up 'small' table:
Query OK, 0 rows affected (0.01 sec)
Query OK, 1678 rows affected (0.13 sec)
Records: 1678 Duplicates: 0 Warnings: 0
Query OK, 1678 rows affected (0.07 sec)
Records: 1678 Duplicates: 0 Warnings: 0
+-----------+
| count(id) |
+-----------+
| 1678 |
+-----------+
1 row in set (0.00 sec)
+-----------+
| count(id) |
+-----------+
| 1678 |
+-----------+
1 row in set (0.00 sec)
Setting up 'big' table:
Query OK, 0 rows affected (0.16 sec)
Query OK, 1678 rows affected (0.17 sec)
Records: 1678 Duplicates: 0 Warnings: 0
*************************** 1. row ***************************
Table: big_table
Create Table: CREATE TABLE `big_table` (
`TABLE_CATALOG` varchar(512) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_SCHEMA` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`ORDINAL_POSITION` bigint(21) unsigned NOT NULL DEFAULT '0',
`COLUMN_DEFAULT` longtext CHARACTER SET utf8,
`IS_NULLABLE` varchar(3) CHARACTER SET utf8 NOT NULL DEFAULT '',
`DATA_TYPE` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`CHARACTER_MAXIMUM_LENGTH` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_OCTET_LENGTH` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_PRECISION` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_SCALE` bigint(21) unsigned DEFAULT NULL,
`DATETIME_PRECISION` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_SET_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLLATION_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLUMN_TYPE` longtext CHARACTER SET utf8 NOT NULL,
`COLUMN_KEY` varchar(3) CHARACTER SET utf8 NOT NULL DEFAULT '',
`EXTRA` varchar(30) CHARACTER SET utf8 NOT NULL DEFAULT '',
`PRIVILEGES` varchar(80) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_COMMENT` varchar(1024) CHARACTER SET utf8 NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
Query OK, 1678 rows affected (0.09 sec)
Records: 1678 Duplicates: 0 Warnings: 0
Query OK, 3356 rows affected (0.07 sec)
Records: 3356 Duplicates: 0 Warnings: 0
Query OK, 6712 rows affected (0.17 sec)
Records: 6712 Duplicates: 0 Warnings: 0
Query OK, 13424 rows affected (0.44 sec)
Records: 13424 Duplicates: 0 Warnings: 0
Query OK, 26848 rows affected (0.63 sec)
Records: 26848 Duplicates: 0 Warnings: 0
Query OK, 53696 rows affected (1.72 sec)
Records: 53696 Duplicates: 0 Warnings: 0
Query OK, 107392 rows affected (3.02 sec)
Records: 107392 Duplicates: 0 Warnings: 0
Query OK, 214784 rows affected (6.28 sec)
Records: 214784 Duplicates: 0 Warnings: 0
Query OK, 429568 rows affected (13.25 sec)
Records: 429568 Duplicates: 0 Warnings: 0
Query OK, 859136 rows affected (28.16 sec)
Records: 859136 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.03 sec)
Query OK, 1718272 rows affected (1 min 9.22 sec)
Records: 1718272 Duplicates: 0 Warnings: 0
+-----------+
| count(id) |
+-----------+
| 1718272 |
+-----------+
1 row in set (1.75 sec)
+-----------+
| count(id) |
+-----------+
| 1718272 |
+-----------+
1 row in set (1.35 sec)
Example 14.3. Speed and Efficiency of CREATE INDEX and DROP INDEX
Here is a sequence of statements demonstrating the relative
speed of CREATE INDEX and
DROP INDEX statements. For a
small table, the elapsed time is less than a second whether we
use the fast or slow technique, so we look at the “rows
affected” output to verify which operations can avoid the
table rebuild. For a large table, the difference in efficiency
is obvious because skipping the table rebuild saves substantial
time.
\! clear -- Make sure we're using the new-style fast DDL. -- Outside of benchmarking and testing, you would -- never enable the old_alter_table setting. set old_alter_table=0; \! echo "=== Create and drop index (small table, new/fast technique) ===" \! echo \! echo "Data size (kilobytes) before index created: " \! du -k data/test/small_table.ibd create index i_dtyp_small on small_table (data_type); \! echo "Data size after index created: " \! du -k data/test/small_table.ibd drop index i_dtyp_small on small_table; -- Revert to the older slower DDL for comparison. set old_alter_table=1; \! echo "=== Create and drop index (small table, old/slow technique) ===" \! echo \! echo "Data size (kilobytes) before index created: " \! du -k data/test/small_table.ibd create index i_dtyp_small on small_table (data_type); \! echo "Data size after index created: " \! du -k data/test/small_table.ibd drop index i_dtyp_small on small_table; -- In the above example, we examined the "rows affected" number, -- ideally looking for a zero figure. Let's try again with a larger -- sample size, where we'll see that the actual time taken can -- vary significantly. -- Back to the new/fast behavior: set old_alter_table=0; \! echo "=== Create and drop index (big table, new/fast technique) ===" \! echo \! echo "Data size (kilobytes) before index created: " \! du -k data/test/big_table.ibd create index i_dtyp_big on big_table (data_type); \! echo "Data size after index created: " \! du -k data/test/big_table.ibd drop index i_dtyp_big on big_table; -- Let's see that again, in slow motion: set old_alter_table=1; \! echo "=== Create and drop index (big table, old/slow technique) ===" \! echo \! echo "Data size (kilobytes) before index created: " \! du -k data/test/big_table.ibd create index i_dtyp_big on big_table (data_type); \! echo "Data size after index created: " \! du -k data/test/big_table.ibd drop index i_dtyp_big on big_table;
Running this code gives this output, condensed for brevity and with the most important points bolded:
Query OK, 0 rows affected (0.00 sec) === Create and drop index (small table, new/fast technique) === Data size (kilobytes) before index created: 384 data/test/small_table.ibd Query OK, 0 rows affected (0.04 sec) Records: 0 Duplicates: 0 Warnings: 0 Data size after index created: 432 data/test/small_table.ibd Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 Query OK, 0 rows affected (0.00 sec) === Create and drop index (small table, old/slow technique) === Data size (kilobytes) before index created: 432 data/test/small_table.ibd Query OK, 1678 rows affected (0.12 sec) Records: 1678 Duplicates: 0 Warnings: 0 Data size after index created: 448 data/test/small_table.ibd Query OK, 1678 rows affected (0.10 sec) Records: 1678 Duplicates: 0 Warnings: 0 Query OK, 0 rows affected (0.00 sec) === Create and drop index (big table, new/fast technique) === Data size (kilobytes) before index created: 315392 data/test/big_table.ibd Query OK, 0 rows affected (33.32 sec) Records: 0 Duplicates: 0 Warnings: 0 Data size after index created: 335872 data/test/big_table.ibd Query OK, 0 rows affected (0.02 sec) Records: 0 Duplicates: 0 Warnings: 0 Query OK, 0 rows affected (0.00 sec) === Create and drop index (big table, old/slow technique) === Data size (kilobytes) before index created: 335872 data/test/big_table.ibd Query OK, 1718272 rows affected (1 min 5.01 sec) Records: 1718272 Duplicates: 0 Warnings: 0 Data size after index created: 348160 data/test/big_table.ibd Query OK, 1718272 rows affected (46.59 sec) Records: 1718272 Duplicates: 0 Warnings: 0
Example 14.4. Concurrent DML During CREATE INDEX and DROP INDEX
Here are some snippets of code that I ran in separate
mysql sessions connected to the same
database, to illustrate DML statements (insert, update, or
delete) running at the same time as CREATE
INDEX and DROP INDEX.
/* CREATE INDEX statement to run against a table while insert/update/delete statements are modifying the column being indexed. */ -- We'll run this script in one session, while simultaneously creating and dropping -- an index on test/big_table.table_name in another session. use test; create index i_concurrent on big_table(table_name);
/* DROP INDEX statement to run against a table while insert/update/delete statements are modifying the column being indexed. */ -- We'll run this script in one session, while simultaneously creating and dropping -- an index on test/big_table.table_name in another session. use test; drop index i_concurrent on big_table;
/* Some queries and insert/update/delete statements to run against a table while an index is being created or dropped. Previously, these operations would have stalled during the index create/drop period and possibly timed out or deadlocked. */ -- We'll run this script in one session, while simultaneously creating and dropping -- an index on test/big_table.table_name in another session. -- In our test instance, that column has about 1.7M rows, with 136 different values. -- Sample values: COLUMNS (20480), ENGINES (6144), EVENTS (24576), FILES (38912), TABLES (21504), VIEWS (10240). set autocommit = 0; use test; select distinct character_set_name from big_table where table_name = 'FILES'; delete from big_table where table_name = 'FILES'; select distinct character_set_name from big_table where table_name = 'FILES'; -- I'll issue the final rollback interactively, not via script, -- the better to control the timing. -- rollback;
Running this code gives this output, condensed for brevity and with the most important points bolded:
mysql: source concurrent_ddl_create.sql Database changed Query OK, 0 rows affected (1 min 25.15 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql: source concurrent_ddl_drop.sql Database changed Query OK, 0 rows affected (24.98 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql: source concurrent_dml.sql Query OK, 0 rows affected (0.00 sec) Database changed +--------------------+ | character_set_name | +--------------------+ | NULL | | utf8 | +--------------------+ 2 rows in set (0.32 sec) Query OK, 38912 rows affected (1.84 sec) Empty set (0.01 sec) mysql: rollback; Query OK, 0 rows affected (1.05 sec)
Example 14.5. Renaming a Column
Here is a demonstration of using ALTER
TABLE to rename a column. We use the new, fast DDL
mechanism to change the name, then the old, slow DDL mechanism
(with old_alter_table=1) to restore the
original column name.
Notes:
Because the syntax for renaming a column also involves
re-specifying the data type, be very careful to specify
exactly the same data type to avoid a costly table rebuild.
In this case, we checked the output of show create
table and
copied any clauses such as table\GCHARACTER SET
and NOT NULL from the original column
definition.
Again, renaming a column for a small table is fast enough that we need to examine the “rows affected” number to verify that the new DDL mechanism is more efficient than the old one. With a big table, the difference in elapsed time makes the improvement obvious.
/* Run through a sequence of 'rename column' statements. Because this operation involves only metadata, not table data, it is fast for big and small tables, with new or old DDL mechanisms. */ \! clear \! echo "Rename column (fast technique, small table):" set old_alter_table=0; alter table small_table change `IS_NULLABLE` `NULLABLE` varchar(3) character set utf8 not null; \! echo "Rename back to original name (slow technique):" set old_alter_table=1; alter table small_table change `NULLABLE` `IS_NULLABLE` varchar(3) character set utf8 not null; \! echo "Rename column (fast technique, big table):" set old_alter_table=0; alter table big_table change `IS_NULLABLE` `NULLABLE` varchar(3) character set utf8 not null; \! echo "Rename back to original name (slow technique):" set old_alter_table=1; alter table big_table change `NULLABLE` `IS_NULLABLE` varchar(3) character set utf8 not null; set old_alter_table=0;
Running this code gives this output, condensed for brevity and with the most important points bolded:
Rename column (fast technique, small table): Query OK, 0 rows affected (0.05 sec) Query OK, 0 rows affected (0.13 sec) Records: 0 Duplicates: 0 Warnings: 0 Rename back to original name (slow technique): Query OK, 0 rows affected (0.00 sec) Query OK, 1678 rows affected (0.35 sec) Records: 1678 Duplicates: 0 Warnings: 0 Rename column (fast technique, big table): Query OK, 0 rows affected (0.00 sec) Query OK, 0 rows affected (0.11 sec) Records: 0 Duplicates: 0 Warnings: 0 Rename back to original name (slow technique): Query OK, 0 rows affected (0.00 sec) Query OK, 1718272 rows affected (1 min 0.00 sec) Records: 1718272 Duplicates: 0 Warnings: 0 Query OK, 0 rows affected (0.00 sec)
Example 14.6. Dropping Foreign Keys
Here is a demonstration of foreign keys, including improvement to the speed of dropping a foreign key constraint.
/* Demonstrate aspects of foreign keys that are or aren't affected by the DDL improvements. - Create a new table with only a few values to serve as the parent table. - Set up the 'small' and 'big' tables as child tables using a foreign key. - Verify that the ON DELETE CASCADE clause makes changes ripple from parent to child tables. - Drop the foreign key constraints, and optionally associated indexes. (This is the operation that is sped up.) */ \! clear -- Make sure foreign keys are being enforced, and allow -- rollback after doing some DELETEs that affect both -- parent and child tables. set foreign_key_checks = 1; set autocommit = 0; -- Create a parent table, containing values that we know are already present -- in the child tables. drop table if exists schema_names; create table schema_names (id int unsigned not null primary key auto_increment, schema_name varchar(64) character set utf8 not null, index i_schema (schema_name)) as select distinct table_schema schema_name from small_table; show create table schema_names\G show create table small_table\G show create table big_table\G -- Creating the foreign key constraint isn't any faster than before. -- It still involves a table rebuild, as illustrated by the "rows affected" figure. alter table small_table add constraint small_fk foreign key i_table_schema (table_schema) references schema_names(schema_name) on delete cascade; alter table big_table add constraint big_fk foreign key i_table_schema (table_schema) references schema_names(schema_name) on delete cascade; show create table small_table\G show create table big_table\G select schema_name from schema_names order by schema_name; select count(table_schema) howmany, table_schema from small_table group by table_schema; select count(table_schema) howmany, table_schema from big_table group by table_schema; -- big_table is the parent table. -- schema_names is the parent table. -- big_table is the child table. -- (One row in the parent table can have many "children" in the child table.) -- Changes to the parent table can ripple through to the child table. -- For example, removing the value 'test' from schema_names.schema_name will -- result in the removal of 20K or so rows from big_table. delete from schema_names where schema_name = 'test'; select schema_name from schema_names order by schema_name; select count(table_schema) howmany, table_schema from small_table group by table_schema; select count(table_schema) howmany, table_schema from big_table group by table_schema; -- Because we've turned off autocommit, we can still get back those deleted rows -- if the DELETE was issued by mistake. rollback; select schema_name from schema_names order by schema_name; select count(table_schema) howmany, table_schema from small_table group by table_schema; select count(table_schema) howmany, table_schema from big_table group by table_schema; -- All of the cross-checking between parent and child tables would be -- deadly slow if there wasn't the requirement for the corresponding -- columns to be indexed! -- But we can get rid of the foreign key using a fast operation -- that doesn't rebuild the table. -- If we didn't specify a constraint name when setting up the foreign key, we would -- have to find the auto-generated name such as 'big_table_ibfk_1' in the -- output from 'show create table'. -- For the small table, we'll drop the foreign key and the associated index. -- Having an index on a small table is less critical. \! echo "DROP FOREIGN KEY and INDEX from small_table:" alter table small_table drop foreign key small_fk, drop index small_fk; -- For the big table, we'll drop the foreign key and leave the associated index. -- If we are still doing queries that reference the indexed column, the index is -- very important to avoid a full table scan of the big table. \! echo "DROP FOREIGN KEY from big_table:" alter table big_table drop foreign key big_fk; show create table small_table\G show create table big_table\G
Running this code gives this output, condensed for brevity and with the most important points bolded:
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.01 sec)
Query OK, 4 rows affected (0.03 sec)
Records: 4 Duplicates: 0 Warnings: 0
*************************** 1. row ***************************
Table: schema_names
Create Table: CREATE TABLE `schema_names` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`schema_name` varchar(64) CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`id`),
KEY `i_schema` (`schema_name`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
*************************** 1. row ***************************
Table: small_table
Create Table: CREATE TABLE `small_table` (
`TABLE_CATALOG` varchar(512) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_SCHEMA` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`ORDINAL_POSITION` bigint(21) unsigned NOT NULL DEFAULT '0',
`COLUMN_DEFAULT` longtext CHARACTER SET utf8,
`IS_NULLABLE` varchar(3) CHARACTER SET utf8 NOT NULL,
`DATA_TYPE` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`CHARACTER_MAXIMUM_LENGTH` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_OCTET_LENGTH` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_PRECISION` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_SCALE` bigint(21) unsigned DEFAULT NULL,
`DATETIME_PRECISION` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_SET_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLLATION_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLUMN_TYPE` longtext CHARACTER SET utf8 NOT NULL,
`COLUMN_KEY` varchar(3) CHARACTER SET utf8 NOT NULL DEFAULT '',
`EXTRA` varchar(30) CHARACTER SET utf8 NOT NULL DEFAULT '',
`PRIVILEGES` varchar(80) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_COMMENT` varchar(1024) CHARACTER SET utf8 NOT NULL DEFAULT '',
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1679 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
*************************** 1. row ***************************
Table: big_table
Create Table: CREATE TABLE `big_table` (
`TABLE_CATALOG` varchar(512) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_SCHEMA` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`ORDINAL_POSITION` bigint(21) unsigned NOT NULL DEFAULT '0',
`COLUMN_DEFAULT` longtext CHARACTER SET utf8,
`IS_NULLABLE` varchar(3) CHARACTER SET utf8 NOT NULL,
`DATA_TYPE` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`CHARACTER_MAXIMUM_LENGTH` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_OCTET_LENGTH` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_PRECISION` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_SCALE` bigint(21) unsigned DEFAULT NULL,
`DATETIME_PRECISION` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_SET_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLLATION_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLUMN_TYPE` longtext CHARACTER SET utf8 NOT NULL,
`COLUMN_KEY` varchar(3) CHARACTER SET utf8 NOT NULL DEFAULT '',
`EXTRA` varchar(30) CHARACTER SET utf8 NOT NULL DEFAULT '',
`PRIVILEGES` varchar(80) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_COMMENT` varchar(1024) CHARACTER SET utf8 NOT NULL DEFAULT '',
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `big_fk` (`TABLE_SCHEMA`)
) ENGINE=InnoDB AUTO_INCREMENT=1718273 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
Query OK, 1678 rows affected (0.10 sec)
Records: 1678 Duplicates: 0 Warnings: 0
Query OK, 1718272 rows affected (1 min 14.54 sec)
Records: 1718272 Duplicates: 0 Warnings: 0
*************************** 1. row ***************************
Table: small_table
Create Table: CREATE TABLE `small_table` (
`TABLE_CATALOG` varchar(512) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_SCHEMA` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`ORDINAL_POSITION` bigint(21) unsigned NOT NULL DEFAULT '0',
`COLUMN_DEFAULT` longtext CHARACTER SET utf8,
`IS_NULLABLE` varchar(3) CHARACTER SET utf8 NOT NULL,
`DATA_TYPE` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`CHARACTER_MAXIMUM_LENGTH` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_OCTET_LENGTH` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_PRECISION` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_SCALE` bigint(21) unsigned DEFAULT NULL,
`DATETIME_PRECISION` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_SET_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLLATION_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLUMN_TYPE` longtext CHARACTER SET utf8 NOT NULL,
`COLUMN_KEY` varchar(3) CHARACTER SET utf8 NOT NULL DEFAULT '',
`EXTRA` varchar(30) CHARACTER SET utf8 NOT NULL DEFAULT '',
`PRIVILEGES` varchar(80) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_COMMENT` varchar(1024) CHARACTER SET utf8 NOT NULL DEFAULT '',
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `small_fk` (`TABLE_SCHEMA`),
CONSTRAINT `small_fk` FOREIGN KEY (`TABLE_SCHEMA`) REFERENCES `schema_names` (`schema_name`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1679 DEFAULT CHARSET=latin1
1 row in set (0.12 sec)
*************************** 1. row ***************************
Table: big_table
Create Table: CREATE TABLE `big_table` (
`TABLE_CATALOG` varchar(512) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_SCHEMA` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`ORDINAL_POSITION` bigint(21) unsigned NOT NULL DEFAULT '0',
`COLUMN_DEFAULT` longtext CHARACTER SET utf8,
`IS_NULLABLE` varchar(3) CHARACTER SET utf8 NOT NULL,
`DATA_TYPE` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`CHARACTER_MAXIMUM_LENGTH` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_OCTET_LENGTH` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_PRECISION` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_SCALE` bigint(21) unsigned DEFAULT NULL,
`DATETIME_PRECISION` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_SET_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLLATION_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLUMN_TYPE` longtext CHARACTER SET utf8 NOT NULL,
`COLUMN_KEY` varchar(3) CHARACTER SET utf8 NOT NULL DEFAULT '',
`EXTRA` varchar(30) CHARACTER SET utf8 NOT NULL DEFAULT '',
`PRIVILEGES` varchar(80) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_COMMENT` varchar(1024) CHARACTER SET utf8 NOT NULL DEFAULT '',
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `big_fk` (`TABLE_SCHEMA`),
CONSTRAINT `big_fk` FOREIGN KEY (`TABLE_SCHEMA`) REFERENCES `schema_names` (`schema_name`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1718273 DEFAULT CHARSET=latin1
1 row in set (0.01 sec)
+--------------------+
| schema_name |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| test |
+--------------------+
4 rows in set (0.00 sec)
+---------+--------------------+
| howmany | table_schema |
+---------+--------------------+
| 563 | information_schema |
| 286 | mysql |
| 786 | performance_schema |
| 43 | test |
+---------+--------------------+
4 rows in set (0.01 sec)
+---------+--------------------+
| howmany | table_schema |
+---------+--------------------+
| 576512 | information_schema |
| 292864 | mysql |
| 804864 | performance_schema |
| 44032 | test |
+---------+--------------------+
4 rows in set (2.10 sec)
Query OK, 1 row affected (1.52 sec)
+--------------------+
| schema_name |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)
+---------+--------------------+
| howmany | table_schema |
+---------+--------------------+
| 563 | information_schema |
| 286 | mysql |
| 786 | performance_schema |
+---------+--------------------+
3 rows in set (0.00 sec)
+---------+--------------------+
| howmany | table_schema |
+---------+--------------------+
| 576512 | information_schema |
| 292864 | mysql |
| 804864 | performance_schema |
+---------+--------------------+
3 rows in set (1.74 sec)
Query OK, 0 rows affected (0.60 sec)
+--------------------+
| schema_name |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| test |
+--------------------+
4 rows in set (0.00 sec)
+---------+--------------------+
| howmany | table_schema |
+---------+--------------------+
| 563 | information_schema |
| 286 | mysql |
| 786 | performance_schema |
| 43 | test |
+---------+--------------------+
4 rows in set (0.01 sec)
+---------+--------------------+
| howmany | table_schema |
+---------+--------------------+
| 576512 | information_schema |
| 292864 | mysql |
| 804864 | performance_schema |
| 44032 | test |
+---------+--------------------+
4 rows in set (1.59 sec)
DROP FOREIGN KEY and INDEX from small_table:
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
DROP FOREIGN KEY from big_table:
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0
*************************** 1. row ***************************
Table: small_table
Create Table: CREATE TABLE `small_table` (
`TABLE_CATALOG` varchar(512) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_SCHEMA` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`ORDINAL_POSITION` bigint(21) unsigned NOT NULL DEFAULT '0',
`COLUMN_DEFAULT` longtext CHARACTER SET utf8,
`IS_NULLABLE` varchar(3) CHARACTER SET utf8 NOT NULL,
`DATA_TYPE` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`CHARACTER_MAXIMUM_LENGTH` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_OCTET_LENGTH` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_PRECISION` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_SCALE` bigint(21) unsigned DEFAULT NULL,
`DATETIME_PRECISION` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_SET_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLLATION_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLUMN_TYPE` longtext CHARACTER SET utf8 NOT NULL,
`COLUMN_KEY` varchar(3) CHARACTER SET utf8 NOT NULL DEFAULT '',
`EXTRA` varchar(30) CHARACTER SET utf8 NOT NULL DEFAULT '',
`PRIVILEGES` varchar(80) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_COMMENT` varchar(1024) CHARACTER SET utf8 NOT NULL DEFAULT '',
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1679 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
*************************** 1. row ***************************
Table: big_table
Create Table: CREATE TABLE `big_table` (
`TABLE_CATALOG` varchar(512) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_SCHEMA` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`TABLE_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_NAME` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`ORDINAL_POSITION` bigint(21) unsigned NOT NULL DEFAULT '0',
`COLUMN_DEFAULT` longtext CHARACTER SET utf8,
`IS_NULLABLE` varchar(3) CHARACTER SET utf8 NOT NULL,
`DATA_TYPE` varchar(64) CHARACTER SET utf8 NOT NULL DEFAULT '',
`CHARACTER_MAXIMUM_LENGTH` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_OCTET_LENGTH` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_PRECISION` bigint(21) unsigned DEFAULT NULL,
`NUMERIC_SCALE` bigint(21) unsigned DEFAULT NULL,
`DATETIME_PRECISION` bigint(21) unsigned DEFAULT NULL,
`CHARACTER_SET_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLLATION_NAME` varchar(32) CHARACTER SET utf8 DEFAULT NULL,
`COLUMN_TYPE` longtext CHARACTER SET utf8 NOT NULL,
`COLUMN_KEY` varchar(3) CHARACTER SET utf8 NOT NULL DEFAULT '',
`EXTRA` varchar(30) CHARACTER SET utf8 NOT NULL DEFAULT '',
`PRIVILEGES` varchar(80) CHARACTER SET utf8 NOT NULL DEFAULT '',
`COLUMN_COMMENT` varchar(1024) CHARACTER SET utf8 NOT NULL DEFAULT '',
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
KEY `big_fk` (`TABLE_SCHEMA`)
) ENGINE=InnoDB AUTO_INCREMENT=1718273 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
Example 14.7. Changing Auto-Increment Value
Here is an illustration of increasing the
auto-increment lower
limit for a table column, demonstrating how this operation now
avoids a table rebuild, plus some other fun facts about
InnoDB auto-increment columns.
/*
If this script is run after foreign_key.sql, the schema_names table is
already set up. But to allow this script to run multiple times without
running into duplicate ID errors, we set up the schema_names table
all over again.
*/
\! clear
\! echo "=== Adjusting the Auto-Increment Limit for a Table ==="
\! echo
drop table if exists schema_names;
create table schema_names (id int unsigned not null primary key auto_increment, schema_name varchar(64) character set utf8 not null, index i_schema (schema_name)) as select distinct table_schema schema_name from small_table;
\! echo "Initial state of schema_names table. AUTO_INCREMENT is included in SHOW CREATE TABLE output."
\! echo "Note how MySQL reserved a block of IDs, but only needed 4 of them in this transaction, so the next inserted values would get IDs 8 and 9."
show create table schema_names\G
select * from schema_names order by id;
\! echo "Inserting even a tiny amount of data can produce gaps in the ID sequence."
insert into schema_names (schema_name) values ('eight'), ('nine');
set old_alter_table=0;
\! echo "Bumping auto-increment lower limit to 20 (fast mechanism):"
alter table schema_names auto_increment=20;
\! echo "Inserting 2 rows that should get IDs 20 and 21:"
insert into schema_names (schema_name) values ('foo'), ('bar');
commit;
set old_alter_table=1;
\! echo "Bumping auto-increment lower limit to 30 (slow mechanism):"
alter table schema_names auto_increment=30;
\! echo "Inserting 2 rows that should get IDs 30 and 31:"
insert into schema_names (schema_name) values ('bletch'),('baz');
commit;
select * from schema_names order by id;
set old_alter_table=0;
\! echo "Final state of schema_names table. AUTO_INCREMENT value shows the next inserted row would get ID=32."
show create table schema_names\G
Running this code gives this output, condensed for brevity and with the most important points bolded:
=== Adjusting the Auto-Increment Limit for a Table ===
Query OK, 0 rows affected (0.01 sec)
Query OK, 4 rows affected (0.02 sec)
Records: 4 Duplicates: 0 Warnings: 0
Initial state of schema_names table. AUTO_INCREMENT is included in SHOW CREATE TABLE output.
Note how MySQL reserved a block of IDs, but only needed 4 of them in this transaction, so the next inserted values would get IDs 8 and 9.
*************************** 1. row ***************************
Table: schema_names
Create Table: CREATE TABLE `schema_names` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`schema_name` varchar(64) CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`id`),
KEY `i_schema` (`schema_name`)
) ENGINE=InnoDB AUTO_INCREMENT=8 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
+----+--------------------+
| id | schema_name |
+----+--------------------+
| 1 | information_schema |
| 2 | mysql |
| 3 | performance_schema |
| 4 | test |
+----+--------------------+
4 rows in set (0.00 sec)
Inserting even a tiny amount of data can produce gaps in the ID sequence.
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.00 sec)
Bumping auto-increment lower limit to 20 (fast mechanism):
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
Inserting 2 rows that should get IDs 20 and 21:
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
Bumping auto-increment lower limit to 30 (slow mechanism):
Query OK, 8 rows affected (0.02 sec)
Records: 8 Duplicates: 0 Warnings: 0
Inserting 2 rows that should get IDs 30 and 31:
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.01 sec)
+----+--------------------+
| id | schema_name |
+----+--------------------+
| 1 | information_schema |
| 2 | mysql |
| 3 | performance_schema |
| 4 | test |
| 8 | eight |
| 9 | nine |
| 20 | foo |
| 21 | bar |
| 30 | bletch |
| 31 | baz |
+----+--------------------+
10 rows in set (0.00 sec)
Query OK, 0 rows affected (0.00 sec)
Final state of schema_names table. AUTO_INCREMENT value shows the next inserted row would get ID=32.
*************************** 1. row ***************************
Table: schema_names
Create Table: CREATE TABLE `schema_names` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`schema_name` varchar(64) CHARACTER SET utf8 NOT NULL,
PRIMARY KEY (`id`),
KEY `i_schema` (`schema_name`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)

User Comments
Add your own comment.