A jobs table is a perfectly good queue for one worker. Add a second and both claim the same row, because SELECT then UPDATE is two statements with a gap in between.
START TRANSACTION;
SELECT id FROM jobs
WHERE status = 'pending'
ORDER BY id
LIMIT 1
FOR UPDATE; -- other workers block here
UPDATE jobs SET status = 'running' WHERE id = ?;
COMMIT;
FOR UPDATE locks the selected rows until the transaction ends, so the second worker waits rather than duplicating. The cost is that it genuinely waits — with many workers the queue serialises on that lock, which is the point at which a real broker starts to look reasonable. Keep the transaction short: claim the row, commit, then do the work outside it.