Fixed #23474 - Schema migrations can inadvertently destroy data

My usage of `get_migration_by_prefix` looks a bit funky, I'm not sure if there's a better way.

I haven't run the tests, or written any new tests yet.
Other tests are not so good at doing things, but we need to execute test cleanup even if an assertion fails. This works for me:
```
        try:
            self.assertTableExists("migrations2_example")
            # Rebuild the graph to reflect the new DB state
            executor.loader.build_graph()
            # Should be a noop: migrations.0002 was never applied
            self.assertEqual(
                executor.migration_plan([("migrations", "0001_initial")]),
                [],
            )
            executor.migrate([("migrations", "0001_initial")])
            self.assertTableExists("migrations2_example")
            # And migrate back to clean up the database
            executor.loader.build_graph()
        finally:
            executor.migrate([("migrations", None)])
```