Turns out this was a threading issue. The object that contained my QAbstractTableModel-derived models had been moved to a worker thread, which doesn't work well with the model/view architecture - both the GUI thread and the worker thread were sending signals to the models, which meant that insert row and remove row operations were overlapping each other, hence the odd behaviour.
The main take-out is: when using the model/view approach, the models must have the same thread affinity as the views (i.e. keep the models on the main application thread); don't move them to worker threads unless you want to get jiggy with serialising access.
A thread that did help: http://stackoverflow.com/questions/9485339/design-pattern-qt-model-view-and-multiple-threads