I'd like to offer a slightly different perspective that may help with your current problem.
Don't think of state changes as bad. After all, if there were no state changed in your program, you could safely replace it with a no-op.
The problem with state change is that it's notoriously hard for the developer and the system to reason about. This is made worse if the state changes are spread widely through the program and especially true if they're implicit/hidden.
What functional programming promotes with concepts like purity and immutability isn't so much the removal of state change. It's more about making the state change transparent and encapsulated.
So, to take your example, one approach would be to have the logic that generates the DB commands simply accumulate the commands but not execute them. Once you have them all then you can have one component that executes the commands.
Further, if your generation logic accepts the current list of commands and returns a new list, with the changes it made, then you have purity in the generation. The advantage is that it's easier to reason about, and test, your logic functions.
It may appear that all you've done is to move the complexity around and that is, to some extent, true. But note, the code that handles the mutability is pretty specific: it doesn't have any generation logic, it just executes commands. Anything you needed to change wrt command batching etc would be encapsulated there.
Where monads tend to be useful is when you need to maintain order and dependency information i.e. if you work out what you need to do then do it straight away they're not very helpful. But note, in the scheme above, there is a conceptual delay between the generation and execution so they could be useful. Once you factor in error paths then their rigour can be invaluable.
And, in practice, it's the delay between actions that I find is a good indicator that a monad may be useful. Even outside of functional languages. For example, error handling and asynchronous code often benefit from monads. interestingly, these are the areas where monads have had crossover e.g. futures are fairly common in non-functional languages.
As an aside, this is my view as to why monads have been particularly prevalent in Haskell. It's not so much that the type system can express them well, though this helps. It's that, due to Haskell's laziness, almost everything has a delay between code execution and the effect occurring.