Delete using another table with index
It can't do an index-only scan in this case.

It needs the actual table row, not just the index entry, of the parent row.  If you do an EXPLAIN VERBOSE, you will see the p.ctid getting exposed.  I believe that is because if the delete encounters a locked row in the child table, it will block until the transaction locking that row finishes.  But once that happens, it has to re-check the parent row to check if it was changed by the same transaction that had just changed the child row.

And of course it can't do an index-only scan on the child, because how would you delete a row in the table without accessing the row in the table?

But anyway, why would doing one index-only scan plus 10,000 individual index scans be faster than one hash join?  Hash joins are pretty awesome.