次の各コード例は、そのパフォーマンス、並列性、およびスケーラビリティーが最新のオンライン DDL 拡張によって向上したいくつかの操作を示しています。
例14.1「オンライン DDL 実験のためのスキーマ設定コード」では、以降の例で使用される
BIG_TABLE
とSMALL_TABLE
という名前のテーブルを設定します。例14.2「CREATE INDEX および DROP INDEX の速度と効率」は、インデックスの作成および削除のパフォーマンスの側面を示しています。
例14.3「CREATE INDEX および DROP INDEX 中の並列 DML」は、
DROP INDEX
操作中に実行されるクエリーと DML ステートメントを示しています。例14.4「カラムの名前変更」は、カラムの名前変更の速度向上、および名前変更操作を行うときにデータ型を正確に同じ状態に維持するために必要な注意事項を示しています。
例14.5「外部キーの削除」は、外部キーがオンライン DDL でどのように機能するかを示しています。外部キーの操作には 2 つのテーブルが関連しているため、ロックに関する追加の考慮事項があります。そのため、外部キーを含むテーブルには、オンライン DDL 操作での制限が存在する場合があります。
例14.6「自動インクリメント値の変更」は、自動インクリメントカラムがオンライン DDL でどのように機能するかを示しています。自動インクリメントカラムを含むテーブルには、オンライン DDL 操作での制限が存在する場合があります。
例14.7「LOCK 句を使用した並列性の制御」は、オンライン DDL 操作の進行中の並列クエリーと DML 操作を許可または制限するためのオプションを示しています。これは、DDL ステートメントが待機するか、並列トランザクションが待機するか、またはデッドロックエラーのために並列トランザクションが DML ステートメントを取り消す可能性がある状況を示しています。
例14.8「オンライン DDL 実験のためのスキーマ設定コード」は、1 つのステートメントでの複数のインデックスの作成および削除を示しています。これは、インデックス操作ごとに個別のステートメントを使用するより効率的である場合があります。
例14.9「主キーの作成および削除」は、主キーはテーブルの作成時に定義する方が効率的であり、あとで追加した場合はコストがかなり高くなることを示しています。
例 14.1 オンライン DDL 実験のためのスキーマ設定コード
これらのデモで使用される初期のテーブルを設定するコードを次に示します。
/*
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;
\! 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;
このコードを実行すると、簡略化のために圧縮され、もっとも重要な点が太字で示された次の出力が得られます。
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)
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)
例 14.2 CREATE INDEX および DROP INDEX の速度と効率
次のステートメントシーケンスは、CREATE INDEX
および DROP INDEX
ステートメントの相対的な速度を示しています。小さなテーブルの場合は、速い方法と遅い方法のどちらを使用しても経過時間は 1 秒未満であるため、「rows affected」の出力を見て、どちらの操作がテーブル再構築を回避できるかを確認します。大きなテーブルの場合は、テーブル再構築のスキップによってかなりの時間が節約されるため、効率の違いは明らかです。
\! clear
\! 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), algorithm=inplace;
\! echo "Data size after index created: "
\! du -k data/test/small_table.ibd
drop index i_dtyp_small on small_table, algorithm=inplace;
-- Compare against the older slower DDL.
\! 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), algorithm=copy;
\! echo "Data size after index created: "
\! du -k data/test/small_table.ibd
drop index i_dtyp_small on small_table, algorithm=copy;
-- 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.
\! 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), algorithm=inplace;
\! echo "Data size after index created: "
\! du -k data/test/big_table.ibd
drop index i_dtyp_big on big_table, algorithm=inplace;
\! 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), algorithm=copy;
\! echo "Data size after index created: "
\! du -k data/test/big_table.ibd
drop index i_dtyp_big on big_table, algorithm=copy;
このコードを実行すると、簡略化のために圧縮され、もっとも重要な点が太字で示された次の出力が得られます。
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
例 14.3 CREATE INDEX および DROP INDEX 中の並列 DML
CREATE INDEX
および DROP INDEX
と同時に実行されている DML ステートメント (挿入、更新、または削除) を示すために、次に、同じデータベースに接続された個別の mysql セッションで実行したいくつかのコードスニペットを示します。
/*
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;
このコードを実行すると、簡略化のために圧縮され、もっとも重要な点が太字で示された次の出力が得られます。
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)
例 14.4 カラムの名前変更
ALTER TABLE
を使用したカラムの名前変更のデモを次に示します。新しい、高速な DDL メカニズムを使用して名前を変更してから、古い、低速な DDL メカニズムを (old_alter_table=1
とともに) 使用して元のカラム名をリストアします。
注:
カラムの名前変更の構文にはデータ型の再指定も含まれるため、コストの高いテーブル再構築を回避するために、まったく同じデータ型を指定するよう十分に注意してください。この場合は、
show create table
の出力をチェックし、元のカラム定義からtable
\GCHARACTER SET
やNOT NULL
などの句をすべてコピーしました。この場合も、小さなテーブルのカラムの名前変更は十分高速であるため、新しい DDL メカニズムが古いメカニズムより効率的であることを確認するには 「rows affected」 の数値を検査する必要があります。大きなテーブルでは、経過時間の違いによって速度の向上が明らかになります。
/*
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):"
alter table small_table change `IS_NULLABLE` `NULLABLE` varchar(3) character set utf8 not null, algorithm=inplace;
\! echo "Rename back to original name (slow technique):"
alter table small_table change `NULLABLE` `IS_NULLABLE` varchar(3) character set utf8 not null, algorithm=copy;
\! echo "Rename column (fast technique, big table):"
alter table big_table change `IS_NULLABLE` `NULLABLE` varchar(3) character set utf8 not null, algorithm=inplace;
\! echo "Rename back to original name (slow technique):"
alter table big_table change `NULLABLE` `IS_NULLABLE` varchar(3) character set utf8 not null, algorithm=copy;
このコードを実行すると、簡略化のために圧縮され、もっとも重要な点が太字で示された次の出力が得られます。
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)
例 14.5 外部キーの削除
外部キーのデモ (外部キー制約の削除の速度の向上を含む) を次に示します。
/*
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 still involves a table rebuild when foreign_key_checks=1,
-- 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
このコードを実行すると、簡略化のために圧縮され、もっとも重要な点が太字で示された次の出力が得られます。
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)
例 14.6 自動インクリメント値の変更
テーブルカラムの自動インクリメントの下限値を増やすコードを次に示します。これは、この操作によってテーブル再構築がどのように回避されているか、さらには InnoDB
の自動インクリメントカラムに関する興味深いその他のいくつかの事実を示しています。
/*
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');
\! echo "Bumping auto-increment lower limit to 20 (fast mechanism):"
alter table schema_names auto_increment=20, algorithm=inplace;
\! echo "Inserting 2 rows that should get IDs 20 and 21:"
insert into schema_names (schema_name) values ('foo'), ('bar');
commit;
\! echo "Bumping auto-increment lower limit to 30 (slow mechanism):"
alter table schema_names auto_increment=30, algorithm=copy;
\! 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;
\! 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
このコードを実行すると、簡略化のために圧縮され、もっとも重要な点が太字で示された次の出力が得られます。
=== 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)
例 14.7 LOCK 句を使用した並列性の制御
この例は、ALTER TABLE
ステートメントの LOCK
句を使用して、オンライン DDL 操作の進行中にテーブルへの並列アクセスを許可または拒否する方法を示しています。この句には、クエリーと DML ステートメントを許可するか (LOCK=NONE
)、クエリーのみを許可するか (LOCK=SHARED
)、または並列アクセスをまったく許可しない (LOCK=EXCLUSIVE
) 設定があります。
ここでは、いずれかのセッションの待機中またはデッドロック中の動作を確認するために、LOCK
句の異なる値を使用して、1 つのセッションで連続した ALTER TABLE
ステートメントを実行してインデックスを作成および削除します。前の例と同じ BIG_TABLE
テーブルを使用し、約 170 万行から始めます。説明のために、IS_NULLABLE
カラムに対してインデックス作成とクエリーを実行します。(ただし、実際には、固有の値が 2 つしかない非常に小さなカラムのインデックスを作成することはありえません。)
mysql: desc big_table;
+--------------------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+--------------------------+---------------------+------+-----+---------+----------------+
| TABLE_CATALOG | varchar(512) | NO | | | |
| TABLE_SCHEMA | varchar(64) | NO | | | |
| TABLE_NAME | varchar(64) | NO | | | |
| COLUMN_NAME | varchar(64) | NO | | | |
| ORDINAL_POSITION | bigint(21) unsigned | NO | | 0 | |
| COLUMN_DEFAULT | longtext | YES | | NULL | |
| IS_NULLABLE | varchar(3) | NO | | | |
...
+--------------------------+---------------------+------+-----+---------+----------------+
21 rows in set (0.14 sec)
mysql: alter table big_table add index i1(is_nullable);
Query OK, 0 rows affected (20.71 sec)
mysql: alter table big_table drop index i1;
Query OK, 0 rows affected (0.02 sec)
mysql: alter table big_table add index i1(is_nullable), lock=exclusive;
Query OK, 0 rows affected (19.44 sec)
mysql: alter table big_table drop index i1;
Query OK, 0 rows affected (0.03 sec)
mysql: alter table big_table add index i1(is_nullable), lock=shared;
Query OK, 0 rows affected (16.71 sec)
mysql: alter table big_table drop index i1;
Query OK, 0 rows affected (0.05 sec)
mysql: alter table big_table add index i1(is_nullable), lock=none;
Query OK, 0 rows affected (12.26 sec)
mysql: alter table big_table drop index i1;
Query OK, 0 rows affected (0.01 sec)
... repeat statements like the above while running queries ...
... and DML statements at the same time in another session ...
DDL ステートメントを実行しているセッションでは、特別なことは何も発生しません。場合によっては、別のトランザクションが DDL 中にテーブルを変更したか、または DDL の前にテーブルをクエリーしたとき、そのトランザクションの完了を待機しているために ALTER TABLE
に非常に長い時間がかかることがあります。
mysql: alter table big_table add index i1(is_nullable), lock=none;
Query OK, 0 rows affected (59.27 sec)
mysql: -- The previous ALTER took so long because it was waiting for all the concurrent
mysql: -- transactions to commit or roll back.
mysql: alter table big_table drop index i1;
Query OK, 0 rows affected (41.05 sec)
mysql: -- Even doing a SELECT on the table in the other session first causes
mysql: -- the ALTER TABLE above to stall until the transaction
mysql: -- surrounding the SELECT is committed or rolled back.
同時に実行されている別のセッションのログを次に示します。ここでは、前のリストに示されている DDL 操作の前、最中、およびあとにテーブルに対してクエリーと DML ステートメントを発行しています。この最初のリストは、クエリーのみを示しています。LOCK=NONE
または LOCK=SHARED
を使用して DDL 操作中にクエリーが許可されること、および ALTER TABLE
ステートメントに LOCK=EXCLUSIVE
が含まれている場合は DDL が完了するまでクエリーが待機することを予測しています。
mysql: show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.01 sec)
mysql: -- A trial query before any ADD INDEX in the other session:
mysql: -- Note: because autocommit is enabled, each
mysql: -- transaction finishes immediately after the query.
mysql: select distinct is_nullable from big_table;
+-------------+
| is_nullable |
+-------------+
| NO |
| YES |
+-------------+
2 rows in set (4.49 sec)
mysql: -- Index is being created with LOCK=EXCLUSIVE on the ALTER statement.
mysql: -- The query waits until the DDL is finished before proceeding.
mysql: select distinct is_nullable from big_table;
+-------------+
| is_nullable |
+-------------+
| NO |
| YES |
+-------------+
2 rows in set (17.26 sec)
mysql: -- Index is being created with LOCK=SHARED on the ALTER statement.
mysql: -- The query returns its results while the DDL is in progress.
mysql: -- The same thing happens with LOCK=NONE on the ALTER statement.
mysql: select distinct is_nullable from big_table;
+-------------+
| is_nullable |
+-------------+
| NO |
| YES |
+-------------+
2 rows in set (3.11 sec)
mysql: -- Once the index is created, and with no DDL in progress,
mysql: -- queries referencing the indexed column are very fast:
mysql: select count(*) from big_table where is_nullable = 'YES';
+----------+
| count(*) |
+----------+
| 411648 |
+----------+
1 row in set (0.20 sec)
mysql: select distinct is_nullable from big_table;
+-------------+
| is_nullable |
+-------------+
| NO |
| YES |
+-------------+
2 rows in set (0.00 sec)
次に、この並列セッションで、DML ステートメントまたは DML ステートメントとクエリーの組み合わせを含むいくつかのトランザクションを実行します。テーブルへの予測可能かつ検証可能な変更を示すために、DELETE
ステートメントを使用します。この部分にあるトランザクションは複数のステートメントにまたがる場合があるため、これらのテストは autocommit
がオフになった状態で実行します。
mysql: set global autocommit = off;
Query OK, 0 rows affected (0.00 sec)
mysql: -- Count the rows that will be involved in our DELETE statements:
mysql: select count(*) from big_table where is_nullable = 'YES';
+----------+
| count(*) |
+----------+
| 411648 |
+----------+
1 row in set (0.95 sec)
mysql: -- After this point, any DDL statements back in the other session
mysql: -- stall until we commit or roll back.
mysql: delete from big_table where is_nullable = 'YES' limit 11648;
Query OK, 11648 rows affected (0.14 sec)
mysql: select count(*) from big_table where is_nullable = 'YES';
+----------+
| count(*) |
+----------+
| 400000 |
+----------+
1 row in set (1.04 sec)
mysql: rollback;
Query OK, 0 rows affected (0.09 sec)
mysql: select count(*) from big_table where is_nullable = 'YES';
+----------+
| count(*) |
+----------+
| 411648 |
+----------+
1 row in set (0.93 sec)
mysql: -- OK, now we're going to try that during index creation with LOCK=NONE.
mysql: delete from big_table where is_nullable = 'YES' limit 11648;
Query OK, 11648 rows affected (0.21 sec)
mysql: -- We expect that now there will be 400000 'YES' rows left:
mysql: select count(*) from big_table where is_nullable = 'YES';
+----------+
| count(*) |
+----------+
| 400000 |
+----------+
1 row in set (1.25 sec)
mysql: -- In the other session, the ALTER TABLE is waiting before finishing,
mysql: -- because _this_ transaction hasn't committed or rolled back yet.
mysql: rollback;
Query OK, 0 rows affected (0.11 sec)
mysql: select count(*) from big_table where is_nullable = 'YES';
+----------+
| count(*) |
+----------+
| 411648 |
+----------+
1 row in set (0.19 sec)
mysql: -- The ROLLBACK left the table in the same state we originally found it.
mysql: -- Now let's make a permanent change while the index is being created,
mysql: -- again with ALTER TABLE ... , LOCK=NONE.
mysql: -- First, commit so the DROP INDEX in the other shell can finish;
mysql: -- the previous SELECT started a transaction that accessed the table.
mysql: commit;
Query OK, 0 rows affected (0.00 sec)
mysql: -- Now we add the index back in the other shell, then issue DML in this one
mysql: -- while the DDL is running.
mysql: delete from big_table where is_nullable = 'YES' limit 11648;
Query OK, 11648 rows affected (0.23 sec)
mysql: commit;
Query OK, 0 rows affected (0.01 sec)
mysql: -- In the other shell, the ADD INDEX has finished.
mysql: select count(*) from big_table where is_nullable = 'YES';
+----------+
| count(*) |
+----------+
| 400000 |
+----------+
1 row in set (0.19 sec)
mysql: -- At the point the new index is finished being created, it contains entries
mysql: -- only for the 400000 'YES' rows left when all concurrent transactions are finished.
mysql:
mysql: -- Now we will run a similar test, while ALTER TABLE ... , LOCK=SHARED is running.
mysql: -- We expect a query to complete during the ALTER TABLE, but for the DELETE
mysql: -- to run into some kind of issue.
mysql: commit;
Query OK, 0 rows affected (0.00 sec)
mysql: -- As expected, the query returns results while the LOCK=SHARED DDL is running:
mysql: select count(*) from big_table where is_nullable = 'YES';
+----------+
| count(*) |
+----------+
| 400000 |
+----------+
1 row in set (2.07 sec)
mysql: -- The DDL in the other session is not going to finish until this transaction
mysql: -- is committed or rolled back. If we tried a DELETE now and it waited because
mysql: -- of LOCK=SHARED on the DDL, both transactions would wait forever (deadlock).
mysql: -- MySQL detects this condition and cancels the attempted DML statement.
mysql: delete from big_table where is_nullable = 'YES' limit 100000;
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction
mysql: -- The transaction here is still going, so in the other shell, the ADD INDEX operation
mysql: -- is waiting for this transaction to commit or roll back.
mysql: rollback;
Query OK, 0 rows affected (0.00 sec)
mysql: -- Now let's try issuing a query and some DML, on one line, while running
mysql: -- ALTER TABLE ... , LOCK=EXCLUSIVE in the other shell.
mysql: -- Notice how even the query is held up until the DDL is finished.
mysql: -- By the time the DELETE is issued, there is no conflicting access
mysql: -- to the table and we avoid the deadlock error.
mysql: select count(*) from big_table where is_nullable = 'YES'; delete from big_table where is_nullable = 'YES' limit 100000;
+----------+
| count(*) |
+----------+
| 400000 |
+----------+
1 row in set (15.98 sec)
Query OK, 100000 rows affected (2.81 sec)
mysql: select count(*) from big_table where is_nullable = 'YES';
+----------+
| count(*) |
+----------+
| 300000 |
+----------+
1 row in set (0.17 sec)
mysql: rollback;
Query OK, 0 rows affected (1.36 sec)
mysql: select count(*) from big_table where is_nullable = 'YES';
+----------+
| count(*) |
+----------+
| 400000 |
+----------+
1 row in set (0.19 sec)
mysql: commit;
Query OK, 0 rows affected (0.00 sec)
mysql: -- Next, we try ALTER TABLE ... , LOCK=EXCLUSIVE in the other session
mysql: -- and only issue DML, not any query, in the concurrent transaction here.
mysql: delete from big_table where is_nullable = 'YES' limit 100000;
Query OK, 100000 rows affected (16.37 sec)
mysql: -- That was OK because the ALTER TABLE did not have to wait for the transaction
mysql: -- here to complete. The DELETE in this session waited until the index was ready.
mysql: select count(*) from big_table where is_nullable = 'YES';
+----------+
| count(*) |
+----------+
| 300000 |
+----------+
1 row in set (0.16 sec)
mysql: commit;
Query OK, 0 rows affected (0.00 sec)
前のリスト例では、次のことがわかりました。
ALTER TABLE
のLOCK
句は、ステートメントの残りの部分からカンマで区切られます。オンライン DDL 操作は、テーブルにアクセスする以前のいずれかのトランザクションがコミットまたはロールバックされるまで、開始前に待機する可能性があります。
オンライン DDL 操作は、テーブルにアクセスするいずれかの並列トランザクションがコミットまたはロールバックされるまで、完了前に待機する可能性があります。
ALTER TABLE
ステートメントがLOCK=NONE
またはLOCK=SHARED
を使用しているかぎり、オンライン DDL 操作が実行されている間の並列クエリーの動作は比較的単純です。autocommit
がオンとオフのどちらになっているかに注意を払ってください。オフになっている場合は、テーブルで DDL 操作を実行する前にほかのセッションのトランザクション (クエリーだけであっても) を終了するときは注意してください。LOCK=SHARED
では、クエリーと DML が混在した並列トランザクションでデッドロックエラーが発生する可能性があるため、DDL が完了したあとにこれらのトランザクションを再開する必要があります。LOCK=NONE
では、並列トランザクションにクエリーと DML を自由に混在させることができます。DDL 操作は、並列トランザクションがコミットまたはロールバックされるまで待機します。LOCK=EXCLUSIVE
では、並列トランザクションにクエリーと DML を自由に混在させることができますが、これらのトランザクションは、DDL 操作が完了するまで待機したあとでしかテーブルにアクセスできません。
例 14.8 オンライン DDL 実験のためのスキーマ設定コード
1 つの ALTER TABLE
ステートメントでテーブル上に複数のインデックスを作成できます。テーブルのクラスタ化されたインデックスは 1 回しかスキャンする必要がない (ただし、データは新しいインデックスごとに個別にソートされます) ため、これはかなり効率的です。例:
CREATE TABLE T1(A INT PRIMARY KEY, B INT, C CHAR(1)) ENGINE=InnoDB;
INSERT INTO T1 VALUES (1,2,'a'), (2,3,'b'), (3,2,'c'), (4,3,'d'), (5,2,'e');
COMMIT;
ALTER TABLE T1 ADD INDEX (B), ADD UNIQUE INDEX (C);
カラム A
に主キーを持つ上のステートメント CREATE TABLE T1
は、いくつかの行を挿入したあと、カラム B
と C
に 2 つの新しいインデックスを構築します。ALTER TABLE
ステートメントの前に T1
に多数の行が挿入されていたとすると、このアプローチは、データをロードする前にすべてのセカンダリインデックスを作成するよりはるかに効率的です。
InnoDB セカンダリインデックスの削除にもテーブルデータのコピーは必要ないため、1 つの ALTER TABLE
ステートメントまたは複数の DROP INDEX
ステートメントで複数のインデックスを削除することは等しく効率的です。
ALTER TABLE T1 DROP INDEX B, DROP INDEX C;
または
DROP INDEX B ON T1;
DROP INDEX C ON T1;
例 14.9 主キーの作成および削除
InnoDB
テーブルのクラスタ化されたインデックスの再構成には常に、テーブルデータのコピーが必要です。そのため、テーブルの再構築を回避するために、あとで ALTER TABLE ... ADD PRIMARY KEY
を発行するのではなく、テーブルの作成時に主キーを定義することをお勧めします。
次の例のように、あとで PRIMARY KEY
を定義した場合はデータがコピーされます。
CREATE TABLE T2 (A INT, B INT);
INSERT INTO T2 VALUES (NULL, 1);
ALTER TABLE T2 ADD PRIMARY KEY (B);
UNIQUE
または PRIMARY KEY
インデックスを作成したとき、MySQL は、いくつかの追加の作業を行う必要があります。UNIQUE
インデックスの場合、MySQL は、テーブルに重複したキーの値が含まれていないことをチェックします。PRIMARY KEY
インデックスの場合も、MySQL は、どの PRIMARY KEY
カラムにも NULL
が含まれていないことをチェックします。
ALGORITHM=COPY
句を使用して主キーを追加したとき、MySQL は実際には、関連付けられたカラム内の NULL
値をデフォルト値、つまり、数値の場合は 0、文字ベースのカラムや BLOB の場合は空の文字列、および DATETIME
の場合は 0000-00-00 00:00:00 に変換します。これは非標準の動作であるため、これに依存しないようにすることをお勧めします。ALGORITHM=INPLACE
を使用した主キーの追加は、SQL_MODE
設定に strict_trans_tables
または strict_all_tables
フラグが含まれている場合にのみ許可されます。SQL_MODE
設定が厳密である場合は、ADD PRIMARY KEY ... , ALGORITHM=INPLACE
が許可されますが、要求された主キーカラムに NULL
値が含まれているとステートメントは引き続き失敗します。ALGORITHM=INPLACE
の動作は、より標準に準拠しています。
次の例は、ADD PRIMARY KEY
句のいくつかの可能性を示しています。ALGORITHM=COPY
句を使用した場合、主キーカラム内に NULL
値が存在していても操作は成功します。データは暗黙のうちに変更され、それによって問題が発生する可能性があります。
mysql> CREATE TABLE add_pk_via_copy (c1 INT, c2 VARCHAR(10), c3 DATETIME);
Query OK, 0 rows affected (0.03 sec)
mysql> INSERT INTO add_pk_via_copy VALUES (1,'a','2014-11-03 11:01:37'),(NULL,NULL,NULL);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> SET sql_mode = '';
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE add_pk_via_copy ADD PRIMARY KEY (c1,c2,c3), ALGORITHM=COPY;
Query OK, 2 rows affected, 3 warnings (0.07 sec)
Records: 2 Duplicates: 0 Warnings: 3
mysql> SHOW WARNINGS;
+---------+------+-----------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------+
| Warning | 1265 | Data truncated for column 'c1' at row 2 |
| Warning | 1265 | Data truncated for column 'c2' at row 2 |
| Warning | 1265 | Data truncated for column 'c3' at row 2 |
+---------+------+-----------------------------------------+
3 rows in set (0.00 sec)
mysql> SELECT * FROM add_pk_via_copy;
+----+----+---------------------+
| c1 | c2 | c3 |
+----+----+---------------------+
| 0 | | 0000-00-00 00:00:00 |
| 1 | a | 2014-11-03 11:01:37 |
+----+----+---------------------+
2 rows in set (0.00 sec)
ALGORITHM=INPLACE
句を使用した場合、この設定ではデータの整合性を高い優先度とみなしているため、操作はさまざまな理由で失敗する可能性があります。このステートメントは、SQL_MODE
設定が十分に「厳密」でない場合、または主キーカラムに NULL
値が含まれている場合にエラーを出力します。これらの両方の要件に対応すれば、ALTER TABLE
操作は成功します。
mysql> CREATE TABLE add_pk_via_inplace (c1 INT, c2 VARCHAR(10), c3 DATETIME);
Query OK, 0 rows affected (0.02 sec)
mysql> INSERT INTO add_pk_via_inplace VALUES (1,'a','2014-11-03 11:01:37'),(NULL,NULL,NULL);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0
mysql> SELECT * FROM add_pk_via_inplace;
+------+------+---------------------+
| c1 | c2 | c3 |
+------+------+---------------------+
| 1 | a | 2014-11-03 11:01:37 |
| NULL | NULL | NULL |
+------+------+---------------------+
2 rows in set (0.00 sec)
mysql> SET sql_mode = '';
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE add_pk_via_inplace ADD PRIMARY KEY (c1,c2,c3), ALGORITHM=INPLACE;
ERROR 1846 (0A000): ALGORITHM=INPLACE is not supported. Reason: cannot silently convert NULL values,
as required in this SQL_MODE. Try ALGORITHM=COPY.
mysql> SET sql_mode ='strict_trans_tables';
Query OK, 0 rows affected (0.00 sec)
mysql> ALTER TABLE add_pk_via_inplace ADD PRIMARY KEY (c1,c2,c3), ALGORITHM=INPLACE;
ERROR 1138 (22004): Invalid use of NULL value
mysql> DELETE FROM add_pk_via_inplace WHERE c1 IS NULL OR c2 IS NULL OR c3 IS NULL;
Query OK, 1 row affected (0.01 sec)
mysql> SELECT * FROM add_pk_via_inplace;
+------+------+---------------------+
| c1 | c2 | c3 |
+------+------+---------------------+
| 1 | a | 2014-11-03 11:01:37 |
+------+------+---------------------+
1 row in set (0.00 sec)
mysql> ALTER TABLE add_pk_via_inplace ADD PRIMARY KEY (c1,c2,c3), ALGORITHM=INPLACE;
Query OK, 0 rows affected (0.09 sec)
Records: 0 Duplicates: 0 Warnings: 0
主キーなしでテーブルを作成すると、InnoDB は、主キーを自動的に選択します。これは、NOT NULL
カラムで定義された最初の UNIQUE
キー、またはシステムで生成されたキーである場合があります。隠れた余分なカラムの不確実性や、それに対して領域要件が発生する可能性を排除するには、CREATE TABLE
ステートメントの一部として PRIMARY KEY
句を指定します。