Cloud Spanner supports a full range of joins (Inner, Cross, Full, Left, Right). It lets you create and query many-to-many relationships just as you would in a standard SQL database.
However, just because you can make those queries, does not mean that they will be efficient. If your queries need to read and write across splits, then your query performance will be slower as it needs to co-ordinate reads and writes to multiple splits. Interleaved tables give you more control over your data layout on disk, to improve the performance of your queries.
If you wanted to create an efficient many-to-many relationship, you could have two tables that interleave with a parent table on a shared common key: e.g.
Parent key = account_id
-- Many 1 key = account_id, user_id
-- Many 2 key = account_id, group_id
This would enable you to make queries that only need to touch a single split, keeping them fast, efficient, and conflict free.
Cloud Spanner also has an array type which depending on your use case may also be helpful.
EDIT: responding to
I can see how to model this to get relatively efficient queries, but I don't see how I would safeguard the system from inconsistencies, i.e. having a Following that references an User that has been deleted.
You could either delete all the follower records within the same account delete mutation as two separate queries in a transaction, or setup a background job to clean it up in the background.
Also, in this situation I can't for instance have a Many2 be cascade-deleted because its reference in Many1 has been deleted, right? The only cascade delete I can do in this case is if I delete a whole Parent.
Yes, that's correct.