The first migrate created the migrations table and ran every pending file as batch 1. A later migrate recorded the ISBN file as batch 2, and migrate:rollback undid that last batch:
$ php artisan migrate ... 2026_09_23_081322_create_reviews_table ....................... 423.07ms DONE $ php artisan migrate:status ... 2026_09_23_081322_create_reviews_table ............................. [1] Ran 2026_09_23_081323_add_isbn_to_products_table ....................... [2] Ran $ php artisan migrate:rollback ... 2026_09_23_081323_add_isbn_to_products_table ................. 245.91ms DONE
migrate --step gives each file its own batch; migrate:rollback --step=3 undoes three files. migrate:reset rolls back everything, migrate:refresh resets and migrates through every down(), and migrate:fresh drops every table without down(), then migrates; both accept --seed. fresh ignores table prefixes, so never aim it at a shared database.
In production migrate asks for confirmation unless deploy scripts pass --force, and --isolated takes a cache lock so that only one of several servers migrates.