Your table suffers from a bad design. It is a violation of normalization. In particular, storing the name in one column is a violation of the first normal form. In an amicable manner, the full name should be broken down into atomic entities - Surname, First Name and Patronymic. Judging by your words “check for duplicates with the same fio field” , you may have a situation where several records correspond to one person (despite the presence of the primary key number) or you want to identify the person not by the primary key, but by full name, which in itself is bad and wrong. However, for this case it is not so important.
The second problem area is the availability of telephone numbers in the persons table, or rather, a situation where one person can have one phone or two (and theoretically three, four and even eleven, which cannot be displayed in your structure). So, in an amicable way, you should create a Phones table, in which three fields should be stored - a key, PersonId - a foreign key for the persons table, identifying the person and PhoneNumber - a phone number. Thereby you would be able to achieve flexibility in terms of the number of telephone numbers, allowing one person to have any number of numbers - from zero to infinity (at the same time you, besides everything else, are also forced to write NULL or an empty string in the phone_2 field? not)
However, this is all - the subjunctive mood. In the current implementation, the query that you brought suffers from an excessive (no, even glaring) number of subqueries. Of course, this greatly affects productivity (say, to indicate the usual condition phone_2! = "" It is not necessary to do a separate subquery for this, which will go through all 120,000 records again. And so on 120,000 iterations for each of 120,000 records. In short, the number of iterations goes to billions)
In principle, such a query should be reduced to using GROUP BY and hAVING and represent something like this:
SELECT fio, phone_2 FROM persons WHERE phone_2 != '' AND ACTIVE = 1 GROUP BY fio, phone_2 HAVING COUNT(*) = 1
however, your case is specifically complicated by the fact that you need to get a field that is not in the GROUP BY clause , so an additional query will be required. For example:
SELECT p.number FROM persons p JOIN ( SELECT fio, phone_2 FROM persons WHERE phone_2 != '' AND ACTIVE = 1 GROUP BY fio, phone_2 HAVING COUNT(*) = 1 ) p2 ON p2.fio = p.fio
I will not argue that it will run in the blink of an eye (to be sure of something, it would be worth trying it on your data), but it should take substantially less than five hours