I built an Access database. It was originally meant to be for 6 people but I see that it's up to about 20, so that could be the problem. Anyway, the backend gets into an inconsistent state a couple of times a day. It is usually possible to resolve this by opening the backend at which point it offers to recover it and usually does so.
In response to a question in the comments: What happens is that the frontends are no longer able to read any of the tables in the backend and so throw an error.
When I open the back end is states the following:
Microsoft Access has detected that this database is in an inconsistent state, and will attempt to recover the database. During this process, a backup copy of the database will be made and all recovered objects will be placed in a new database. Access will then open the new database. The names of objects that were not successfully recovered will be logged in the "Recovery Errors table.
The only option is to press "OK". Usually, this can be done in situ, but when I have been looking at the file on a VPN I sometimes find it spins for a little while. In that case, I can copy the inconsistent file to my harddrive, open it, press OK, and let it do its thing. It usually only takes a couple of seconds. At that point it says:
Microsoft Access has recovered this database. Examine the database to verify that there are no missing database objects.
So far this has been successful apart from once when I had to go to a slightly older backup.
The database is not particularly complicated (I don't think) but it does have some VBA.
The basic idea of the database is that we are tracking lots that need to move through a set of processes but that get broken up. So we might receive 1000 items, and then have 300 of them go to prescreening, and later another 100, and so on through all the about 8 steps.
The way I've done this (which I'd be happy to change - I just don't know what to change) is to have a small submission table, which tracks the lots as they come in, and a larger table (called preorder for reasons which don't matter) which tracks the line items.
The preorder table has quite a large number of fields (about 75). But of importance to this is the quantity field and the status field. The status field tracks the items through the process (so from step 1=Receiving to 8=Shipping or whatever). The quantity tracks how many of this particular item there are from this batch with the other properties at this status.
Then there are forms for each of these steps with very similar VBA behind them. One of the fields will be basically "quantity to move to the next step" (this is one of the fields in the preorder table that is initially defaulted to 0). The user fills in this quantity (and some additional information potentially) and presses a button to process the step.
- opens a recordset (rs) for the preorder table where the status is 1 (say) and the quantity to go to the next step is >0.
- It also opens an appendonly recordset of the preorder table (rs2).
- Then it goes through the records in rs, adjusts the quantity down, and appends a new copy of the record with the new quantity and an updated status.
The below is an example from one of the screens. Others are similar, although there are some nuances baked in that may or may not be important.
Private Sub btnProcess_Click() DoCmd.Hourglass True Dim db As Database Dim rs As DAO.Recordset Dim rs2 As DAO.Recordset Set db = CurrentDb Set rs = db.OpenRecordset("SELECT * FROM tblPreorder " & _ "WHERE (((tblPreorder.StatusID)=1) AND ((tblPreorder.PSQuantity)>0));", dbOpenDynaset, dbFailOnError) If rs.EOF Then MsgBox "No records found for processing" rs.Close DoCmd.Hourglass False Exit Sub End If Set rs2 = db.OpenRecordset("tblPreorder", dbOpenDynaset, dbAppendOnly) rs.MoveFirst Do Until rs.EOF myQuantity = rs("Quantity").Value PSQuantity = rs("PSQuantity").Value rs2.AddNew For Each fld In rs.Fields SFld = fld.Name 'to catch special fields Select Case SFld 'special cases Case "ID": 'do nothing Case "Quantity": rs.Edit rs(SFld).Value = myQuantity - PSQuantity rs.Update rs2(SFld).Value = PSQuantity Case "Comment": If Len(Trim(rs("PSComment")) > 0) Then rs2(SFld).Value = Trim(rs("Comment")) & vbCrLf & Trim(rs("PSComment")) Else rs2(SFld).Value = rs(SFld) End If Case "StatusID": rs2(SFld).Value = 2 'Changes the status from 1 to 2 Case "DateChanged": rs2(SFld).Value = Now() Case "EmployeeID": rs2(SFld).Value = UserID() Case "Location": If rs("PSLocation") <> "" Then rs2(SFld).Value = rs("PSLocation") End If Case "PSDate": rs2(SFld).Value = Now() Case "PSEmployeeID": rs2(SFld).Value = UserID() Case "PSQuantity": rs.Edit rs(SFld) = 0 rs.Update rs2(SFld) = 0 Case "ReleasedFiles": 'do nothing Case Else: rs2(SFld).Value = fld.Value End Select Next fld rs2.Update rs.MoveNext Loop rs.Close rs2.Close c = Me.CurrentRecord Me.Requery On Error Resume Next: DoCmd.GoToRecord acDataForm, Me.Name, acGoTo, c MsgBox "Items moved to BNC Request" DoCmd.Hourglass False End Sub
- If you see anything obviously wrong, of course, let me know. It works like a charm when it's just me testing it. I've never managed to replicate the issue, but I can see that it's happening in the production version.
- I've followed the advice on a couple of websites on avoiding this (e.g. https://www.techrepublic.com/article/get-it-done-top-10-ways-to-prevent-access-database-corruption/). I'm basically tinkering and trying things because I don't fundamentally know what the issue is. E.g. I switched from recordset to DAO.recordset in the above code. It didn't seem to make a difference, so I might go back. Everyone is using Access 365 and the backend is Access 365 so it seemed to make sense to use an Access specific recordset. I close all the recordsets (rs.close); I compile the VBA; I have saved the frontend as an accde; All users use the accde from their own computers; All users are using wired connections; I have version control so everyone is using the latest frontend.
- The main question, I suppose: How can I diagnose this? I could easily imagine putting a new table in to track who's hitting the process button and when and which one. I'm probably going to do that to see what happens in the lead up to a corruption. But what are your recommendations for what to track and are there any other tricks and tips to get to the root of this?
Code specific questions:
- Recordset or DAO.Recordset or some other thing?
- dbFailOnError or dbSeeChanges or some other thing?
- rs.Edit ... rs.Update on the couple of fields that get updated as I loop (current code)? Or one rs.Edit ... rs.Update on either side of the loop?
- Should I be putting a manual hold or something to prevent people from running similar code simultaneously? It offends me a little to do so, since I kind of imagine that the people who make MS Access will do a better job than me at that sort of thing. But I could probably set a flag somewhere that literally makes people wait their turn. I'd still be worried about a race condition.
Wider database questions:
- Are there other secret settings that need to be adjusted to minimize this issue? Everyone is using MS Access 365. Name AutoCorrect Options, Filter lookup options, caching, data type support options. I don't really know what these do and I suspect there are gotchas everywhere! I had a similar database that I used for years without issue, so I was lulled into a false sense of security here.
- Have I structured this completely wrong? I'm okay to do a whole bunch of work - it would just be nice to have a good sense that it would solve the problem!