What are the pros and cons of creating FK constraints in the database itself, rather than managing them at the application level?
In a parallel environment, in practice it is difficult to implement referential integrity in the application code, so that it is correct and with good performance.
If you do not use the lock very carefully, you can participate in the race, for example:
- Imagine there is one row in the parent table and there are no matching rows in the child element.
- Transaction T1 inserts a row into the child table, but does not commit yet. He can do this because there is a corresponding row in the parent table.
- Transaction T2 deletes the parent row. He can do this, since from his point of view there are no child rows (T1 has not yet been fixed).
- Transmission T1 and T2.
- At this point, you have a child string without a parent (i.e. broken referential integrity).
To fix this, you can lock the parent row from both transactions, but it is likely to be less efficient compared to the highly optimized FK implemented in the DBMS itself.
In addition, all your clients must adhere to the same "blocking protocol" (one wrong client is enough to display data). And complexity increases quickly if you have multiple levels of nested FK or diamond-shaped FK. Even if you implement referential integrity in triggers, you only solve the "one bad client" problem, but the rest remains.
Another nice thing about FK at the database level is that they usually support reference actions like ON DELETE CASCADE. And all this is simple and self-documenting, unlike referential integrity, looping inside the application code.
From a design point of view, should they ever be used together or could cause conflict?
You should always use FK at the database level. You can also use application-level “pre-checks” if it benefits your user (i.e. you don't want to wait until the actual INSERT / UPDATE / DELETE notifies the user), but you should always specify the code as if INSERT / UPDATE / DELETE may fail even if your application level check passed.
If they should not be used together, what is considered “best practice” as to which approach to use?
As I said, always use FK at the database level. If you wish, you can also use FK at the application level "from above."