On a table of 15000 it took around 0.6 seconds without any indexing.
I like the idea of using surname <= 'bloggs' LIMIT 10 however this would be difficult when we are a few pages back as the only reference point I would have is 'bloggs'.
Your solutions solves that because I can do:
WHERE d1.rank BETWEEN (d2.rank - 300) AND (d2.rank - 260).
In fact I could do:
SELECT d1.cand_name_fo, d1.cand_name_ln, d1.cand_id, d1.rank
FROM (
SELECT cand_id, cand_name_fo,cand_name_ln,(@rnk1 := @rnk1+1) as 'rank'
FROM tbl_cand p
JOIN (SELECT @rnk1 := 0) x
ORDER BY p.cand_name_ln ASC, p.cand_name_fo ASC
) d1
JOIN (
SELECT * FROM (
SELECT cand_name_fo,cand_name_ln,(@rnk2 := @rnk2+1) as 'rank'
FROM tbl_cand p
JOIN (SELECT @rnk2 := 0) x
ORDER BY p.cand_name_ln ASC, p.cand_name_fo ASC
) x
WHERE cand_name_ln >= 'bloggs' LIMIT 1
) d2
WHERE d1.rank BETWEEN (d2.rank - 3) AND (d2.rank + 3)
ORDER BY d1.rank;
Changing the WHERE statement to just look at the surname but the problem is if we have 100 'Smith' and we are looking for 'zoe' 'smith' it wouldn't show her on the first page where we want it.