Доступ к файловой системе действительно не транзакционный. Вам нужно будет смоделировать распределенную транзакцию "все или ничего" самостоятельно: если фиксация в базе данных завершится неудачно, удалите файл в файловой системе. И наоборот, если запись файла не удалась, откат транзакции базы данных (это будет немного сложнее, но это грубый набросок).
Обратите внимание, что это может быть довольно сложно, когда файл обновляется. Сначала необходимо скопировать его, чтобы в случае сбоя транзакции базы данных после перезаписи файла вы все равно могли восстановить старую версию файла. То, хотите ли вы сделать это, зависит от желаемого уровня надежности .
Постарайтесь, чтобы все манипуляции проходили через ваше приложение (создание, запись, удаление файлов). Если вы не можете сделать это и не можете предотвратить прямой доступ к файлу в файловой системе (и, возможно, удалить), я не вижу другого способа, кроме как периодически синхронизировать базу данных с файловой системой: проверить, какой файл был удален, и удалить запись в базе данных. Вы можете создать задание, которое запускается каждую X минуту для этого.
Я бы также предложил сохранить хэш (например, MD5) файла в базе данных. Потратьте немного времени на его вычисление, но мне было очень полезно обнаруживать проблемы, например, если файл переименован в файловой системе по ошибке, но не в базе данных. Это также позволяет периодически выполнять проверку целостности, чтобы убедиться, что ничего не было прикручено.
Если такого подхода недостаточно (например, вы хотите, чтобы он был более устойчивым), я не вижу другого способа, кроме как сохранить двоичный файл в базе данных в LOB. Тогда это будет действительно транзакционно и безопасно.