A self-join on 500,000 rows is likely to be expensive. Why not just enumerate the rows and grab the one in the middle?
select d.*
from (select d.*, (@rn := @rn + 1) as rn
from data d cross join
(select @rn := 0) params
order by d.val
) d
where 2*rn in (@rn, @rn + 1);
The weird where
clause chooses the value in the middle -- it is just an approximation if there are an eve number of rows. Because you want the actual row values, you need the approximation. The normal calculation of the median itself would be:
select avg(d.val)
from (select d.*, (@rn := @rn + 1) as rn
from data d cross join
(select @rn := 0) params
order by d.val
) d
where 2*rn in (@rn - 1, @rn, @rn + 1);
EDIT:
The same logic works per person as well, but with a bit more logic to get the overall counts:
select d.person, avg(val) as median
from (select d.*,
(@rn := if(@p = person, @rn + 1
if(@p := person, 1, 1)
) as rn
from data d cross join
(select @rn := 0, @p := '') params
order by person, d.val
) d join
(select person, count(*) as cnt
from data
group by person
) p
on d.person = p.person
where 2*rn in (d.cnt - 1, d.cnt, d.cnt + 1)
group by person;