I think there are a number of possibilities in the answers already provided here. Since you only take shared locks, the deadlock can't be due to lock escalation, and must simply be acquiring locks that are incompatible with those acquired in another process, and acquiring those locks in a different order...
Your shared locks are incompatible with another process taking exclusive locks. The scenario might run something like this...
- You take shared lock on resource A
- Other process takes exclusive lock on resource B
- Other process tries to take exclusive lock on resource A, and blocks waiting for you to release your shared lock on A.
- You try to take shared lock on resource B, and would block waiting for the other process to release its exclusive lock on B, except that you're now in a deadlock situation, which is identified by the server and it chooses a process to kill.
N.B. deadlocks can have more players than just 2. Sometimes there's a whole chain of interwoven activity that results in a deadlock, but the principle is the same.
Often, if multiple applications access the same database, there is a DBA that manages all access via stored procedures, so he can ensure resources are always locked in the same order. If you're not in that situation, and the other applications use ad-hoc SQL statements you'd have to inspect their code to find out if they might conflict with your app in the way I've described. That doesn't sound like fun.
A pragmatic solution might be to catch the error when your transaction is killed as a deadlock victim, and simply re-try the transaction several times. Depending on how much activity the other apps are generating, you might achieve acceptable results this way.