Permalink
Please sign in to comment.
Showing
with
204 additions
and 8 deletions.
- +7 −6 doc/requirements-and-limitations.md
- +68 −0 doc/shared-key.md
- +22 −0 localtests/fail-drop-pk/create.sql
- +1 −0 localtests/fail-drop-pk/expect_failure
- +1 −0 localtests/fail-drop-pk/extra_args
- +22 −0 localtests/fail-no-shared-uk/create.sql
- +1 −0 localtests/fail-no-shared-uk/expect_failure
- +1 −0 localtests/fail-no-shared-uk/extra_args
- +24 −0 localtests/swap-pk-uk/create.sql
- +1 −0 localtests/swap-pk-uk/extra_args
- +1 −0 localtests/swap-pk-uk/order_by
- +24 −0 localtests/swap-uk-uk/create.sql
- +1 −0 localtests/swap-uk-uk/extra_args
- +1 −0 localtests/swap-uk-uk/order_by
- +22 −0 localtests/swap-uk/create.sql
- +1 −0 localtests/swap-uk/extra_args
- +6 −2 localtests/test.sh
| @@ -0,0 +1,68 @@ | ||
| +# Shared key | ||
| + | ||
| +A requirement for a migration to run is that the two _before_ and _after_ tables have a shared unique key. This is to elaborate and illustrate on the matter. | ||
| + | ||
| +### Introduction | ||
| + | ||
| +Consider a classic, simple migration. The table is any normal: | ||
| + | ||
| +``` | ||
| +CREATE TABLE tbl ( | ||
| + id bigint unsigned not null auto_increment, | ||
| + data varchar(255), | ||
| + more_data int, | ||
| + PRIMARY KEY(id) | ||
| +) | ||
| +``` | ||
| + | ||
| +And the migration is a simple `add column ts timestamp`. | ||
| + | ||
| +In such migration there is no change in indexes, and in particular no change to any unique key, and specifically no change to the `PRIMARY KEY`. To run this migration, `gh-ost` would iterate the `tbl` table using the primary key, copy rows from `tbl` to the _ghost_ table `_tbl_gho` by order of `id`, and then apply binlog events onto `_tbl_gho`. | ||
| + | ||
| +Applying the binlog events assumes the existence of a shared unique key. For example, an `UPDATE` statement in the binary log translate to a `REPLACE` statement which `gh-ost` applies to the _ghost_ table. Such statement expects to add or replace an existing row based on given row data. In particular, it would _replace_ an existing row if a unique key violation is met. | ||
| + | ||
| +So `gh-ost` correlates `tbl` and `_tbl_gho` rows using a unique key. In the above example that would be the `PRIMARY KEY`. | ||
| + | ||
| +### Rules | ||
| + | ||
| +There must be a shared set of not-null columns for which there is a unique constraint in both the original table and the migration (_ghost_) table. | ||
| + | ||
| +### Interpreting the rules | ||
| + | ||
| +The same columns must be covered by a unique key in both tables. This doesn't have to be the `PRIMARY KEY`. This doesn't have to be a key of the same name. | ||
| + | ||
| +Upon migration, `gh-ost` inspects both the original and _ghost_ table and attempts to find at least one such unique key (or rather, a set of columns) that is shared between the two. Typically this would just be the `PRIMARY KEY`, but sometimes you may change the `PRIMARY KEY` itself, in which case `gh-ost` will look for other options. | ||
| + | ||
| +`gh-ost` expects unique keys where no `NULL` values are found, i.e. all columns covered by the unique key are defined as `NOT NULL`. This is implicitly true for `PRIMARY KEY`s. If no such key can be found, `gh-ost` bails out. In the event there is no such key, but you happen to _know_ your columns have no `NULL` values even though they're `NULL`-able, you may take responsibility and pass the `--allow-nullable-unique-key`. The migration will run well as long as no `NULL` values are found in the unique key's columns. Any actual `NULL`s may corrupt the migration. | ||
| + | ||
| +### Examples: allowed and not allowed | ||
| + | ||
| +``` | ||
| +create table some_table ( | ||
| + id int auto_increment, | ||
| + ts timestamp, | ||
| + name varchar(128) not null, | ||
| + owner_id int not null, | ||
| + loc_id int, | ||
| + primary key(id), | ||
| + unique key name_uidx(name) | ||
| +) | ||
| +``` | ||
| + | ||
| +Following are examples of migrations that are _good to run_: | ||
| + | ||
| +- `add column i int` | ||
| +- `add key owner_idx(owner_id)` | ||
| +- `add unique key owner_name_idx(owner_id, name)` - though you need to make sure to not write conflicting rows while this migration runs | ||
| +- `drop key name_uidx` - `primary key` is shared between the tables | ||
| +- `drop primary key, add primary key(owner_id, loc_id)` - `name_uidx` is shared between the tables and is used for migration | ||
| +- `change id bigint unsigned` - the `'primary key` is used. The change of type still makes the `primary key` workable. | ||
| +- `drop primary key, drop key name_uidx, create primary key(name), create unique key id_uidx(id)` - swapping the two keys. `gh-ost` is still happy because `id` is still unique in both tables. So is `name`. | ||
| + | ||
| + | ||
| +Following are examples of migrations that _cannot run_: | ||
| + | ||
| +- `drop primary key, drop key name_uidx` - no unique key to _ghost_ table, so clearly cannot run | ||
| +- `drop primary key, drop key name_uidx, create primary key(name, owner_id)` - no shared columns to both tables. Even though `name` exists in the _ghost_ table's `primary key`, it is only part of the key and in itself does not guarantee uniqueness in the _ghost_ table. | ||
| + | ||
| +Also, you cannot run a migration on a table that doesn't have some form of `unique key` in the first place, such as `some_table (id int, ts timestamp)` |
| @@ -0,0 +1,22 @@ | ||
| +drop table if exists gh_ost_test; | ||
| +create table gh_ost_test ( | ||
| + id int auto_increment, | ||
| + i int not null, | ||
| + ts timestamp, | ||
| + primary key(id) | ||
| +) auto_increment=1; | ||
| + | ||
| +drop event if exists gh_ost_test; | ||
| +delimiter ;; | ||
| +create event gh_ost_test | ||
| + on schedule every 1 second | ||
| + starts current_timestamp | ||
| + ends current_timestamp + interval 60 second | ||
| + on completion not preserve | ||
| + enable | ||
| + do | ||
| +begin | ||
| + insert into gh_ost_test values (null, 11, now()); | ||
| + insert into gh_ost_test values (null, 13, now()); | ||
| + insert into gh_ost_test values (null, 17, now()); | ||
| +end ;; |
| @@ -0,0 +1 @@ | ||
| +No PRIMARY nor UNIQUE key found in table |
| @@ -0,0 +1 @@ | ||
| +--alter="change id id int, drop primary key" |
| @@ -0,0 +1,22 @@ | ||
| +drop table if exists gh_ost_test; | ||
| +create table gh_ost_test ( | ||
| + id int auto_increment, | ||
| + i int not null, | ||
| + ts timestamp, | ||
| + primary key(id) | ||
| +) auto_increment=1; | ||
| + | ||
| +drop event if exists gh_ost_test; | ||
| +delimiter ;; | ||
| +create event gh_ost_test | ||
| + on schedule every 1 second | ||
| + starts current_timestamp | ||
| + ends current_timestamp + interval 60 second | ||
| + on completion not preserve | ||
| + enable | ||
| + do | ||
| +begin | ||
| + insert into gh_ost_test values (null, 11, now()); | ||
| + insert into gh_ost_test values (null, 13, now()); | ||
| + insert into gh_ost_test values (null, 17, now()); | ||
| +end ;; |
| @@ -0,0 +1 @@ | ||
| +No shared unique key can be found after ALTER |
| @@ -0,0 +1 @@ | ||
| +--alter="drop primary key, add primary key (id, i)" |
| @@ -0,0 +1,24 @@ | ||
| +drop table if exists gh_ost_test; | ||
| +create table gh_ost_test ( | ||
| + id bigint, | ||
| + i int not null, | ||
| + ts timestamp(6), | ||
| + primary key(id), | ||
| + unique key its_uidx(i, ts) | ||
| +) ; | ||
| + | ||
| +drop event if exists gh_ost_test; | ||
| +delimiter ;; | ||
| +create event gh_ost_test | ||
| + on schedule every 1 second | ||
| + starts current_timestamp | ||
| + ends current_timestamp + interval 60 second | ||
| + on completion not preserve | ||
| + enable | ||
| + do | ||
| +begin | ||
| + insert into gh_ost_test values ((unix_timestamp() << 2) + 0, 11, now(6)); | ||
| + insert into gh_ost_test values ((unix_timestamp() << 2) + 1, 13, now(6)); | ||
| + insert into gh_ost_test values ((unix_timestamp() << 2) + 2, 17, now(6)); | ||
| + insert into gh_ost_test values ((unix_timestamp() << 2) + 3, 19, now(6)); | ||
| +end ;; |
| @@ -0,0 +1 @@ | ||
| +--alter="drop primary key, drop key its_uidx, add primary key (i, ts), add unique key id_uidx(id)" |
| @@ -0,0 +1 @@ | ||
| +id |
| @@ -0,0 +1,24 @@ | ||
| +drop table if exists gh_ost_test; | ||
| +create table gh_ost_test ( | ||
| + id bigint, | ||
| + i int not null, | ||
| + ts timestamp(6), | ||
| + unique key id_uidx(id), | ||
| + unique key its_uidx(i, ts) | ||
| +) ; | ||
| + | ||
| +drop event if exists gh_ost_test; | ||
| +delimiter ;; | ||
| +create event gh_ost_test | ||
| + on schedule every 1 second | ||
| + starts current_timestamp | ||
| + ends current_timestamp + interval 60 second | ||
| + on completion not preserve | ||
| + enable | ||
| + do | ||
| +begin | ||
| + insert into gh_ost_test values ((unix_timestamp() << 2) + 0, 11, now(6)); | ||
| + insert into gh_ost_test values ((unix_timestamp() << 2) + 1, 13, now(6)); | ||
| + insert into gh_ost_test values ((unix_timestamp() << 2) + 2, 17, now(6)); | ||
| + insert into gh_ost_test values ((unix_timestamp() << 2) + 3, 19, now(6)); | ||
| +end ;; |
| @@ -0,0 +1 @@ | ||
| +--alter="drop key id_uidx, drop key its_uidx, add unique key its2_uidx(i, ts), add unique key id2_uidx(id)" |
| @@ -0,0 +1 @@ | ||
| +id |
| @@ -0,0 +1,22 @@ | ||
| +drop table if exists gh_ost_test; | ||
| +create table gh_ost_test ( | ||
| + id int auto_increment, | ||
| + i int not null, | ||
| + ts timestamp, | ||
| + primary key(id) | ||
| +) auto_increment=1; | ||
| + | ||
| +drop event if exists gh_ost_test; | ||
| +delimiter ;; | ||
| +create event gh_ost_test | ||
| + on schedule every 1 second | ||
| + starts current_timestamp | ||
| + ends current_timestamp + interval 60 second | ||
| + on completion not preserve | ||
| + enable | ||
| + do | ||
| +begin | ||
| + insert into gh_ost_test values (null, 11, now()); | ||
| + insert into gh_ost_test values (null, 13, now()); | ||
| + insert into gh_ost_test values (null, 17, now()); | ||
| +end ;; |
| @@ -0,0 +1 @@ | ||
| +--alter="drop primary key, add unique key(id)" |
0 comments on commit
56ab127