Тут можно обойтись и одной таблицей со полями userId1, userId2, status. Рассмотрим пример вконтактика - там если отправляешь заявку добавления в друзья, то становишься подписчиком, а когда заявку подтверждают, то друзьями. Здесь поле статуса отвечает как раз за определение типа связи, а их смена должна быть определена в триггерах. На уровень приложения смену статуса выносить не стоит.
Пример. Пользователь делает заявку на добавление в друзья, при этом выполняется запрос:
insert into friends_rels (userId1, userId2) values ($id1, $id2);
А уже триггер должен определить является ли это заявкой, или же подтверждением. Определяет путем запроса к таблице:
select userId1, userId2 from friends_rels where userId1 = $id2 and userId2 = $id1;
Если находит, значит это подтверждение заявки: модифицирует строку NEW.status = 'friends' и выполняет update на заявку, где также присваивает статус того, что они - друзья. Если же запись селектом не была найдена, то значит это заявка на добавление в друзья и нужно лишь модифицировать статус: NEW.status = 'subscribe'.
Первичный ключ для строки не стоит делать - он попросту не нужен, а нужно только повешать уникальность на сочетание двух полей.
Таким образом вся логика сосредоточена в одном только триггере на уровне БД, т.е. на уровне хранения данных. Отсутствуют лишние Joinы для определения типа связи. Также не нужно обрабатывать кучу частных ситуаций типа той, когда одной пользователь сделал заявку второму, а второй пользователь сделал заявку на добавление в друзья первого - здесь все пройдет верно, а в других реализациях возможно отклонение от требуемого поведения.