Description:
Version: Tested on MySQL 8.0.45 and 8.4.8. Likely affects all current versions.
####################
# Trigger Scenario
####################
When applying row-based replication events on a replica, a crash occurs if all of the following conditions are met:
1. The table has no primary key and no NOT NULL unique index
2. The table contains a FULLTEXT index
3. On the source, multiple updates to the same row occur within a single transaction, and the updated columns include the FULLTEXT-indexed column(s)
4. The replica's `slave_rows_search_algorithms` includes HASH_SCAN
Expected behavior: The replica should apply the UPDATE events successfully and remain consistent with the source without crashing.
Actual behavior: The replica crashes with an assertion failure `result != FTS_INVALID` in `fts0fts.cc:2460` due to an illegal FTS state transition (`DELETE→DELETE = FTS_INVALID`).
####################
# Crash Backtrace
####################
2026-02-28T03:50:07.579637Z 13 [ERROR] [MY-013183] [InnoDB] Assertion failure: fts0fts.cc:2460:result != FTS_INVALID thread 140320881686272
InnoDB: We intentionally generate a memory trap.
InnoDB: Submit a detailed bug report to http://bugs.mysql.com.
InnoDB: If you get repeated assertion failures or crashes, even
InnoDB: immediately after the mysqld startup, there may be
InnoDB: corruption in the InnoDB tablespace. Please refer to
InnoDB: http://dev.mysql.com/doc/refman/8.0/en/forcing-innodb-recovery.html
InnoDB: about forcing recovery.
2026-02-28T03:50:07Z UTC - mysqld got signal 6 ;
Most likely, you have hit a bug, but this error can also be caused by malfunctioning hardware.
BuildID[sha1]=1fc9bee9eedc4065e6be6dd35b64645200dfd808
Thread pointer: 0x7f9e84000940
Attempting backtrace. You can use the following information to find out
where mysqld died. If you see no messages after this, something went
terribly wrong...
stack_bottom = 7f9f004ea998 thread_stack 0x100000
#0 0x36f0f0a _Z18print_fatal_signali at mysql-server/sql/signal_handler.cc:155
#1 0x36f11bc _Z15my_server_abortv at mysql-server/sql/signal_handler.cc:270
#2 0x4b0b82e _Z8my_abortv at mysql-server/mysys/my_init.cc:264
#3 0x4f208dc _Z23ut_dbg_assertion_failedPKcS0_m at mysql-server/storage/innobase/ut/ut0dbg.cc:100
#4 0x51c4a7e fts_trx_row_get_new_state at mysql-server/storage/innobase/fts/fts0fts.cc:2460
#5 0x51c4f4d fts_trx_table_add_op at mysql-server/storage/innobase/fts/fts0fts.cc:2622
#6 0x51c50a2 _Z14fts_trx_add_opP5trx_tP12dict_table_tm13fts_row_stateP11ib_vector_t at mysql-server/storage/innobase/fts/fts0fts.cc:2665
#7 0x4de62cd row_fts_do_update at mysql-server/storage/innobase/row/row0mysql.cc:1822
#8 0x4de648d row_fts_update_or_delete at mysql-server/storage/innobase/row/row0mysql.cc:1857
#9 0x4de7ecc row_update_for_mysql_using_upd_graph at mysql-server/storage/innobase/row/row0mysql.cc:2395
#10 0x4de814f _Z20row_update_for_mysqlPKhP14row_prebuilt_t at mysql-server/storage/innobase/row/row0mysql.cc:2457
#11 0x4b8414c _ZN11ha_innobase10update_rowEPKhPh at mysql-server/storage/innobase/handler/ha_innodb.cc:9847
#12 0x389e74b _ZN7handler13ha_update_rowEPKhPh at mysql-server/sql/handler.cc:8051
#13 0x46ebeec _ZN21Update_rows_log_event11do_exec_rowEPK14Relay_log_info at mysql-server/sql/log_event.cc:12744
#14 0x46e0016 _ZN14Rows_log_event12do_apply_rowEPK14Relay_log_info at mysql-server/sql/log_event.cc:8917
#15 0x46e180d _ZN14Rows_log_event18do_scan_and_updateEPK14Relay_log_info at mysql-server/sql/log_event.cc:9501
#16 0x46e1c46 _ZN14Rows_log_event23do_hash_scan_and_updateEPK14Relay_log_info at mysql-server/sql/log_event.cc:9590
#17 0x46e3a81 _ZN14Rows_log_event14do_apply_eventEPK14Relay_log_info at mysql-server/sql/log_event.cc:10211
#18 0x46f8747 _ZN9Log_event21do_apply_event_workerEP12Slave_worker at mysql-server/sql/log_event.cc:1036
#19 0x47c362f _ZN12Slave_worker23slave_worker_exec_eventEP9Log_event at mysql-server/sql/rpl_rli_pdb.cc:1739
#20 0x47c6195 _Z27slave_worker_exec_job_groupP12Slave_workerP14Relay_log_info at mysql-server/sql/rpl_rli_pdb.cc:2504
#21 0x47e1572 handle_slave_worker at mysql-server/sql/rpl_replica.cc:6105
#22 0x570f3d1 pfs_spawn_thread at mysql-server/storage/perfschema/pfs.cc:3050
####################
# Workaround
####################
To avoid this issue:
1. Add an explicit primary key or a NOT NULL unique key to the table
2. Ensure the replica's `slave_rows_search_algorithms` includes INDEX_SCAN (this prevents the HASH_SCAN inner loop from being triggered)
How to repeat:
The following is an MTR (MySQL Test Runner) test case that reproduces the issue:
```
--source include/have_binlog_format_row.inc
--source include/master-slave.inc
--source include/rpl_connection_slave.inc
SET @saved_slave_rows_search_algorithms= @@global.slave_rows_search_algorithms;
SET @@global.slave_rows_search_algorithms= 'HASH_SCAN';
--source include/rpl_connection_master.inc
CREATE TABLE test (
id1 INT NOT NULL,
id2 INT NULL,
c VARCHAR(200),
UNIQUE KEY u_id(id1, id2),
FULLTEXT (c)
) ENGINE=InnoDB;
INSERT INTO test (id1, id2, c) VALUES (1, 1, 'Happy Every Day');
--delimiter |
CREATE FUNCTION f1 () RETURNS INT BEGIN
UPDATE test SET c = 'Joyful Daily' WHERE id1 = 1 AND id2 = 1;
UPDATE test SET c = 'Bliss All Day' WHERE id1 = 1 AND id2 = 1;
RETURN 0;
END|
--delimiter ;
SELECT f1();
--source include/sync_slave_sql_with_master.inc
--source include/rpl_connection_slave.inc
SELECT * FROM test;
--source include/rpl_connection_master.inc
DROP FUNCTION f1;
DROP TABLE test;
--source include/sync_slave_sql_with_master.inc
--source include/rpl_connection_slave.inc
SET @@global.slave_rows_search_algorithms= @saved_slave_rows_search_algorithms;
--source include/rpl_end.inc
```
Suggested fix:
####################
# Root Cause Analysis
####################
In the HASH_SCAN replication path, when the same row is updated multiple times within a single `Update_rows_log_event`, the function `do_scan_and_update()` (in `log_event.cc`) contains an inner `do...while` optimization loop. This loop reuses the After Image (AI) from the previous `ha_update_row()` call directly for the next hash lookup, skipping the call to `next_record_scan()` (i.e., `row_search_mvcc()`).
However, `prebuilt->fts_doc_id` in InnoDB is only refreshed during the `row_search_mvcc()` → `row_sel_store_mysql_rec()` code path. By skipping this path, the inner loop's subsequent iterations retain the stale `fts_doc_id` from the previous iteration instead of the current row's actual `doc_id`.
As a result, InnoDB's FTS subsystem attempts to perform a DELETE operation using the incorrect (stale) `old_doc_id`, leading to a duplicate DELETE on the same FTS document ID and triggering the illegal state transition `DELETE→DELETE = FTS_INVALID`, which causes an assertion failure and crash.
####################
# Suggested Fix
####################
Ensure that `prebuilt->fts_doc_id` is refreshed before each `ha_update_row()` call in the `do_scan_and_update()` inner loop, even when `next_record_scan()` is skipped. This could be done by explicitly re-reading the FTS doc ID from the current record before performing the update.