Implementing A Successful Multiuser Access
Implementing A Successful Multiuser Access
Multiuser Access/JET
Application
By Tom Wickerath
Ever wish you could get more power and better performance from your Access applications? Experienced
Access database developers know the techniques for speeding up an application, especially a networked
application with linked tables. But just being aware of the common settings (even the default ones) that slow
down a Microsoft Access application will help you decide where you can optimize your application's
performance.
You should always split a multiuser database into front end (FE) and back end (BE) databases. A copy of the
FE database should be installed on each user's local hard drive. The BE database, which contains only the
shared tables, is located on a file server. Sharing an entire database over the network has been identified by
Microsoft personnel as the number one cause of database corruption, so don't do it. For more information on
split databases, please see the gem tip: Split the Database.
Here's a list of steps to take and habits to acquire (or avoid) to help you speed up the performance of your
Access application:
See the Microsoft Knowledge Base article: "Slow Performance When User Opens an Object with Name
AutoCorrect Enabled":
http://support.microsoft.com/?id=290181
For additional reasons why one should avoid this default option, see Access MVP Allen Browne's tip,
"Failures caused by Name Auto-Correct":
http://allenbrowne.com/bug-03.html
Note: You must disable the Track Name AutoCorrect option first. Otherwise, a subdatasheet property set to
[None] can easily revert back to [Auto].
During compaction, pages of records and indexes are reordered in contiguous blocks for faster sequential
access, and the table statistics are updated. Compacting the database allows queries to use the new table
statistics to create a new optimization plan the first time the queries are run following the compact operation.
Queries can become very slow if they are based on an old optimization plan that is no longer valid for the data
at hand.
Many Access applications migrate from a single user application to an application that needs to be used by
numerous people in the organization. The Access development environment allows -- and even encourages --
novice users to use default settings, such as Name AutoCorrect and Subdatasheets, along with techniques such
as table lookup fields, that can be performance killers in multiuser applications. Pessimistic locking and
page-level record locking usually won't show a noticeable impact on performance in a single user database,
but in a multiuser environment the read/write delays will keep the users waiting, so use optimistic locking and
row-level locking whenever possible.
Consider isolating Memo, OLE object and Hyperlink data types in separate tables with a 1:1 relationship to
the rest of the columns in the original table, since "record-level locking is not enabled for Memo data types."
See "Record-level locking" in this Microsoft KB article, about 2/3 down the page.
Dragging Recordsets across the network can also severely impact performance. An inefficient query may take
seconds to execute in a single user application, but may take several minutes to execute when accessing this
query with data stored on a file server.
Whenever a network is involved, picture a sprint runner who is forced to breath through a straw. The goal
should be to reduce the amount of data that must be transferred over the network. For example, don't open
forms bound to huge recordsets. Select only the fields needed and use criteria in queries to narrow down
exactly which records are needed. Make sure to index fields that are used to specify sorts or criteria. Similar
to this tip, bound combo boxes and list boxes should not include hundreds of records. A modification can be
made in the case that there are many hundreds of records
(http://msdn2.microsoft.com/en-us/library/aa188218(office.10).aspx), which is especially important if the
database is split with the back end on a file server.
Each user must have RWCD privileges for the shared folder, in order to share a database for simultaneous
use. The following Microsoft Knowledge Base article provides more information on this topic:
To determine which user is in your database at any given time, try the AppUser application, which you can
download from the MVPS.org Web site:
http://www.mvps.org/access/modules/mdl0055.htm
See Access MVP Tony Toews's tip, "LDB locking which a persistent recordset connection fixes":
http://www.granite.ab.ca/access/performanceldblocking.htm
See the section, "Minimize the number of connections that are made from each client," in this Microsoft
Knowledge Base article:
http://support.microsoft.com/kb/303528/EN-US/#15
Also see the fourth section, "Improve performance of linked tables," in this Microsoft Office Online tip:
http://office.microsoft.com/en-us/assistance/HP051874531033.aspx
An easy method of doing this is to create a table in the back end database that has just one record. For
example, this record might indicate the latest version number of the front end database. Create a form that is
bound to this table. Use VBA code or an Autoexec macro to open this form in hidden mode when the front
end database is opened. That's all there is to it!
Use indexes
You should establish indexes on any fields that are used for sorting or as criteria in queries. Without indexes,
the entire Recordset is transferred over the network prior to any filtering. Note: You do not need to index
foreign key fields when these fields participate in a relationship created in Tools > Relationships, as long as
you enforce referential integrity (RI). In this case Access automatically indexes the foreign key fields,
although these indexes will not show up in the indexes window. You must enforce RI before Access will
create a hidden index. (Without enforcing RI, creating relationships is little more than an exercise in drawing
lines).
Domain Aggregate Functions are used to look up a value or calculate statistics within a set of records -- a
domain. These include DCount, DLookup, DMax, DMin, DSum, DAvg, DFirst, DLast, DStDev, DStDevP,
DVar, and DVarP, all of which may adversely affect performance.
In queries that involve linked tables, avoid using VBA functions in query criteria, especially domain
aggregate functions, such as DSum, anywhere in your queries. When you use a Domain Aggregate Function,
Access may retrieve all of the data in the linked table to execute the query.
Use JETSHOWPLAN
Use the JETSHOWPLAN registry flag to see Jet's plan of execution for your queries. Instructions can be
found in the article, "Use Microsoft Jet's ShowPlan to write more efficient queries" by Susan Sales Harkins
and Mike Gunderloy. Download this zip file which includes a replacement for Listing A (which has a syntax
error in the version offered with the article), two .reg files for toggling the registry setting on and off, and two
examples of the output file, Showplan.out.
Note: The output files are text files that you can open with WordPad or NotePad. You can permanently
associate this file extension with one of these applications, which will enable you to simply double-click on
the Showplan.out file to open it.
If you have a Tab control that includes subforms, implementing the Tab-on-demand technique can be very
helpful. Please see the attached Word document that discusses this issue.
Consider implementing a method of closing inactive sessions. This will help prevent a user from keeping the
database open for long periods of time with no activity. See the Microsoft Knowledge Base article, "HOW
TO: Detect User Idle Time or Inactivity in Access 2000":
http://support.microsoft.com/?id=210297
I tend to use Const IDLEMINUTES = 20 (instead of the 1 minute setting shown in the Knowledge Base
article), and I do not display a message box as shown in the article, since message boxes are modal and this
would defeat any attempt to close:
Sub IdleTimeDetected(sngExpiredMinutes)
'Dim strMessage As String
'strMessage = "No user activity detected in the last" & vbCrLf
'strMessage = strMessage & sngExpiredMinutes & " minute(s)!"
'MsgBox strMessage, vbInformation, "No Sign of Activity!"
Application.Quit acSaveYes
End Sub
See the Microsoft Knowledge Base article, "How to keep a Jet 4.0 database in top working condition":
http://support.microsoft.com/?id=303528
See the Microsoft Knowledge Base article, "How to optimize Office Access and Jet database engine network
performance with Windows 2000-based and Windows XP-based clients":
http://support.microsoft.com/?id=889588
The path to the database should not traverse through several folders; and
Folder names should not exceed eight characters; and
Make sure that your antivirus software is not configured to scan network files.
See Access MVP Tony Toews's article, "Microsoft Access Performance FAQ":
http://www.granite.ab.ca/access/performancefaq.htm
See the Microsoft Knowledge Base article, "How To Improve Performance of Applications Using Jet 4.0":
http://support.microsoft.com/?id=240434
To avoid page-level locking with DAO 3.6, see the Microsoft Knowledge Base article, "PRB: Jet 4.0 Row-
Level Locking Is Not Available with DAO 3.60"
http://support.microsoft.com/?id=306435
See the Microsoft Knowledge Base article, "ACC2000: Optimizing for Client/Server Performance":
http://support.microsoft.com/?id=208858
This applies to Access 2002 and 2003 as well. This article includes the following advice: "NOTE: If you
make changes to fields or indexes on the server, you must relink the remote tables in Access." This is due to
the current links caching structural and connection information for the remote tables at the time of the link,
and this information never gets updated. To relink the tables, delete any existing linked tables. Compact the
database. Then reestablish the linked tables using File > Get External Data > Link Tables...
When you specify "Open databases using record-level locking" (Tools > Options | Advanced tab) you should
realize that this is a request, not a demand. Michael Kaplan has recently posted some information on this topic
on his blog site:
http://blogs.msdn.com/michkap/archive/2005/10/19/482694.aspx
See the Microsoft Knowledge Base article, "Files on Network Shares Open Slowly or Read-Only or You
Receive an Error Message":
http://support.microsoft.com/?id=814112
And, for good measure, if you have any laptop users who plug their laptops into a docking station, you might
want to be aware of a previous issue, which I have reprinted below.
"The user had a laptop. When he plugged the laptop in without the docking station, it worked fine,
but when docked, it slowed to a crawl. It turned out to be the network cards. The profile had to be
changed because the network cards were working against each other. They disabled the internal
network card for the 'IN DOCK' profile. Once that was done, the connectivity was great."
Also, I strongly encourage advising laptop users with wireless connections not to open a Jet database until
they establish a hard-wired network connection. Jet databases are very susceptible to corruption from
network disconnections, so it's best to use a stable network connection, not wireless connections. Currently,
there is no way to automatically detect a wireless connection in order to block these users, so the best policy is
to train users to use only wired connections to Jet databases.
Check hardware
As a follow-up to complaints of slow performance in Access, you might want to check out the hardware too.
This has nothing to do with Access in particular, but can increase your computer's performance for any
application you run.
Shut down all application software (Word, Excel, Access, Internet Explorer, etc.). Navigate to your
temp folders and delete all of the files present.
and
where YourUserName is what you use to log into Windows. Note: The Temporary Internet Files
folder can also be emptied by right-clicking the IE desktop icon, left-click on properties, and then
click on the Delete Files... command button. Place a check in the "Delete all offline content" option.
If you use Netscape, you should clear the Netscape disk cache as well. Click on Edit > Preferences...,
select the plus sign next to Advanced, select Cache, and click on the "Clear Disk Cache" button.
Many PC's are probably not being defragged on a regular basis (if ever). Schedule a defragment
operation after cleaning out the temporary files, compacting your Access databases, and compacting
any personal folders in Outlook or Outlook Express.
You might want to schedule the defrag to run one time per month, during a time that you know you
will not be needing your PC. Recommendation: Close all applications, including Outlook, before
running the disk defragmenter. Disable the antivirus software, the screen saver and any power
saving settings.
Tom Wickerath is a chemist at The Boeing Company in Seattle, Washington, USA. He works in the
Analytical Chemistry Group of Boeing's Materials & Process Technology (M&PT) organization. In the
early 1990's, Tom became interested in the use of database technology to avoid many of the
inefficiencies involved in using spreadsheets for the analysis of large amounts of data.
Tom has taught Microsoft Access courses to college students for three years at Bellevue Community
College in Bellevue, Washington. He has also been an active member of the Pacific Northwest Access
Developer's Group (PNWADG) since the days of Access 2.0 and served as an officer of this group
from 1998 to 2002. As a longtime Access expert, Tom has given presentations on complex Access
subjects, including tools designed for Access developers, to the Seattle Access Group, the PNWADG
and to Boeing Company personnel.
Tom has provided his expertise to Microsoft Access developers and users for years in the Microsoft
Access Newsgroups in UseNet, and he was awarded MVP for Microsoft Access in April, 2006. Check
the Microsoft.Public.Access Newsgroup for Tom's latest expert advice and feel free to post any
questions in these Newsgroups.
Special Note From The Author: "If you find that any of these tips save you a lot of time and
frustration with your database development, please consider making a tax deductible contribution to the
Northwest Kidney Center, located in Seattle, Washington, USA. I've seen first hand how valuable their
work is. With your help, they can do more research in the treatment and prevention of kidney disease,
provide increased financial assistance to patients in need, and generally make kidney patients' lives
more comfortable. You can even use PayPal to make a quick and easy donation online, and you'll feel
better knowing that you have contributed to a better quality of life for those in need. Thanks."
-- Tom Wickerath
http://www.nwkidney.org/nkc/howYouCanHelp/donate/index.html
This is particularly important for multi-user applications where each user has a copy of the
front-end database on their machine sharing the same centralized data. When the
application is enhanced (new queries, forms, reports, code, etc.), it is simply distributed to
each user. Programs like our Total Access Startup can centralize and automate the
distribution process.
If you are not familiar with a split database architecture, read our paper on Splitting
Microsoft Access Databases to Improve Performance and Simplify Maintainability
This is not a big deal for a single MS Access database application which would create the
lock file when the database is opened and maintain it until the database is closed. But in a
linked database design, the lock file on the back-end database may be created and deleted
every time a table is opened and closed. When no connections to any tables on the back end
database remain open, the lock file is deleted. That takes time.
By forcing Access to keep the linked table's database open, Access avoids creating a new
lock on the backend database every time one of its tables is used. This lets you open tables,
forms, and reports much faster. Over a network, you'll usually see a substantial
improvement with how quickly a form opens when it's based on a linked table.
The procedure below supports multiple backend databases. Edit the section with the list of
databases to open, then call this when your application starts:
OpenAllDatabases True
OpenAllDatabases False
Dim x As Integer
Dim strName As String
Dim strMsg As String
' List of databases kept in a static array so we can close them later
Static dbsOpen() As DAO.Database
If pfInit Then
ReDim dbsOpen(1 To cintMaxDatabases)
For x = 1 To cintMaxDatabases
' Specify your back end databases
Select Case x
Case 1:
strName = "H:\folder\Backend1.mdb"
Case 2:
strName = "H:\folder\Backend2.mdb"
End Select
strMsg = ""
On Error GoTo 0
If strMsg <> "" Then
MsgBox strMsg
Exit For
End If
Next x
Else
On Error Resume Next
For x = 1 To cintMaxDatabases
dbsOpen(x).Close
Next x
End If
End Sub
Additional Resources
Microsoft Access Best Practices: Finding Errors, Suggestions, and Performance
Tips in Your Databases with Total Access Analyzer
Source code for Starting Microsoft Access databases in Total Visual SourceBook
Module class for Managing Microsoft Access Linked Tables and Databases in Total
Visual SourceBook
Splitting Microsoft Access Databases to Improve Performance and Simplify
Maintainability
100+ Microsoft Access Performance Tips to Speed up Your Access Databases
More tips
Good luck!
Microsoft Access Performance FAQ - LDB locking which a persistent
recordset connection fixes
(Last updated )
When the symptoms encountered indicate that performance is acceptable with a single user
in the database but drops significantly when two or more users are in the database, the
problem may be caused by interaction with the LDB file.
In Access 2000, when a second and subsequent user tries to access a shared backend
database on the server, there seems to be a situation where Access tries to perform a delete
on the LDB file (which fails because another user is currently in the file). This attempt is
made about 15 times before silently failing and the records are returned from the linked
table.
To resolve this issue we need a persistent connection to the back-end from each of the
front-end workstations. This can be done using a bound form which is always open or by
keeping a recordset open at all times..
A good way of testing this is to ensure you are the only one opening the back end. Then run
the front end database until it gets to the main menu. At this point you should see an LDB
file present on the server with the same name as the back end.
If you don't see this LDB file then you know you don't have a persistent connection.
Bound form
The simple situation would be to create a simple form based on a table. Bound form means
that the record source of the form ha a table or query specified. Any table but one with as
few records as possible. Or create a dummy table and put one record in it. In your startup
form add the following line of code in the startup forms OnOpen event.
I always have a global options table containing such details as default corporate logo, report
banding true/false and colour, bar code font file name and so forth. I have a hidden form
bound to this table which is always open. I then reference these fields as appropriate in
code using forms!GlobalOptions!goReportBandingColour and forms!GlobalOptions!
goCorporateLogoPathandFilename.
Alternatively you can create a dummy (test) table in the backend file and create code in the
front-end file which opens a recordset on this table and persist the recordset until the front-
end app is closed. To do so:
Create an empty form. .
In the OnClose event, which will fire when the MDB is closed, close the recordset and Set
variable to nothing
Ensure you always open this form when opening the MDB. You will likely want to open
this form hidden.
David Fenton suggests using a global database connection which is always open. Same
concept as using a table in the above section.
The problem with this approach is that you need to know the path and name of the backend
MDB. While you can use a parsed linked table connection property this becomes more
work.