UPDATE ballots
SET status = "burned"
WHERE contents = :ballot AND status = "unused"
which, if it succeeds, then sends the ballot to the decryption oracle with the private key, to be decrypted and sent back to the user; and UPDATE ballots
SET status = "used"
, voter_id = :voter
, choice = :choice
WHERE contents = :ballot
AND status = "unused"
AND region = :region
which, if it succeeds, then sends back a confirmation that this user has been logged with that ballot and made that choice for that ballot.If you allow people to access the decryption oracle without going through that first pathway, which simultaneously checks if the ballot was not spent and immediately spends it into the "burning" pathway, then either of those opens up the space to attacks which decrypt individual ballots. With that said, just about any auditing mechanism applied to the decryption oracle would be revealing the existence of those attacks anyway so you can still get a measure of security without this.
You can potentially even distribute the database (e.g. over a blockchain among several political parties), but as far as I can tell the decryption authority would still need to be centralized and could be a single-point of failure. (In this case it would be a program which is watching that blockchain and interacting with it via some “I publish a burned ballot onto the ledger after I think the blockchain has passed N blocks ahead of the ledger request to burn that ballot” algorithm, and nodes in the network need to reject requests to cast ballots that they think have been requested to be burnt.)