You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
When the _xxxx_gho table is queried, data may be lost. #887
gh-ost Refer to this document for switching order :#82
Suppose now operating DDL on table a, three temporary tables _a_del, _a_gho, and_a_ghc will be generated.
rename operation is: rename a to _a_del, _a_gho to a,
lock table statement is: lock table a write, _a_del write
According to the order of acquiring the lock:
first acquire the _a_del table lock,
then _a_gho table lock ,
and finally the a table lock.
There seems to be a corruption in the original comment. On my mobile GitHub app, I can actually see what the question is. Copy+pasting here (as is, including typos):
If there is a query transaction on the _a_gho table lock that causes the acquisition if the lock to be delayed, other threads may be before rename First obtain the lock of table a, write the data in table, and then perform table rename operation, at this time the data is lost
This situation does not happen. First, gh-ost locks the original table, then, it waits until the _gho table has received all the writes from the original table up to the lock. From that point, there can be no mor ewrites on _gho table. gh-ost then proceeds to lock _gho table.
DDL statements, LOCK TABLES, and other similar statements try to reduce the number of possible deadlocks between concurrent DDL statements by acquiring locks on explicitly named tables in name order.
when does this statement run? In gh-ost, this statement only runs after_gho table is known to have received the insert from a. Did you mimic this behavior?
DDL statements, LOCK TABLES, and other similar statements try to reduce the number of possible deadlocks between concurrent DDL statements by acquiring locks on explicitly named tables in name order.
I'm not sure what this quote means to this issue's context. Can you please clarify?
when does this statement run? In gh-ost, this statement only runs after_gho table is known to have received the insert from a. Did you mimic this behavior?
Yes. Rename statement happened after all data sync from table a.
Here the insert operation is from APP SERVERS, this operation comes after gh-ost have got the write lock, normally this insert should be blocked until rename finished.
In our case, there are other sessions acces to the _gho table (got one transaction start before unlock tables , and not finished when unlock), because rename table a to _a_del,_a_gho to a acquiring locks on explicitly named tables in name order, when there got a trx on the _gho table, rename is waiting for the trx on _gho table to commit before acquire table a lock , this cause the insert from APP SERVERS run before rename finished(which should blocked after rename ok), data lost happend here.
Session1
session2
Session3
Session4
create table a (id int); create table _a_del(id int); create table _a_gho(id int); lock table a write,_a_del write; #OK
insert into a set id=1; #blocked
rename table a to _a_del, _a_gho to a; #blocked #acquiring locks order: _a_del, _a_gho, a #waiting lock of: _a_del
#now table a with no WRITE lock #session 2 insert may insert to table a
commit;
—OK
select * from a; Empty set (0.00 sec)
select * from _a_del; +------+ | id | +------+ | 1 | +------+ 1 row in set (0.01 sec)
DDL statements, LOCK TABLES, and other similar statements try to reduce the number of possible deadlocks between concurrent DDL statements by acquiring locks on explicitly named tables in name order.
I'm not sure what this quote means to this issue's context. Can you please clarify?
This is the acquiring locks order of DDL statement from MySQL manual.
I'm meanwhile curious: who would be reading from _a_gho? This table is unknown to anyone outside gh-ost itself or the engineer that runs the migration.
I'm meanwhile curious: who would be reading from _a_gho? This table is unknown to anyone outside gh-ost itself or the engineer that runs the migration.
--
DB manage program such as table checksum/data replication program/migration table check may access all tables in MySQL.
Table _a_gho is unkown to user, but known for DBAs ,we think this read comes from DBA's program:)
We met a case two days ago which cause a table lost one row after ALTER table through gh-ost.
This is the first time we meet this case, after check ,we think there maybe a risk of data loss if someone access the _gho table(get lock on _a_gho table before unlock tables command) when cut-over。
Thanks for you reply ,waiting for your review result.
I'm meanwhile curious: who would be reading from _a_gho? This table is unknown to anyone outside gh-ost itself or the engineer that runs the migration.
hi Shlomi-noach , is there anly update for the case please ^_^?
Sorry to bother, but we look forward fix this ASAP for there is a risk of lossing data, we run gh-ost doing DDL every day。
A fix for the case above we think is just change the _a_ghc and _a_gho to another name which name order is after table a, so the rename waiting to get the lock , there would no time "gap" for DML to get lock and run between unlock tables and the rename 。
I'm not sure I will be able to provide a fix in the next few days.
A fix for the case above we think is just change the _a_ghc and _a_gho to another name which name order is after table a, so the rename waiting to get the lock , there would no time "gap" for DML to get lock and run between unlock tables and the rename 。
I'm not sure I understand exactly what you mean. Can you please paste the code change? Better yet, if you think you have the correct solution, could you create a pull request?
session1 session2 session3 session4
lock tables t write, tdel write;
OK
insert into t values(null,13);#blocked
#blocked rename table t to tdel, tgho to t;
#blocked
#blocked #blocked begin;
select * from tgho;
#blocked #blocked OK, transaction open
drop table tdel; #blocked #blocked transaction open
OK #blocked #blocked transaction open
unlock tables; #blocked #blocked transaction open
OK #blocked #blocked transaction open
#blocked #blocked commit;
OK OK OK
select * from t;
+----+------+
| id | n |
+----+------+
| 1 | 13 |
+----+------+
elect * from tdel;
Empty set (0.01 sec)
Still looking into why it behaves different than yours, and correctly so.
The prefix of tables' name in your test is different with me ,that's the biggest difference.
You can change the tdel to _t_del,tgho to _t_gho and try again, you'll get a defferent result.
The reason why tables' name affect the result is metioned in the manual:
https://dev.mysql.com/doc/refman/8.0/en/metadata-locking.html
DDL statements, LOCK TABLES, and other similar statements try to reduce the number of possible deadlocks between concurrent DDL statements by acquiring locks on explicitly named tables in name order.
The DEL/GHO tables' name are in different order, as follow:
+---------------+
| Tables_in_sg2 |
+---------------+
| t |
| tdel |
| tgho |
+---------------+
3 rows in set (0.00 sec)
+---------------+
| Tables_in_sg3 |
+---------------+
| _t_del |
| _t_gho |
| t |
+---------------+
3 rows in set (0.00 sec)
In database sg2 ,the table t comes first in table name order , and in database sg3, the table t comes last.
The name order affect the acquiring locks order in rename operation.
When you use t,tdel,tgho, the rename operation try acquire lock on table t first, after session1 unlock tables, the rename session get the table lock first; but when you use t,_t_del,_t_gho, when you lock table t,_t_del in session1, the rename operation try acquire lock on _t_del first(waiting for the lock of _t_del table), and after session1 drop table _t_del, the rename try to acquire lock on _t_gho table, and if there is some transaction on the _t_gho table, the rename is waiting for _t_gho lock, if now session1 unlock tables, the rename still waiting for _g_gho until the transaction on _t_gho end, and that the problem happen: the insert in session2 could get the lock on t and could insert success.
Don't know if i made it clear,the table name order affect the test result.
---the variables:
> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set (0.00 sec)
> select @@sql_mode;
+-------------------------------------------------------------------------------------------------------------------------------------------+
| @@sql_mode |
+-------------------------------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+-------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
> select @@version;
+------------------+
| @@version |
+------------------+
| 5.7.18|
+------------------+
1 row in set (0.00 sec)
Metadata locks on tables are acquired in name order, which in some cases can make a difference in operation outcome when multiple transactions execute concurrently. See Section 8.11.4, “Metadata Locking”.
Metadata locks on tables are acquired in name order, which in some cases can make a difference in operation outcome when multiple transactions execute concurrently. See Section 8.11.4, “Metadata Locking”.
I'll keep looking at the implications.
Hi,shlomi, is there any update here ? Look forward the fix for this case :)
Again, sorry for the delay. I took some time to review this issue again and can confirm it is as you describe. I appreciate your findings and analysis. OK, so there's an immediate solution for you (sorry, I could have figured it out much sooner), and then we need to consider long term solutions.
Immediate term
use e.g.: gh-ost --force-table-name=zzz_8e57cbfa9eabd7d50d0e ..., where:
zzz is to ensure table name is greater than your real table name (assuming you don't have tables called zzz*
8e57cbfa9eabd7d50d0e is some random value generated by application, e.g. via openssl rand -hex 10
This will ensure gh-ost creates the following tables:
zzz_8e57cbfa9eabd7d50d0e_gho
zzz_8e57cbfa9eabd7d50d0e_ghc
zzz_8e57cbfa9eabd7d50d0e_del
long term
Is a bit of a problem. _ is a common prefix for "temporary" or "internal" tables. And it's a valid ascii character which does not require escaping. In the ascii table, z is only followed by {, '|', '}' and '~'.. None of which are valid characters for a non-escaped table.
So I'm going to bang my head a bit around fixing the problem without modifying the prefix _. This will not be easy and I'mn not sure if I can crack it. The trick for atomic table renames was complex enough without this added constraint.
Cross fingers.
Oh, another alternative is to use -cut-over=two-step, which leaves a gap in time when the table does not exist... It's a compromise.
Sorry, my suggested short term solution above will not work as I hoped, because right now gh-ost always prepends a _ to the table name. This would be an easy fix if needed.
I had discussed the short term solution with @shatianjiang , maybe there is still exists risk of lost data when there got a table named 'zzzzzXXX'(with 'zzzz' prefix...), but it would work most of time:)
So look forward the long term fix for this issue.
And we're keep finding a solution also, if there is any progress ,we'll update here.
@zhongliangkang Even if the _a_gho table is not queried, I have also encountered a scenario where data is lost in the rename phase. Because in the rename phase, the order acquires table locks is: _a_del, _a_gho, a. When gh-ost acquired _a_del table lock and try to _a_gho table lock, although the time is very short, it is also possible that the a table has dml SQL to be allowed to execute.
I think the following methods may be solve it:
after drop table _a_del then time.sleep(100ms), finally unlock tables
For the generation of the gh-ost table of gh-ost, use ascii ~ to replace _, such as ~a_gho replace _a_gho
@cenkore For pull code #987 , I think it is very good, but I am not sure why code not be merged?
@shlomi-noach For "I'll likely add a simple option (e.g. --force-table-name-prefix or something) to control that" having any update?
Activity
shlomi-noach commentedon Oct 13, 2020
There seems to be a corruption in the original comment. On my mobile GitHub app, I can actually see what the question is. Copy+pasting here (as is, including typos):
This situation does not happen. First, gh-ost locks the original table, then, it waits until the
_ghotable has received all the writes from the original table up to the lock. From that point, there can be no mor ewrites on_ghotable.gh-ostthen proceeds to lock_ghotable.wukongHH commentedon Oct 13, 2020
How to repeat :
mysql> select * from a;
Empty set (0.00 sec)
mysql> select * from _a_del;
+------+
| id |
+------+
| 1 |
+------+
1 row in set (0.00 sec)
wukongHH commentedon Oct 13, 2020
https://dev.mysql.com/doc/refman/8.0/en/metadata-locking.html
DDL statements, LOCK TABLES, and other similar statements try to reduce the number of possible deadlocks between concurrent DDL statements by acquiring locks on explicitly named tables in name order.
brightgogo commentedon Oct 13, 2020
Sorry for accidentally closed, reopen.
shlomi-noach commentedon Oct 13, 2020
@wukongHH
when does this statement run? In
gh-ost, this statement only runs after_ghotable is known to have received theinsertfroma. Did you mimic this behavior?I'm not sure what this quote means to this issue's context. Can you please clarify?
zhongliangkang commentedon Oct 13, 2020
Yes. Rename statement happened after all data sync from table
a.Here the insert operation is from APP SERVERS, this operation comes after gh-ost have got the write lock, normally this insert should be blocked until rename finished.
In our case, there are other sessions acces to the
_ghotable (got one transaction start beforeunlock tables, and not finished when unlock), becauserename table a to _a_del,_a_gho to aacquiring locks on explicitly named tables in name order, when there got a trx on the_ghotable,renameis waiting for the trx on_ghotable to commit before acquire tablealock , this cause theinsertfrom APP SERVERS run before rename finished(which should blocked after rename ok), data lost happend here.create table _a_del(id int);
create table _a_gho(id int);
lock table a write,_a_del write; #OK
#blocked
#acquiring locks order: _a_del, _a_gho, a
#waiting lock of: _a_del
select * from _a_gho;
#waiting lock of: _a_gho ,cause session 4 trx not commited
awith no WRITE lock#session 2 insert may insert to table
aEmpty set (0.00 sec)
+------+
| id |
+------+
| 1 |
+------+
1 row in set (0.01 sec)
This is the acquiring locks order of DDL statement from MySQL manual.
shlomi-noach commentedon Oct 13, 2020
Thank you, I will look into it.
I'm meanwhile curious: who would be reading from
_a_gho? This table is unknown to anyone outsidegh-ostitself or the engineer that runs the migration.zhongliangkang commentedon Oct 13, 2020
--
DB manage program such as table checksum/data replication program/migration table check may access all tables in MySQL.
Table
_a_ghois unkown to user, but known for DBAs ,we think this read comes from DBA's program:)We met a case two days ago which cause a table lost one row after ALTER table through gh-ost.
This is the first time we meet this case, after check ,we think there maybe a risk of data loss if someone access the _gho table(get lock on
_a_ghotable beforeunlock tablescommand) when cut-over。Thanks for you reply ,waiting for your review result.
zhongliangkang commentedon Oct 21, 2020
hi Shlomi-noach , is there anly update for the case please ^_^?
Sorry to bother, but we look forward fix this ASAP for there is a risk of lossing data, we run gh-ost doing DDL every day。
A fix for the case above we think is just change the
_a_ghcand_a_ghoto another name which name order is after tablea, so therenamewaiting to get the lock , there would no time "gap" for DML to get lock and run betweenunlock tablesand therename。Thanks.
shlomi-noach commentedon Oct 21, 2020
Hi,
I'm not sure I will be able to provide a fix in the next few days.
I'm not sure I understand exactly what you mean. Can you please paste the code change? Better yet, if you think you have the correct solution, could you create a pull request?
I'm not the owner of https://github.com/github/gh-ost and will not be able to merge any changes here. But I may merge such changes in https://github.com/openark/gh-ost
shlomi-noach commentedon Nov 9, 2020
I can confirm I get the same behavior as yours, but something's strange here:
At first I thought this might be related to the fact your table doesn;t have a
PRIMARY KEY; I added aPRIMARY KEYand I'm still getting your results.I then tries a different table structure, and I got different results, which are good. See below.
Can you please clarify what is the output of your:
I'm using the following:
Here's a different table definition which works correctly:
#blocked
select * from tgho;
Still looking into why it behaves different than yours, and correctly so.
shlomi-noach commentedon Nov 9, 2020
it's not the
AUTO_INCREMENT. Still trying to understand what.shlomi-noach commentedon Nov 9, 2020
Sorry, I did not execute the
renameand theselect * from tghoin the same order as you did.zhongliangkang commentedon Nov 9, 2020
The prefix of tables' name in your test is different with me ,that's the biggest difference.
You can change the
tdelto_t_del,tghoto_t_ghoand try again, you'll get a defferent result.The reason why tables' name affect the result is metioned in the manual:
The DEL/GHO tables' name are in different order, as follow:
In database sg2 ,the table
tcomes first in table name order , and in database sg3, the tabletcomes last.The name order affect the acquiring locks order in
renameoperation.When you use
t,tdel,tgho, therenameoperation try acquire lock on tabletfirst, after session1 unlock tables, the rename session get the table lock first; but when you uset,_t_del,_t_gho, when you lock tablet,_t_delin session1, therenameoperation try acquire lock on_t_delfirst(waiting for the lock of_t_deltable), and after session1 drop table_t_del, therenametry to acquire lock on_t_ghotable, and if there is some transaction on the_t_ghotable, the rename is waiting for_t_gholock, if now session1 unlock tables, therenamestill waiting for_g_ghountil the transaction on_t_ghoend, and that the problem happen: theinsertin session2 could get the lock ontand could insert success.Don't know if i made it clear,the table name order affect the test result.
---the variables:
shlomi-noach commentedon Nov 9, 2020
WOW. OK, let me digest this and see how to proceed.
shlomi-noach commentedon Nov 9, 2020
from https://dev.mysql.com/doc/refman/5.7/en/rename-table.html:
I'll keep looking at the implications.
zhongliangkang commentedon Nov 30, 2020
Hi,shlomi, is there any update here ? Look forward the fix for this case :)
shlomi-noach commentedon Dec 10, 2020
Sorry for the delays, I hope to give this a shot by end of month
shlomi-noach commentedon Dec 30, 2020
Again, sorry for the delay. I took some time to review this issue again and can confirm it is as you describe. I appreciate your findings and analysis. OK, so there's an immediate solution for you (sorry, I could have figured it out much sooner), and then we need to consider long term solutions.
Immediate term
use e.g.:
gh-ost --force-table-name=zzz_8e57cbfa9eabd7d50d0e ..., where:zzzis to ensure table name is greater than your real table name (assuming you don't have tables calledzzz*8e57cbfa9eabd7d50d0eis some random value generated by application, e.g. viaopenssl rand -hex 10This will ensure
gh-ostcreates the following tables:zzz_8e57cbfa9eabd7d50d0e_ghozzz_8e57cbfa9eabd7d50d0e_ghczzz_8e57cbfa9eabd7d50d0e_dellong term
Is a bit of a problem.
_is a common prefix for "temporary" or "internal" tables. And it's a valid ascii character which does not require escaping. In the ascii table,zis only followed by{, '|', '}' and '~'.. None of which are valid characters for a non-escaped table.So I'm going to bang my head a bit around fixing the problem without modifying the prefix
_. This will not be easy and I'mn not sure if I can crack it. The trick for atomic table renames was complex enough without this added constraint.Cross fingers.
Oh, another alternative is to use
-cut-over=two-step, which leaves a gap in time when the table does not exist... It's a compromise.shlomi-noach commentedon Dec 30, 2020
Sorry, my suggested short term solution above will not work as I hoped, because right now
gh-ostalways prepends a_to the table name. This would be an easy fix if needed.zhongliangkang commentedon Dec 31, 2020
Thanks for your solutions 👍
I had discussed the short term solution with @shatianjiang , maybe there is still exists risk of lost data when there got a table named 'zzzzzXXX'(with 'zzzz' prefix...), but it would work most of time:)
So look forward the long term fix for this issue.
And we're keep finding a solution also, if there is any progress ,we'll update here.
Thanks again.
shlomi-noach commentedon Dec 31, 2020
The change you'd need to make for short term solution is in:
gh-ost/go/base/context.go
Lines 280 to 286 in c681c54
As you can see
gh-ostalways prepends_to table names. I'll likely add a simple option (e.g.--force-table-name-prefixor something) to control that.rudy-gao commentedon Jan 11, 2022
@zhongliangkang Even if the
_a_ghotable is not queried, I have also encountered a scenario where data is lost in the rename phase. Because in the rename phase, the order acquires table locks is:_a_del, _a_gho, a. When gh-ost acquired_a_deltable lock and try to_a_ghotable lock, although the time is very short, it is also possible that theatable has dml SQL to be allowed to execute.I think the following methods may be solve it:
_a_delthen time.sleep(100ms), finally unlock tables~to replace_, such as~a_ghoreplace_a_gho@cenkore For pull code #987 , I think it is very good, but I am not sure why code not be merged?
@shlomi-noach For "I'll likely add a simple option (e.g. --force-table-name-prefix or something) to control that" having any update?
zhongliangkang commentedon Jan 25, 2022
Yes. Even if no query on the _gho table, data may lost, add a query make it easy to reproduce.
We fix it by add 'zz' prefix temporary..
#987 may need more deep thinking..
abaowhy commentedon May 2, 2025
I have resove this issue。#1536