mirror of
https://github.com/MariaDB/server.git
synced 2025-08-08 11:22:35 +03:00
MDEV-36017 Alter table aborts when temporary directory is full
Problem: ======= - In 10.11, During Copy algorithm, InnoDB does use bulk insert for row by row insert operation. When temporary directory ran out of memory, row_mysql_handle_errors() fails to handle DB_TEMP_FILE_WRITE_FAIL. - During inplace algorithm, concurrent DML fails to write the log operation into the temporary file. InnoDB fail to mark the error for the online log. - ddl_log_write() releases the global ddl lock prematurely before release the log memory entry Fix: === row_mysql_handle_errors(): Rollback the transaction when InnoDB encounters DB_TEMP_FILE_WRITE_FAIL convert_error_code_to_mysql(): Report an aborted transaction when InnoDB encounters DB_TEMP_FILE_WRITE_FAIL during alter table algorithm=copy or innodb bulk insert operation row_log_online_op(): Mark the error in online log when InnoDB ran out of temporary space fil_space_extend_must_retry(): Mark the os_has_said_disk_full as true if os_file_set_size() fails btr_cur_pessimistic_update(): Return error code when btr_cur_pessimistic_insert() fails ddl_log_write(): Release the global ddl lock after releasing the log memory entry when error was encountered btr_cur_optimistic_update(): Relax the assertion that blob pointer can be null during rollback because InnoDB can ran out of space while allocating the external page ha_innobase::extra(): Rollback the transaction during DDL before calling convert_error_code_to_mysql(). row_undo_mod_upd_exist_sec(): Remove the assertion which says that InnoDB should fail to build index entry when rollbacking an incomplete transaction after crash recovery. This scenario can happen when InnoDB ran out of space. row_upd_changes_ord_field_binary_func(): Relax the assertion to make that externally stored field can be null when InnoDB ran out of space.
This commit is contained in:
47
mysql-test/suite/innodb/r/alloc_fail.result
Normal file
47
mysql-test/suite/innodb/r/alloc_fail.result
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
#
|
||||||
|
# MDEV-36017 Alter table aborts when temporary
|
||||||
|
# directory is full
|
||||||
|
#
|
||||||
|
SET SESSION DEFAULT_STORAGE_ENGINE=InnoDB;
|
||||||
|
CREATE TABLE t1(f1 CHAR(100) NOT NULL, f2 CHAR(100) NOT NULL,
|
||||||
|
f3 CHAR(100) NOT NULL, f4 CHAR(100) NOT NULL,
|
||||||
|
f5 CHAR(100) NOT NULL)ENGINE=InnoDB;
|
||||||
|
INSERT INTO t1 SELECT 'a', 'b', 'c', 'd', 'e' FROM seq_1_to_65536;
|
||||||
|
SET STATEMENT DEBUG_DBUG="+d,write_to_tmp_file_fail" FOR
|
||||||
|
CREATE TABLE t2 as SELECT * FROM t1;
|
||||||
|
ERROR HY000: Got error 59 'Temp file write failure' from InnoDB
|
||||||
|
DROP TABLE t1;
|
||||||
|
CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(100),
|
||||||
|
f3 CHAR(100))ENGINE=InnoDB;
|
||||||
|
INSERT INTO t1 SELECT seq, 'a', 'b' FROM seq_1_to_1024;
|
||||||
|
SET STATEMENT DEBUG_DBUG="+d,write_to_tmp_file_fail" FOR
|
||||||
|
ALTER TABLE t1 FORCE, ALGORITHM=COPY;
|
||||||
|
ERROR HY000: Got error 59 'Temp file write failure' from InnoDB
|
||||||
|
DROP TABLE t1;
|
||||||
|
CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(100),
|
||||||
|
f3 CHAR(100))ENGINE=InnoDB;
|
||||||
|
INSERT INTO t1 SELECT seq, 'a', 'b' FROM seq_1_to_4096;
|
||||||
|
SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit";
|
||||||
|
ALTER TABLE t1 ADD KEY(f1), ADD INDEX(f3(10));
|
||||||
|
connect con1,localhost,root,,,;
|
||||||
|
SET DEBUG_SYNC="now WAIT_FOR dml_start";
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO t1 SELECT * FROM t1;
|
||||||
|
SET STATEMENT DEBUG_DBUG="+d,os_file_write_fail" FOR COMMIT;
|
||||||
|
SET DEBUG_SYNC="now SIGNAL dml_commit";
|
||||||
|
connection default;
|
||||||
|
ERROR HY000: Temporary file write failure
|
||||||
|
disconnect con1;
|
||||||
|
CHECK TABLE t1;
|
||||||
|
Table Op Msg_type Msg_text
|
||||||
|
test.t1 check status OK
|
||||||
|
DROP TABLE t1;
|
||||||
|
SET STATEMENT DEBUG_DBUG="+d,ddl_log_write_fail" FOR
|
||||||
|
CREATE TABLE t1(f1 INT NOT NULL)ENGINE=InnoDB;
|
||||||
|
DROP TABLE t1;
|
||||||
|
CREATE TABLE t1(f1 TEXT, index(f1(2)))ENGINE=InnoDB;
|
||||||
|
INSERT INTO t1 VALUES('a');
|
||||||
|
set statement DEBUG_DBUG="+d,btr_page_alloc_fail" for
|
||||||
|
UPDATE t1 set f1= REPEAT('b', 12000);
|
||||||
|
ERROR HY000: The table 't1' is full
|
||||||
|
DROP TABLE t1;
|
2
mysql-test/suite/innodb/t/alloc_fail.opt
Normal file
2
mysql-test/suite/innodb/t/alloc_fail.opt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
--innodb_sort_buffer_size=64k
|
||||||
|
--innodb_rollback_on_timeout=1
|
55
mysql-test/suite/innodb/t/alloc_fail.test
Normal file
55
mysql-test/suite/innodb/t/alloc_fail.test
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
--source include/have_innodb.inc
|
||||||
|
--source include/have_sequence.inc
|
||||||
|
--source include/have_debug.inc
|
||||||
|
--echo #
|
||||||
|
--echo # MDEV-36017 Alter table aborts when temporary
|
||||||
|
--echo # directory is full
|
||||||
|
--echo #
|
||||||
|
SET SESSION DEFAULT_STORAGE_ENGINE=InnoDB;
|
||||||
|
CREATE TABLE t1(f1 CHAR(100) NOT NULL, f2 CHAR(100) NOT NULL,
|
||||||
|
f3 CHAR(100) NOT NULL, f4 CHAR(100) NOT NULL,
|
||||||
|
f5 CHAR(100) NOT NULL)ENGINE=InnoDB;
|
||||||
|
INSERT INTO t1 SELECT 'a', 'b', 'c', 'd', 'e' FROM seq_1_to_65536;
|
||||||
|
--error ER_GET_ERRMSG
|
||||||
|
SET STATEMENT DEBUG_DBUG="+d,write_to_tmp_file_fail" FOR
|
||||||
|
CREATE TABLE t2 as SELECT * FROM t1;
|
||||||
|
DROP TABLE t1;
|
||||||
|
|
||||||
|
CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(100),
|
||||||
|
f3 CHAR(100))ENGINE=InnoDB;
|
||||||
|
INSERT INTO t1 SELECT seq, 'a', 'b' FROM seq_1_to_1024;
|
||||||
|
--error ER_GET_ERRMSG
|
||||||
|
SET STATEMENT DEBUG_DBUG="+d,write_to_tmp_file_fail" FOR
|
||||||
|
ALTER TABLE t1 FORCE, ALGORITHM=COPY;
|
||||||
|
DROP TABLE t1;
|
||||||
|
|
||||||
|
CREATE TABLE t1(f1 INT NOT NULL, f2 CHAR(100),
|
||||||
|
f3 CHAR(100))ENGINE=InnoDB;
|
||||||
|
INSERT INTO t1 SELECT seq, 'a', 'b' FROM seq_1_to_4096;
|
||||||
|
SET DEBUG_SYNC="inplace_after_index_build SIGNAL dml_start WAIT_FOR dml_commit";
|
||||||
|
SEND ALTER TABLE t1 ADD KEY(f1), ADD INDEX(f3(10));
|
||||||
|
|
||||||
|
connect(con1,localhost,root,,,);
|
||||||
|
SET DEBUG_SYNC="now WAIT_FOR dml_start";
|
||||||
|
BEGIN;
|
||||||
|
INSERT INTO t1 SELECT * FROM t1;
|
||||||
|
SET STATEMENT DEBUG_DBUG="+d,os_file_write_fail" FOR COMMIT;
|
||||||
|
SET DEBUG_SYNC="now SIGNAL dml_commit";
|
||||||
|
|
||||||
|
connection default;
|
||||||
|
--error ER_TEMP_FILE_WRITE_FAILURE
|
||||||
|
reap;
|
||||||
|
disconnect con1;
|
||||||
|
CHECK TABLE t1;
|
||||||
|
DROP TABLE t1;
|
||||||
|
|
||||||
|
SET STATEMENT DEBUG_DBUG="+d,ddl_log_write_fail" FOR
|
||||||
|
CREATE TABLE t1(f1 INT NOT NULL)ENGINE=InnoDB;
|
||||||
|
DROP TABLE t1;
|
||||||
|
|
||||||
|
CREATE TABLE t1(f1 TEXT, index(f1(2)))ENGINE=InnoDB;
|
||||||
|
INSERT INTO t1 VALUES('a');
|
||||||
|
--error ER_RECORD_FILE_FULL
|
||||||
|
set statement DEBUG_DBUG="+d,btr_page_alloc_fail" for
|
||||||
|
UPDATE t1 set f1= REPEAT('b', 12000);
|
||||||
|
DROP TABLE t1;
|
@@ -3051,13 +3051,15 @@ static bool ddl_log_write(DDL_LOG_STATE *ddl_state,
|
|||||||
error= ((ddl_log_write_entry(ddl_log_entry, &log_entry)) ||
|
error= ((ddl_log_write_entry(ddl_log_entry, &log_entry)) ||
|
||||||
ddl_log_write_execute_entry(log_entry->entry_pos, 0,
|
ddl_log_write_execute_entry(log_entry->entry_pos, 0,
|
||||||
&ddl_state->execute_entry));
|
&ddl_state->execute_entry));
|
||||||
mysql_mutex_unlock(&LOCK_gdl);
|
DBUG_EXECUTE_IF("ddl_log_write_fail", error= true;);
|
||||||
if (error)
|
if (error)
|
||||||
{
|
{
|
||||||
if (log_entry)
|
if (log_entry)
|
||||||
ddl_log_release_memory_entry(log_entry);
|
ddl_log_release_memory_entry(log_entry);
|
||||||
|
mysql_mutex_unlock(&LOCK_gdl);
|
||||||
DBUG_RETURN(1);
|
DBUG_RETURN(1);
|
||||||
}
|
}
|
||||||
|
mysql_mutex_unlock(&LOCK_gdl);
|
||||||
ddl_log_add_entry(ddl_state, log_entry);
|
ddl_log_add_entry(ddl_state, log_entry);
|
||||||
ddl_state->flags|= ddl_log_entry->flags; // Update cache
|
ddl_state->flags|= ddl_log_entry->flags; // Update cache
|
||||||
DBUG_RETURN(0);
|
DBUG_RETURN(0);
|
||||||
|
@@ -3697,8 +3697,10 @@ btr_cur_optimistic_update(
|
|||||||
*offsets = rec_get_offsets(rec, index, *offsets, index->n_core_fields,
|
*offsets = rec_get_offsets(rec, index, *offsets, index->n_core_fields,
|
||||||
ULINT_UNDEFINED, heap);
|
ULINT_UNDEFINED, heap);
|
||||||
#if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG
|
#if defined UNIV_DEBUG || defined UNIV_BLOB_LIGHT_DEBUG
|
||||||
|
/* Blob pointer can be null if InnoDB was killed or
|
||||||
|
ran out of space while allocating a page. */
|
||||||
ut_a(!rec_offs_any_null_extern(rec, *offsets)
|
ut_a(!rec_offs_any_null_extern(rec, *offsets)
|
||||||
|| thr_get_trx(thr) == trx_roll_crash_recv_trx);
|
|| thr_get_trx(thr)->in_rollback);
|
||||||
#endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */
|
#endif /* UNIV_DEBUG || UNIV_BLOB_LIGHT_DEBUG */
|
||||||
|
|
||||||
if (UNIV_LIKELY(!update->is_metadata())
|
if (UNIV_LIKELY(!update->is_metadata())
|
||||||
@@ -4371,7 +4373,12 @@ btr_cur_pessimistic_update(
|
|||||||
cursor, offsets, offsets_heap,
|
cursor, offsets, offsets_heap,
|
||||||
new_entry, &rec,
|
new_entry, &rec,
|
||||||
&dummy_big_rec, n_ext, NULL, mtr);
|
&dummy_big_rec, n_ext, NULL, mtr);
|
||||||
ut_a(err == DB_SUCCESS);
|
if (err) {
|
||||||
|
/* This should happen when InnoDB tries to extend the
|
||||||
|
tablespace */
|
||||||
|
ut_ad(err == DB_OUT_OF_FILE_SPACE);
|
||||||
|
return err;
|
||||||
|
}
|
||||||
ut_a(rec);
|
ut_a(rec);
|
||||||
ut_a(dummy_big_rec == NULL);
|
ut_a(dummy_big_rec == NULL);
|
||||||
ut_ad(rec_offs_validate(rec, cursor->index(), *offsets));
|
ut_ad(rec_offs_validate(rec, cursor->index(), *offsets));
|
||||||
@@ -6240,6 +6247,9 @@ btr_store_big_rec_extern_fields(
|
|||||||
FSP_NO_DIR, 0, &mtr, &mtr,
|
FSP_NO_DIR, 0, &mtr, &mtr,
|
||||||
&error);
|
&error);
|
||||||
|
|
||||||
|
DBUG_EXECUTE_IF("btr_page_alloc_fail",
|
||||||
|
block= nullptr;
|
||||||
|
error= DB_OUT_OF_FILE_SPACE;);
|
||||||
if (!block) {
|
if (!block) {
|
||||||
alloc_fail:
|
alloc_fail:
|
||||||
mtr.commit();
|
mtr.commit();
|
||||||
|
@@ -605,7 +605,7 @@ fil_space_extend_must_retry(
|
|||||||
*success = os_file_set_size(node->name, node->handle, new_size,
|
*success = os_file_set_size(node->name, node->handle, new_size,
|
||||||
node->punch_hole == 1);
|
node->punch_hole == 1);
|
||||||
|
|
||||||
os_has_said_disk_full = *success;
|
os_has_said_disk_full = !*success;
|
||||||
if (*success) {
|
if (*success) {
|
||||||
os_file_flush(node->handle);
|
os_file_flush(node->handle);
|
||||||
last_page_no = size;
|
last_page_no = size;
|
||||||
|
@@ -2161,6 +2161,11 @@ convert_error_code_to_mysql(
|
|||||||
return(HA_ERR_RECORD_FILE_FULL);
|
return(HA_ERR_RECORD_FILE_FULL);
|
||||||
|
|
||||||
case DB_TEMP_FILE_WRITE_FAIL:
|
case DB_TEMP_FILE_WRITE_FAIL:
|
||||||
|
/* This error can happen during
|
||||||
|
copy_data_between_tables() or bulk insert operation */
|
||||||
|
innodb_transaction_abort(thd,
|
||||||
|
innobase_rollback_on_timeout,
|
||||||
|
error);
|
||||||
my_error(ER_GET_ERRMSG, MYF(0),
|
my_error(ER_GET_ERRMSG, MYF(0),
|
||||||
DB_TEMP_FILE_WRITE_FAIL,
|
DB_TEMP_FILE_WRITE_FAIL,
|
||||||
ut_strerr(DB_TEMP_FILE_WRITE_FAIL),
|
ut_strerr(DB_TEMP_FILE_WRITE_FAIL),
|
||||||
@@ -15931,7 +15936,7 @@ ha_innobase::extra(
|
|||||||
}
|
}
|
||||||
m_prebuilt->table->skip_alter_undo = 0;
|
m_prebuilt->table->skip_alter_undo = 0;
|
||||||
if (dberr_t err= trx->bulk_insert_apply<TRX_DDL_BULK>()) {
|
if (dberr_t err= trx->bulk_insert_apply<TRX_DDL_BULK>()) {
|
||||||
m_prebuilt->table->skip_alter_undo = 0;
|
trx->rollback();
|
||||||
return convert_error_code_to_mysql(
|
return convert_error_code_to_mysql(
|
||||||
err, m_prebuilt->table->flags,
|
err, m_prebuilt->table->flags,
|
||||||
trx->mysql_thd);
|
trx->mysql_thd);
|
||||||
|
@@ -398,12 +398,17 @@ start_log:
|
|||||||
}
|
}
|
||||||
|
|
||||||
log->tail.blocks++;
|
log->tail.blocks++;
|
||||||
|
DBUG_EXECUTE_IF("os_file_write_fail",
|
||||||
|
log->error = DB_TEMP_FILE_WRITE_FAIL;
|
||||||
|
goto write_failed;);
|
||||||
|
|
||||||
if (os_file_write(
|
if (os_file_write(
|
||||||
IORequestWrite,
|
IORequestWrite,
|
||||||
"(modification log)",
|
"(modification log)",
|
||||||
log->fd,
|
log->fd,
|
||||||
buf, byte_offset, srv_sort_buf_size)
|
buf, byte_offset, srv_sort_buf_size)
|
||||||
!= DB_SUCCESS) {
|
!= DB_SUCCESS) {
|
||||||
|
log->error = DB_TEMP_FILE_WRITE_FAIL;
|
||||||
write_failed:
|
write_failed:
|
||||||
index->type |= DICT_CORRUPT;
|
index->type |= DICT_CORRUPT;
|
||||||
}
|
}
|
||||||
|
@@ -5177,6 +5177,9 @@ dberr_t row_merge_bulk_t::write_to_tmp_file(ulint index_no)
|
|||||||
m_block, m_crypt_block,
|
m_block, m_crypt_block,
|
||||||
buf->index->table->space->id))
|
buf->index->table->space->id))
|
||||||
return DB_TEMP_FILE_WRITE_FAIL;
|
return DB_TEMP_FILE_WRITE_FAIL;
|
||||||
|
|
||||||
|
DBUG_EXECUTE_IF("write_to_tmp_file_fail",
|
||||||
|
return DB_TEMP_FILE_WRITE_FAIL;);
|
||||||
MEM_UNDEFINED(&m_block[0], srv_sort_buf_size);
|
MEM_UNDEFINED(&m_block[0], srv_sort_buf_size);
|
||||||
return DB_SUCCESS;
|
return DB_SUCCESS;
|
||||||
}
|
}
|
||||||
|
@@ -708,6 +708,7 @@ handle_new_error:
|
|||||||
case DB_DEADLOCK:
|
case DB_DEADLOCK:
|
||||||
case DB_RECORD_CHANGED:
|
case DB_RECORD_CHANGED:
|
||||||
case DB_LOCK_TABLE_FULL:
|
case DB_LOCK_TABLE_FULL:
|
||||||
|
case DB_TEMP_FILE_WRITE_FAIL:
|
||||||
rollback:
|
rollback:
|
||||||
/* Roll back the whole transaction; this resolution was added
|
/* Roll back the whole transaction; this resolution was added
|
||||||
to version 3.23.43 */
|
to version 3.23.43 */
|
||||||
|
@@ -1130,9 +1130,8 @@ row_undo_mod_upd_exist_sec(
|
|||||||
dtuple_t* entry = row_build_index_entry(
|
dtuple_t* entry = row_build_index_entry(
|
||||||
node->row, node->ext, index, heap);
|
node->row, node->ext, index, heap);
|
||||||
if (UNIV_UNLIKELY(!entry)) {
|
if (UNIV_UNLIKELY(!entry)) {
|
||||||
/* The server must have crashed in
|
/* InnoDB must have run of space or been killed
|
||||||
row_upd_clust_rec_by_insert() before
|
before the updated externally stored columns (BLOBs)
|
||||||
the updated externally stored columns (BLOBs)
|
|
||||||
of the new clustered index entry were written. */
|
of the new clustered index entry were written. */
|
||||||
|
|
||||||
/* The table must be in DYNAMIC or COMPRESSED
|
/* The table must be in DYNAMIC or COMPRESSED
|
||||||
@@ -1140,19 +1139,6 @@ row_undo_mod_upd_exist_sec(
|
|||||||
store a local 768-byte prefix of each
|
store a local 768-byte prefix of each
|
||||||
externally stored column. */
|
externally stored column. */
|
||||||
ut_a(dict_table_has_atomic_blobs(index->table));
|
ut_a(dict_table_has_atomic_blobs(index->table));
|
||||||
|
|
||||||
/* This is only legitimate when
|
|
||||||
rolling back an incomplete transaction
|
|
||||||
after crash recovery. */
|
|
||||||
ut_a(thr_get_trx(thr)->is_recovered);
|
|
||||||
|
|
||||||
/* The server must have crashed before
|
|
||||||
completing the insert of the new
|
|
||||||
clustered index entry and before
|
|
||||||
inserting to the secondary indexes.
|
|
||||||
Because node->row was not yet written
|
|
||||||
to this index, we can ignore it. But
|
|
||||||
we must restore node->undo_row. */
|
|
||||||
} else {
|
} else {
|
||||||
/* NOTE that if we updated the fields of a
|
/* NOTE that if we updated the fields of a
|
||||||
delete-marked secondary index record so that
|
delete-marked secondary index record so that
|
||||||
|
@@ -1414,16 +1414,11 @@ row_upd_changes_ord_field_binary_func(
|
|||||||
if (UNIV_LIKELY_NULL(buf)) {
|
if (UNIV_LIKELY_NULL(buf)) {
|
||||||
if (UNIV_UNLIKELY(buf == field_ref_zero)) {
|
if (UNIV_UNLIKELY(buf == field_ref_zero)) {
|
||||||
/* The externally stored field
|
/* The externally stored field
|
||||||
was not written yet. This
|
was not written yet. InnoDB must
|
||||||
record should only be seen by
|
have ran out of space or been killed
|
||||||
trx_rollback_recovered()
|
before storing the page */
|
||||||
when the server had crashed before
|
ut_ad(thr);
|
||||||
storing the field. */
|
ut_ad(thr->graph->trx->in_rollback);
|
||||||
ut_ad(!thr
|
|
||||||
|| thr->graph->trx->is_recovered);
|
|
||||||
ut_ad(!thr
|
|
||||||
|| thr->graph->trx
|
|
||||||
== trx_roll_crash_recv_trx);
|
|
||||||
return(TRUE);
|
return(TRUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user