Here you can do with one table with the fields userId1, userId2, status. Consider the example of contact - there if you send a friend add request, you become a subscriber, and when the application is confirmed, then friends. Here, the status field is responsible just for determining the type of communication, and their change must be defined in the triggers. At the application level, you should not make a status change.
Example. The user makes a request to add as a friend, while fulfilling the request:
insert into friends_rels (userId1, userId2) values ($id1, $id2);
And the trigger has to determine whether this is an application, or confirmation. Defined by querying the table:
select userId1, userId2 from friends_rels where userId1 = $id2 and userId2 = $id1;
If it does, it means this confirmation of the application: modifies the string NEW.status = 'friends' and performs an update on the application, where it also assigns the status of the fact that they are friends. If the record was not found by the select, then this is an application for adding as a friend and you just need to modify the status: NEW.status = 'subscribe' .
The primary key for the string should not be done - it is simply not needed, but you only need to hang the uniqueness on the combination of the two fields.
Thus, all logic is concentrated in one trigger only at the DB level, i.e. at the data storage level. There are no extra Join to determine the type of connection. Also, there is no need to handle a bunch of private situations like the one when one user made a request to the second, and the second user made a request to add a friend as the first - everything will be correct here, and in other implementations a deviation from the desired behavior is possible.