オンライン再定義(カラム追加)

 

更新待ち時間結果

MySQL
pt-online-schema-change -> 8秒
ALGORITHM=INSTANT -> ほぼ無し
ALGORITHM=INPLACE -> 1秒
ALGORITHM=COPY -> 5秒(カラム追加時間)

Oracle
dbms_redefinition -> 0.5秒
alter table -> ほぼ無し

PostgreSQL
pg-online-schema-change -> 8秒
alter table -> ほぼ無し

SQL Server
alter table -> ほぼ無し

 


(8.0.29)

pt-online-schema-change

https://www.s-style.co.jp/products/percona/toolkit/pt-online-schema-change/

 

-- pt-online-schema-changeのインストール

yum install -y https://www.percona.com/downloads/percona-toolkit/3.0.2/binary/redhat/7/x86_64/percona-toolkit-3.0.2-1.el7.x86_64.rpm

pt-online-schema-change --version

 

-- テーブル作成

drop table tab1;
create table tab1(col1 bigint ,col2 bigint ,col3 datetime(6) );
alter table tab1 add constraint tab1pk primary key(col1);
create index ind1 on tab1(col2);

-- 初期データセット
drop procedure proc1;

delimiter //
create procedure proc1(in x bigint)
begin
  declare i bigint;
  set i = 0;
  start transaction;
  while i < x do
    set i = i + 1;
    insert into tab1(col1,col2,col3) values(i,i,current_timestamp(6) );
  end while;
  commit;
end
//
delimiter ;

call proc1(1000000);


-- 継続的にデータ追加と更新
drop procedure proc2;

delimiter //
create procedure proc2()
begin
  declare i bigint;
  set i = (select max(col1) from tab1);
  start transaction;
  while true do
    set i = i + 1;
    insert into tab1(col1,col2,col3) values(i,i,current_timestamp(6) );
    update tab1 set col2 = floor(rand() * 1000000)+1 where col1 = floor(rand() * 1000000)+1;
    commit;
    do sleep(0.01);
    start transaction;
  end while;
  commit;
end
//
delimiter ;


call proc2();


-- ■[1]オンライン再定義


mysql -e "select count(*) from tab1;" test

pt-online-schema-change --execute  --alter "add column col4 varchar(10)" h=localhost,D=test,t=tab1

mysql -e "select count(*) from tab1;" test


No slaves found.  See --recursion-method if host mmm066 has slaves.
Not checking slave lag because no slaves were found and --check-slave-lag was not specified.
Operation, tries, wait:
  analyze_table, 10, 1
  copy_rows, 10, 0.25
  create_triggers, 10, 1
  drop_triggers, 10, 1
  swap_tables, 10, 1
  update_foreign_keys, 10, 1
Altering `test`.`tab1`...
Creating new table...
Created new table test._tab1_new OK.
Altering new table...
Altered `test`.`_tab1_new` OK.
2022-09-04T09:24:03 Creating triggers...
2022-09-04T09:24:03 Created triggers OK.
2022-09-04T09:24:03 Copying approximately 997442 rows...
Copying `test`.`tab1`:  77% 00:08 remain
2022-09-04T09:24:35 Copied rows OK.
2022-09-04T09:24:35 Analyzing new table...
2022-09-04T09:24:35 Swapping tables...
2022-09-04T09:24:35 Swapped original and new tables OK.
2022-09-04T09:24:35 Dropping old table...
2022-09-04T09:24:35 Dropped old table `test`.`_tab1_old` OK.
2022-09-04T09:24:35 Dropping triggers...
2022-09-04T09:24:35 Dropped triggers OK.
Successfully altered `test`.`tab1`.


定義確認

show create table tab1;

 

更新まち時間確認


select col1,timediff(col3,lag(col3) over(order by col1) ) from tab1
where col1 > 1000001
order by timediff(col3,lag(col3) over(order by col1) ) desc 
limit 10
;

+---------+-----------------------------------------------+
| col1    | timediff(col3,lag(col3) over(order by col1) ) |
+---------+-----------------------------------------------+
| 1000056 | 00:00:08.215585                               |
| 1000055 | 00:00:01.018387                               |
| 1000052 | 00:00:00.785414                               |
| 1000060 | 00:00:00.757824                               |
| 1000053 | 00:00:00.745839                               |
| 1000063 | 00:00:00.737877                               |
| 1000054 | 00:00:00.725609                               |
| 1000057 | 00:00:00.713841                               |
| 1000065 | 00:00:00.711029                               |
| 1000059 | 00:00:00.710176                               |
+---------+-----------------------------------------------+

select * from tab1 where col1 > 1000001 order by col1;

テーブル切り替え時、8秒程度更新まちとなる

 

-- ■[2]カラム追加(INSTANT)

alter table tab1 add column col4 varchar(10) ,ALGORITHM=INSTANT;


→ (0.58 sec)

更新まち時間確認


select col1,timediff(col3,lag(col3) over(order by col1) ) from tab1
where col1 > 1000001
order by timediff(col3,lag(col3) over(order by col1) ) desc 
limit 10
;

+---------+-----------------------------------------------+
| col1    | timediff(col3,lag(col3) over(order by col1) ) |
+---------+-----------------------------------------------+
| 1000030 | 00:00:00.767813                               |
| 1000034 | 00:00:00.721776                               |
| 1000031 | 00:00:00.719620                               |
| 1000042 | 00:00:00.714988                               |
| 1000032 | 00:00:00.710831                               |
| 1000049 | 00:00:00.708120                               |
| 1000036 | 00:00:00.707198                               |
| 1000046 | 00:00:00.706937                               |
| 1000033 | 00:00:00.706072                               |
| 1000041 | 00:00:00.703376                               |
+---------+-----------------------------------------------+

更新まちほぼなし


-- ■[3]カラム追加(INPLACE)

alter table tab1 add column col4 varchar(10) ,ALGORITHM=INPLACE;

→ (6.41 sec)


更新まち時間確認


select col1,timediff(col3,lag(col3) over(order by col1) ) from tab1
where col1 > 1000001
order by timediff(col3,lag(col3) over(order by col1) ) desc 
limit 10
;

+---------+-----------------------------------------------+
| col1    | timediff(col3,lag(col3) over(order by col1) ) |
+---------+-----------------------------------------------+
| 1000037 | 00:00:01.316871                               |
| 1000038 | 00:00:00.870233                               |
| 1000034 | 00:00:00.743590                               |
| 1000033 | 00:00:00.725118                               |
| 1000035 | 00:00:00.721162                               |
| 1000049 | 00:00:00.708549                               |
| 1000051 | 00:00:00.704501                               |
| 1000040 | 00:00:00.703096                               |
| 1000048 | 00:00:00.703080                               |
| 1000052 | 00:00:00.701528                               |
+---------+-----------------------------------------------+

1秒程度更新まちとなる


-- ■[4]カラム追加(COPY)

alter table tab1 add column col4 varchar(10) ,ALGORITHM=COPY;

→ (4.32 sec)


更新まち時間確認


select col1,timediff(col3,lag(col3) over(order by col1) ) from tab1
where col1 > 1000001
order by timediff(col3,lag(col3) over(order by col1) ) desc 
limit 10
;

+---------+-----------------------------------------------+
| col1    | timediff(col3,lag(col3) over(order by col1) ) |
+---------+-----------------------------------------------+
| 1000025 | 00:00:04.988967                               |
| 1000034 | 00:00:00.718311                               |
| 1000035 | 00:00:00.716203                               |
| 1000029 | 00:00:00.716112                               |
| 1000026 | 00:00:00.708156                               |
| 1000032 | 00:00:00.706651                               |
| 1000030 | 00:00:00.706439                               |
| 1000028 | 00:00:00.705421                               |
| 1000027 | 00:00:00.705292                               |
| 1000031 | 00:00:00.699694                               |
+---------+-----------------------------------------------+

カラム追加中更新まちとなる

 

(19c)
https://qiita.com/ora_gonsuke777/items/5754c18a6609ec295d8f

 

-- テーブル作成

drop table tab1 purge;
create table tab1(col1 int ,col2 int ,col3 timestamp);
alter table tab1 add constraint tab1pk primary key(col1);
create index ind1 on tab1(col2);


-- 初期データセット
declare
begin
for i in 1..10000000 loop
  insert into tab1(col1,col2,col3) values(i,i,systimestamp);
end loop;
end;
/

commit;


-- 継続的にデータ追加と更新

declare
i number;
begin
select max(col1) into i from tab1;
while true loop
  i := i+1;
  insert into tab1(col1,col2,col3) values(i,i,systimestamp);
  update tab1 set col2 = floor(dbms_random.value(1, 10000000) ) where col1 = floor(dbms_random.value(1, 10000000) );
  commit;
  dbms_lock.sleep(0.01);
end loop;
end;
/


-- ■[1]オンライン再定義

-- 1. 仮表作成

drop table tab1_new purge;
create table tab1_new(col1 int ,col2 int ,col3 timestamp);
alter table tab1_new add constraint tab1pk_new primary key(col1);
create index ind1_new on tab1_new(col2);


select count(*) from tab1;
select count(*) from tab1_new;

set time on
set timing on

-- 2. オンライン再定義可能かチェック

exec dbms_redefinition.can_redef_table(user, 'TAB1', dbms_redefinition.cons_use_pk);


-- 3. オンライン再定義を開始

exec dbms_redefinition.start_redef_table(user, 'TAB1', 'TAB1_NEW');

→ 経過: 00:00:30.58

-- 4. 仮表にカラム追加

alter table tab1_new add col4 varchar2(10) ;


→ 経過: 00:00:00.03

-- 5. 対象表と仮表の依存オブジェクト関連付け

begin
  dbms_redefinition.register_dependent_object(
      user                         -- uname
    , 'TAB1'                       -- orig_table
    , 'TAB1_NEW'                   -- int_table
    , dbms_redefinition.cons_index -- dep_type
    , user                         -- dep_owner
    , 'TAB1PK'                     -- dep_orig_name
    , 'TAB1PK_NEW'                 -- dep_int_name
  );
end;
/

begin
  dbms_redefinition.register_dependent_object(
      user                              -- uname
    , 'TAB1'                            -- orig_table
    , 'TAB1_NEW'                        -- int_table
    , dbms_redefinition.cons_constraint -- dep_type
    , user                              -- dep_owner
    , 'TAB1PK'                          -- dep_orig_name
    , 'TAB1PK_NEW'                      -- dep_int_name
  );
end;
/

begin
  dbms_redefinition.register_dependent_object(
      user                         -- uname
    , 'TAB1'                       -- orig_table
    , 'TAB1_NEW'                   -- int_table
    , dbms_redefinition.cons_index -- dep_type
    , user                         -- dep_owner
    , 'IND1'                       -- dep_orig_name
    , 'IND1_NEW'                   -- dep_int_name
  );
end;
/


-- 6. オンライン再定義をキャンセル

exec dbms_redefinition.abort_redef_table(user, 'TAB1', 'TAB1_NEW');

→ 経過: 00:00:00.60


-- 7. 対象表と仮表の同期
select count(*) from tab1;
select count(*) from tab1_new;

exec dbms_redefinition.sync_interim_table(user, 'TAB1', 'TAB1_NEW');

→ 経過: 00:00:01.03


-- 8. オンライン再定義を完了
select count(*) from tab1;
select count(*) from tab1_new;


exec dbms_redefinition.finish_redef_table(user, 'TAB1', 'TAB1_NEW');

select count(*) from tab1;
select count(*) from tab1_new;


→ 経過: 00:00:03.63

このプロシージャの実行時は、元の表が一時的にロックされます。

 

 

-- 9. 更新まち時間確認


select col1, col3 - lag(col3) over(order by col1)  from tab1
where col1 > 10000001
order by col3 - lag(col3) over(order by col1) desc nulls last
fetch first 10 rows only
;

      COL1 COL3-LAG(COL3)OVER(ORDERBYCOL1)
---------- ---------------------------------------------------------------------------
  10006247 +000000000 00:00:00.517028  -> finish_redef_table実行時の更新まち★
  10001800 +000000000 00:00:00.294029
  10001849 +000000000 00:00:00.244878
  10006324 +000000000 00:00:00.243451
  10001883 +000000000 00:00:00.231692
  10006323 +000000000 00:00:00.215534
  10002692 +000000000 00:00:00.205420
  10002688 +000000000 00:00:00.199196
  10001998 +000000000 00:00:00.196793
  10002686 +000000000 00:00:00.190752


テーブル切り替え時の更新まちは1秒以内


-- 10. 定義確認


select * from user_tables where table_name in ('TAB1','TAB1_NEW');

select * from user_constraints where table_name in ('TAB1','TAB1_NEW');

select * from user_indexes where table_name in ('TAB1','TAB1_NEW');

 

-- ■[2]カラム追加

alter table tab1 add col4 varchar2(10) ;

→ 経過: 00:00:00.14


更新まち時間確認
select col1, col3 - lag(col3) over(order by col1)  from tab1
where col1 > 10000001
order by col3 - lag(col3) over(order by col1) desc nulls last
fetch first 10 rows only
;

      COL1 COL3-LAG(COL3)OVER(ORDERBYCOL1)
---------- ---------------------------------------------------------------------------
  10001821 +000000000 00:00:00.045169
  10001082 +000000000 00:00:00.032013
  10001513 +000000000 00:00:00.028966
  10001486 +000000000 00:00:00.028494
  10001282 +000000000 00:00:00.028007
  10001503 +000000000 00:00:00.026638
  10001610 +000000000 00:00:00.026011
  10001532 +000000000 00:00:00.025983
  10000519 +000000000 00:00:00.025061
  10001523 +000000000 00:00:00.024440

更新まちほぼなし

 

 

(14)
https://github.com/shayonj/pg-osc

pg-osc

Requirements
 PostgreSQL 9.6 and later
 Ruby 2.6 and later
 Database user should have permissions for TRIGGER and/or a SUPERUSER

-- rubyのインストール

dnf update
dnf -y install git bzip2 gcc gcc-c++ openssl-devel readline-devel zlib-devel
dnf -y install epel-release
dnf -y install nodejs
git clone https://github.com/rbenv/rbenv.git /usr/local/rbenv
git clone https://github.com/rbenv/ruby-build.git /usr/local/rbenv/plugins/ruby-build

vim /etc/profile.d/rbenv.sh

export RBENV_ROOT=/usr/local/rbenv
export PATH=${RBENV_ROOT}/bin:$PATH
eval "$(rbenv init --no-rehash -)"

source /etc/profile.d/rbenv.sh

visudo

Defaults env_keep += "RBENV_ROOT"
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin:/usr/local/rbenv/bin:/usr/local/rbenv/shims

rbenv install -l
rbenv install 3.1.2
rbenv global 3.1.2

rbenv rehash
ruby --version


-- pg-oscのインストール


su - postgres

vim Gemfile

source 'https://rubygems.org'
gem 'pg_online_schema_change'


export PATH=$PATH:/usr/pgsql-14/bin
bundle install

bundle info pg_online_schema_change

export PATH=$PATH:/usr/local/rbenv/versions/3.1.2/bin
pg-online-schema-change help perform


-- テーブル作成

drop table tab1;
create table tab1(col1 bigint ,col2 bigint ,col3 timestamp);
alter table tab1 add constraint tab1pk primary key(col1);
create index ind1 on tab1(col2);


-- 初期データセット

insert into tab1(col1,col2,col3)  select g,g,clock_timestamp() from generate_series(1,10000000) g;


-- 継続的にデータ追加と更新

do $$
declare
i bigint;
begin
  select max(col1) into STRICT i from tab1;
  while true loop
    i := i+1;
    insert into tab1(col1,col2,col3) values(i,i,clock_timestamp() );
    update tab1 set col2 = floor(random() * 10000000)+1 where col1 = floor(random() * 10000000)+1;
    commit;
    PERFORM pg_sleep(0.01);
  end loop;
end;
$$;

 

-- ■[1]オンライン再定義

export PGPASSWORD="postgres"
pg-online-schema-change perform \
--alter-statement 'alter table tab1 add col4 varchar(10)' \
--dbname "test" \
--host "localhost" \
--username "postgres" \
--drop


{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":40,"time":"2022-09-04T11:01:10.898+09:00","v":0,"msg":"DEPRECATED: -w is deprecated. Please pass PGPASSWORD environment variable instead.","version":"0.7.1"}
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:01:12.645+09:00","v":0,"msg":"Setting up audit table","audit_table":"pgosc_at_tab1_570ebd","version":"0.7.1"}
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:01:13.849+09:00","v":0,"msg":"Setting up triggers","version":"0.7.1"}
WARNING:  there is already a transaction in progress
WARNING:  there is already a transaction in progress
WARNING:  there is no transaction in progress
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:01:13.864+09:00","v":0,"msg":"Setting up shadow table","shadow_table":"pgosc_st_tab1_570ebd","version":"0.7.1"}
WARNING:  there is already a transaction in progress
WARNING:  there is already a transaction in progress
WARNING:  there is already a transaction in progress
WARNING:  there is already a transaction in progress
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:01:14.032+09:00","v":0,"msg":"Running alter statement on shadow table","shadow_table":"pgosc_st_tab1_570ebd","parent_table":"tab1","version":"0.7.1"}
WARNING:  there is already a transaction in progress
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:01:14.033+09:00","v":0,"msg":"Clearing contents of audit table before copy..","shadow_table":"pgosc_st_tab1_570ebd","parent_table":"tab1","version":"0.7.1"}
WARNING:  there is already a transaction in progress
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:01:14.033+09:00","v":0,"msg":"Copying contents..","shadow_table":"pgosc_st_tab1_570ebd","parent_table":"tab1","version":"0.7.1"}
WARNING:  there is already a transaction in progress
WARNING:  there is already a transaction in progress
WARNING:  there is already a transaction in progress
WARNING:  there is no transaction in progress
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:02:29.510+09:00","v":0,"msg":"Performing ANALYZE!","version":"0.7.1"}
INFO:  analyzing "public.tab1"
INFO:  "tab1": scanned 30000 of 63696 pages, containing 4709845 live rows and 36 dead rows; 30000 rows in sample, 9999943 estimated total rows
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:02:31.154+09:00","v":0,"msg":"Replaying rows, count: 105","version":"0.7.1"}
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:02:32.990+09:00","v":0,"msg":"Remaining rows below delta count, proceeding towards swap","version":"0.7.1"}
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:02:32.990+09:00","v":0,"msg":"Performing swap!","version":"0.7.1"}
WARNING:  there is already a transaction in progress
WARNING:  there is already a transaction in progress
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:02:33.770+09:00","v":0,"msg":"Replaying rows, count: 4","version":"0.7.1"}
WARNING:  there is already a transaction in progress
WARNING:  there is already a transaction in progress
WARNING:  there is already a transaction in progress
WARNING:  there is already a transaction in progress
WARNING:  there is no transaction in progress
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:02:33.777+09:00","v":0,"msg":"Performing ANALYZE!","version":"0.7.1"}
INFO:  analyzing "public.tab1"
INFO:  "tab1": scanned 30000 of 63696 pages, containing 4709840 live rows and 23 dead rows; 30000 rows in sample, 9999932 estimated total rows
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:02:39.754+09:00","v":0,"msg":"Validating constraints!","version":"0.7.1"}
{"name":"pg-online-schema-change","hostname":"mmm070","pid":3995,"level":30,"time":"2022-09-04T11:02:40.124+09:00","v":0,"msg":"All tasks successfully completed","version":"0.7.1"}


更新まち時間確認
select col1, col3 - lag(col3) over(order by col1)  from tab1
where col1 > 10000001
order by col3 - lag(col3) over(order by col1) desc nulls last
limit 10
;

   col1   |    ?column?
----------+-----------------
 10000075 | 00:00:07.901357
 10000032 | 00:00:02.002951
 10000042 | 00:00:01.979979
 10000069 | 00:00:01.942204
 10000057 | 00:00:01.933931
 10000043 | 00:00:01.930007
 10000041 | 00:00:01.888046
 10000068 | 00:00:01.886958
 10000049 | 00:00:01.873002
 10000052 | 00:00:01.853888


8秒程度の更新待ち発生

インデックス名が変化するので再設定必要

alter index pgosc_st_tab1_570ebd_pkey rename to tab1pk;
alter index pgosc_st_tab1_570ebd_col2_idx rename to ind1;

 


-- ■[2]カラム追加

\timing 1

select count(*) from tab1;
alter table tab1 add col4 varchar(10);
select count(*) from tab1;


→ 時間: 673.829 ミリ秒

更新まち時間確認
select col1, col3 - lag(col3) over(order by col1)  from tab1
where col1 > 10000001
order by col3 - lag(col3) over(order by col1) desc nulls last
limit 10
;

   col1   |    ?column?
----------+-----------------
 10000012 | 00:00:01.366635
 10000010 | 00:00:01.126939
 10000011 | 00:00:01.070448
 10000004 | 00:00:00.897543
 10000006 | 00:00:00.841607
 10000007 | 00:00:00.839127
 10000008 | 00:00:00.832665
 10000005 | 00:00:00.826167
 10000009 | 00:00:00.826099
 10000017 | 00:00:00.826012
 

select  col1, col3 - lag(col3) over(order by col1) from tab1
where col1 > 10000001
order by col1;


更新待ちはほぼなし
※カラム追加実施後に0.5秒程度更新待ちの増加がある

 

 

 


(2019)


-- テーブル作成

drop table tab1;
create table tab1(col1 bigint not null,col2 bigint ,col3 datetime2);
alter table tab1 add constraint tab1pk primary key(col1);
create index ind2 on tab1(col2);


-- 初期データセット

set nocount on
declare @i bigint;
set @i = 1;
begin transaction;
while @i <= 10000000
begin
  insert into tab1(col1,col2,col3) values(@i,@i,getdate() );
  set @i = @i + 1;
end
commit;


-- 継続的にデータ追加と更新

set nocount on
declare @i bigint;
set @i = (select max(col1) from tab1);
begin transaction;
while 1 = 1
begin
  set @i = @i + 1;
  insert into tab1(col1,col2,col3) values(@i,@i,getdate() );
  update tab1 set col2 = floor(rand() * 10000000)+1 where col1 = floor(rand() * 10000000)+1;
  commit;
  waitfor delay '00:00:00.01';
  begin transaction;
end
commit;

 


-- ■[1]オンライン再定義
調べた限り、オンライン再定義のツールなし

 

-- ■[2]カラム追加

set statistics time on

alter table tab1 add col4 varchar(10) ;


→ 経過時間 = 1 ミリ秒。


更新まち時間確認
select top 10 
col1,datediff(millisecond, lag(col3) over(order by col1), col3 )  from tab1
where col1 > 10000001
order by datediff(millisecond, lag(col3) over(order by col1), col3 )  desc

10002885    94
10002886    90
10002551    63
10000735    60
10002187    60
10000737    57
10001194    47
10000736    43
10001188    43
10000048    40

更新まちほぼなし