74
Chapter 5: Improving Application Performance 5-1 CHAPTER 5: IMPROVING APPLICATION PERFORMANCE Objectives The objectives are: Write optimized C/AL code. Optimize Sum Index Field Technology (SIFT) tables. Optimize cursors by using the right C/AL code statements. Optimize key design and usage in Microsoft Dynamics ® NAV. Avoid locks and deadlocks. Troubleshoot performance issues related to the graphical user interface. Setup index hinting. Optimize data entry using the Bulk Insert feature. Use some tips and tricks that are useful when optimizing Microsoft Dynamics NAV on Microsoft ® SQL Server ® . Introduction This chapter describes how to solve and avoid performance issues by optimizing C/AL code and indexes. Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Na2009 enus sql_05

Embed Size (px)

Citation preview

Page 1: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-1

CHAPTER 5: IMPROVING APPLICATION PERFORMANCE Objectives

The objectives are:

• Write optimized C/AL code. • Optimize Sum Index Field Technology (SIFT) tables. • Optimize cursors by using the right C/AL code statements. • Optimize key design and usage in Microsoft Dynamics® NAV. • Avoid locks and deadlocks. • Troubleshoot performance issues related to the graphical user

interface. • Setup index hinting. • Optimize data entry using the Bulk Insert feature. • Use some tips and tricks that are useful when optimizing Microsoft

Dynamics NAV on Microsoft® SQL Server®.

Introduction This chapter describes how to solve and avoid performance issues by optimizing C/AL code and indexes.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 2: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-2

Optimizing C/AL Code There are several areas where developers need to focus when optimizing Microsoft Dynamics NAV applications. These areas are as follows, in order of importance (based on the processing costs):

• SIFT • Keys and Indexes • Cursors • Locks • Suboptimum Code • Graphical User Interface (GUI)

This lesson contains general guidelines for writing C/AL code. It explains which areas to pay extra attention to.

Keys

Keys define the order in which data is stored in your tables. Logically, records are stored sequentially in ascending order, sorted by the clustered index. Physically, records can be sorted on disk in a different order. You can speed up searches in tables by defining several keys which sort information in different ways.

Defining keys is one thing; using the correct keys is also very important for performance. When writing code or creating reports, you must use the appropriate keys to get maximum performance. If you do not specify an adequate key, Microsoft SQL Server will try to find the best index.

When a key is created in Microsoft Dynamics NAV, an index is created for that key in the corresponding SQL Server table. By default, a primary key is translated into the clustered unique index and secondary keys become non-clustered unique indexes. The only time when data in a table in SQL Server is stored in a sorted manner is when there is a clustered index defined in the table. The data is then stored sorted according to the fields in the clustered index.

The primary key does not always provide the best sorting order for records. A typical example is a ledger entry table. The primary key of a ledger entry table is a single field, Entry No. However, most queries on ledger entry tables use fields other than the primary key, such as Posting Date, No. or Status. Data manipulation and retrieval on these tables can be optimized by physically storing the records in the order in which they are often queried.

The physical storage order of records is determined by the clustered index. By default, if you do not specify a clustered index, the primary key will be used as clustered index. To select a key as clustered index, you can set the Clustered property of a key to Yes. You can have only one clustered index per table. Tables without clustered indexes are called "heaps."

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 3: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-3

A heap can be considered as an unordered table or a collection of unordered data pages. This means that rows will be inserted into the heap anywhere there is space. As data is fragmented, retrieving data from a heap causes many data page reads. Often this results in reading the complete table. For this reason, heaps are to be avoided.

Indexes that are only designed for sorting purposes can create overhead during insert, update, delete statements on SQL Server. That is why sometimes we recommend not to maintain these indexes on SQL Server. On the other hand, Microsoft Dynamics NAV will request a dynamic cursor most of the time and, in general, it is a good idea that an index fits both the order by and the where clause.

Cursors

When writing code to retrieve or browse through record sets in Microsoft Dynamics NAV, you can use a number of instructions. Retrieving data can be done for different reasons, such as you want to modify the data, or you just want to check whether records exist that meet specific criteria (without doing anything with the records). In SQL Server Option, each FIND instruction will be translated into one or more SQL statement that read data a particular way (using a specific cursor and isolation level).

Very often, performance issues are caused by improper use of FIND statements, causing wrong cursors and isolation levels to be used. As a result of this, data is returned but not used, or data is returned as read-only when it must be modified. Using the correct FIND statement improves data retrieval and processing.

Locks

There are additional considerations to make when working with Microsoft Dynamics NAV on SQL Server. Microsoft Dynamics NAV is designed to read without locks and locks only if it is necessary, following optimistic concurrency recommendations. If records are to be modified, that intent should be indicated to the Database Management System (DBMS) (use explicit locking), so that the data is read properly.

Implicit Locking

The following table demonstrates implicit locking. The C/AL code is mapped to the equivalent action on SQL Server:

C/AL Code SQL Server

Cust.FIND('-'); SELECT * FROM Customer WITH (READUNCOMMITTED) (the retrieved record timestamp = TS1)

Cust.Name := 'John Doe';

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 4: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-4

C/AL Code SQL Server

Cust.MODIFY; SELECT * FROM Customer WITH (UPDLOCK, REPEATABLEREAD) (the retrieved record timestamp = TS2) performs the update UPDATE Customer SET Name = 'John Doe' WITH (REPEATABLEREAD) WHERE TimeStamp <= TS1

The reason for such a complex execution is that:

• The data is read with the READUNCOMMITED isolation level, but because users will update it, they need to ensure that they read committed data and issue an update lock on it to prevent other users from updating the same records.

• The data is originally read uncommitted. Users need to lock the record and ensure that they update the record with an original timestamp. If somebody else changes the record, that person receives the following error message: "Another user has modified the record since users retrieved from the database."

Explicit Locking

If developers indicate that their intention is to modify the record by using explicit locking, they can eliminate the unacceptable behavior completely, as shown in the following table:

C/AL Code SQL Server Cust.LOCKTABLE; Indicates intention of modification.

Cust.FIND('-'); SELECT * FROM Customer WITH (UPDLOCK) (the retrieved record timestamp = TS1)

Cust.Name := 'John Doe';

Cust.MODIFY; UPDATE Customer SET Name = 'John Doe' WITH (REPEATABLEREAD) (the retrieved record timestamp is guaranteed to be TS1)

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 5: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-5

Instead of re-reading the data, Microsoft Dynamics NAV can immediately proceed to issue an UPDATE statement. This behavior explains the many occurrences of the following piece of code in the standard application:

IF Rec.RECORDLEVELLOCKING THEN Rec.LOCKTABLE;

The LOCKTABLE instruction is used to read the data with the correct isolation level. On SQL Server, LOCKTABLE will not lock any records. It will change the way records are accessed.

The RECORDLEVELLOCKING function returns true if the code is being executed on SQL Server, otherwise it returns false.

Suboptimum Code

Taking performance into consideration often influences programming decisions. Often, users pay a high price in terms of performance because the code is not optimized. For example, if developers do not use explicit locking, or if they program bad loops and provoke problems with NEXT. Therefore, developers must review their code and check for the presence of performance degrading statements or scenarios, such as the following:

• Read the same table multiple times. • Use COUNT to check whether records exist that meet specific

criteria. • Use MARKEDONLY instead of pushing records to a temporary

table and read them from there. • Use WHILE FIND to browse through a record set. (The WHILE

FIND always looks for the first or last record in a set and therefore automatically disables the read ahead mechanism).

• Use IF NOT INSERT THEN MODIFY.

Additionally, there are features in the application that require special attention. For example, the advanced dimensions functionality can be cost demanding when you use a lot of dimensions and have analysis views updated automatically on posting.

Other functionalities that require attention to are as follows:

• Automatic Cost Posting, Automatic Cost Adjustment, Expected Cost Posting to G/L

• Discount Posting ="All Discounts" • Credit Warnings ="Both Warnings" • Stockout Warnings

Users should review the application setup for the performance aspect and make corrective actions possible.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 6: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-6

BLOBs

BLOBs can also cause performance issues because they are stored in a specific way in the SQL Server database. First of all, BLOB fields in Microsoft Dynamics NAV have a compressed property, which indicates whether the data will be saved compressed. By default, the property is set to True, which means that Microsoft Dynamics NAV will use a special algorithm to compact the BLOB data. However, SQL Server does not know this algorithm and needs additional operations to handle the data.

Secondly, as Microsoft Dynamics NAV always generates SELECT * FROM queries, all data from a table is returned, including BLOB fields. As the BLOB is stored separately from the other table columns, SQL Server needs extra operations (page reads and CPU power) to collect the BLOB data. (Because a BLOB can take up to 2 GB, BLOBs are often spread over several pages.) However, the BLOB data is not relevant for many transactions and processes (such as item lookups, order posting, and item journal posting). Nevertheless, the BLOB data is always read.

When using BLOBs, we recommend that you:

• Set the Compressed property on BLOB fields to False. As a consequence, SQL Server needs less operations to retrieve the BLOB data.

• Keep BLOBs away from transactions and processes, by storing the BLOB data in separate tables. You do not have to delete or disable the BLOB field; not using the field will already do this.

Problems with NEXT

In some cases, the NEXT command causes the biggest performance problem in Microsoft Dynamics NAV. The problem is that NEXT uses a cursor, and you cannot change the isolation level in the middle of the data retrieval. This means that data has to be retrieved again, using a new SELECT statement. This imposes a serious performance penalty in SQL Server and, in some cases, leads to very lengthy execution.

Typical scenarios where NEXT causes problems are as follows:

• The filtering of a record set is changed • The sorting of a record set is changed • A key value is changed • The isolation level is changed • NEXT is called in the middle of nowhere (on a record that is

retrieved using GET or another way) • NEXT in combination with FINDFIRST or FINDLAST

The following examples show some of the scenarios listed here.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 7: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-7

Change Filtered Values

In the following example, a field used to filter a table is assigned a new value in the record set.

SETRANGE(FieldA, Value); FIND('-'); REPEAT ... FieldA := NewValue; ... MODIFY; ... UNTIL NEXT = 0;

By changing the filtered field, the record will be moved outside the current record set. When calling the NEXT instruction, Microsoft Dynamics NAV needs to execute extra queries to detect its cursor position in the original record set and will finally retrieve a complete new record set.

This is no longer the case from version 5.0 and later versions, because instead Microsoft Dynamics NAV will request a dynamic cursor for this type of statement. However, for example, FindSet with parameter (FALSE) Microsoft Dynamics NAV will reissue the queries to find data.

Change Sorting

The same happens when you change the sorting of a record set after it was retrieved.

SETCURRENTKEY(FieldA); FIND('-'); REPEAT SETCURRENTKEY(FieldB); FIND('-'); ... UNTIL NEXT = 0;

When you change the sorting of a record set and retrieve the first record, Microsoft Dynamics NAV calls for a new record set (and an extra cursor).

Change Isolation Level

The following code shows an example of a changed isolation level.

FINDFIRST; REPEAT ... MODIFY; UNTIL NEXT = 0;

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 8: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-8

Initially, the record set is retrieved with the READUNCOMMITTED isolation level. However, the MODIFY instruction requires a higher isolation level to modify the data. Since it is not possible to change the isolation level, in the middle of the process, Microsoft Dynamics NAV requests for a new record set.

Jumping Through Record Sets

The following code is a typical example of "jumping through a record set".

SETRANGE(FieldA, Value); FIND('-'); ... REPEAT SETRANGE(FieldB,Value2); ... FIND('+'); ... SETRANGE(FieldB); ... UNTIL NEXT = 0;

In this example, a new extra filter is applied to a record set, which leads to a new record set. The FIND('+') instruction requires a new cursor. When the NEXT statement is reached, Microsoft Dynamics NAV needs several queries to reposition its cursor in the original record set.

FINDFIRST /FINDLAST with NEXT When NEXT is used in combination with FINDFIRST or FINDLAST, you go from a non-cursor to a cursor situation. The FINDFIRST instruction retrieves the data without cursors. NEXT causes a re-read with a cursor.

SETRANGE(FieldA); FINDFIRST; REPEAT ... UNTIL NEXT = 0;

Change Key Values

In the following code, a field that is part of an active key is changed.

SETCURRENTKEY(FieldA); FIND('-'); REPEAT ... FieldA := NewValue; ... UNTIL NEXT = 0;

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 9: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-9

By changing the key field, you disturb the current sorting and at the same time disable the benefits of SQL Server's read-ahead mechanism. When calling the NEXT instruction, SQL Server tries to read the next record based on the new key value.

Solutions

To eliminate performance problems with NEXT, consider the following solutions:

• Browse sets nicely (without jumping) and by preference use a separate looping variable.

• Restore original key values, sorting, and filters before the NEXT statement.

• If you modify them multiple times then read records to temporary tables, modify within, and write back afterward.

• Use FINDSET(TRUE,TRUE).

Note also that FINDSET(TRUE,TRUE) is not a "real solution." It is merely a reduction of the costs and should be used only as a last resort.

Graphical User Interface (GUI)

The GUI overhead can slow down the client, if, for example, a dialog is refreshed 1000 times in a loop. GUI overhead can also cause increased server trips. When users use the default SourceTablePlacement = <Saved> on forms, it costs more than using Last or First. Users should review all forms showing data from large tables to look for performance problems.

Another big overhead may come from the "Find As You Type" feature. When Find As You Type is enabled, the system is forced to do another query for each keystroke. This causes extra queries to be sent to the server.

Finally, displaying many FlowFields on normal forms such as Customer Card, Item Card, and so on, can adversely affect form retrieval time, as the FlowFields have to be calculated. This can be a problem especially on list forms (showing multiple records at a time).

The basic principle is to display these FlowFields on demand rather than by default when the user is not even interested in the information provided. If you need to display many FlowFields, use special forms such as Customer Statistics, Item Statistics, Customer Entry Statistics, Item Entry Statistics, Customer Sales, Item Turnover, and so on.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 10: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-10

SIFT SIFT was originally implemented on SQL Server by using extra summary tables called SIFT Tables, that were maintained through table triggers directly in the table definitions on SQL Server. When an update was performed on a table that contains SIFT indexes, a series of additional updates were necessary to update the associated SIFT tables. This imposed an extra performance penalty - one that grew as the number of SIFT indexes on a table increased.

With regards to performance, SIFT tables are one of the biggest Microsoft Dynamics NAV performance problems on SQL Server, as one record update in the base table produces a potentially massive stream of Input/Output (I/O) requests with updates to the records in the SIFT tables, possibly blocking other users during that time.

In Microsoft Dynamics NAV 5.0 SP 1, Microsoft replaced SIFT tables with V-SIFT, which are indexed views. However, Microsoft Dynamics NAV developers will likely be involved with older versions, where they may encounter performance issues related to the SIFT tables. It is important for Microsoft Dynamics NAV developers to know how SIFT tables worked before version 5.0 SP1 and how to troubleshoot performance issues related to these tables.

Optimizing SIFT Tables

SIFT tables are used in Microsoft Dynamics NAV 5.0 and older, to implement SIFT on the SQL Server, and store aggregate values for SumIndexFields for keys in the source tables.

The overhead of the separate SIFT tables is massive and should be carefully considered for activation. By default, Microsoft Dynamics NAV activates the SIFT tables when developers create a new index with SumIndexFields. Developers should review all of the existing SIFT indexes and determine whether they need to keep them activated.

Developers can de-activate the creation and maintenance of a SIFT table by using the MaintainSIFTIndex property in the Microsoft Dynamics NAV key designer. If they make the property false, and there is no other maintained SIFT index supporting the retrieval of the cumulative sum, Microsoft Dynamics NAV asks SQL Server to calculate the sum itself.

For example, if developers have a Sales Line table and put Amount in the SumIndexFields for the primary key ("Document Type, Document No., Line No."), a new SIFT table "CRONUS International Ltd_$37$0" is created and maintained. When a CALCSUM is used to display a FlowField in Microsoft Dynamics NAV showing the sum of all Sales Lines for a specific Sales Header (Order ORD-980001), the resulting query looks as follows:

SELECT SUM(s29) FROM "CRONUS International Ltd_$37$0" WHERE "bucket" = 2 AND "f1" = 1 AND "f3" = 'ORD-980001'

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 11: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-11

If developers disable the SIFT table by clearing the MaintainSIFTIndex check box, Microsoft Dynamics NAV still works, and the resulting query looks as follows:

SELECT SUM("Amount") FROM "CRONUS International Ltd_$Sales Line" WHERE "Document Type" = 1 AND "Document No_" = 'ORD-980001'

This is a very light load on CPU overhead compared to the massive costs of maintaining the SIFT table.

SIFT tables are very beneficial when users need to sum a large number of records. With that in mind, developers can check existing SIFT tables and see whether they need all of the level of details. There is no need, for example, to store a cumulative sum of just a few records. Developers can use the property SIFTLevels and disable specific levels by clearing the Maintain for a specific bucket check box, thus reducing the overall overhead of the SIFT table while still keeping the SIFT table in place for summing the larger number of records. However, there is no need, for example, to keep cumulative sums on the top level buckets if they are used, such as a total of Quantity on Location Code in the Item Ledger Entry table, since users always filter on Item No.

Table Optimization

As explained earlier, every time you update a key or a SumIndexField in a base table all of the SIFT tables associated with the base table must also be updated. This means that the number of SIFT tables that you create, as well as the number of SIFT levels that you maintain, affects performance.

If you have a very dynamic base table that constantly has records inserted, modified and deleted, the SIFT tables that are associated with it will constantly need to be updated.

As a consequence, the SIFT tables can get very large, both because of the new records that are entered and because the records that are deleted from the base table are not removed from the SIFT tables. This can badly affect performance, especially when the SIFT tables are queried to calculate sums.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 12: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-12

To keep the SIFT tables from growing very large and to maintain performance, it is important to optimize the tables regularly. To initiate the optimization process, click File, Database, Information, Tables, Optimize in the Microsoft Dynamics NAV client.

FIGURE 5.1 TABLE OPTIMIZATION ON LARGE TABLES

The optimization process removes any entries that contain zero values in all numeric fields from each SIFT table. The removal of these redundant entries frees space and makes updating and summing SIFT information more efficient. At the same time, the optimization process rebuilds all indexes.

As an alternative, you can run an SQL query on the SIFT table to determine how many records there are with zero values in all the sum fields in the table. If there are a large number of these records, you can either initiate the optimization process in Microsoft Dynamics NAV and remove them or schedule a query to delete these records on SQL Server.

VSIFT

Starting with Microsoft Dynamics NAV 5.0 SP 1, the SIFT tables are replaced by indexed views. Separate SIFT tables are no longer part of Microsoft Dynamics NAV on SQL Server.

Microsoft Dynamics NAV 5.0 SP1 uses "indexed views" to maintain SIFT totals. Indexed views are a standard SQL Server feature. An indexed view is similar to a normal SQL Server view except that the contents have been materialized (computed and stored) to disk to speed up the retrieval of data.

One indexed view is created for each SIFT key that is enabled. When you create a SIFT key for a table, you must set the MaintainSIFTIndex property for that key to Yes to enable the SIFT key and create the indexed view.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 13: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-13

After the indexed view has been created, the contents of the view are maintained so that changes are made to the base table. If you set the MaintainSIFTIndex property for that key to No, the indexed view is dropped and totals are no longer maintained. The indexed view that is used for a SIFT key is always created at the most finely-grained level. Therefore, if you create a SIFT key for AccountNo.,PostingDate, the database will store an aggregated value for each account for each date. This means that in the worst case scenario, 365 records multiplied by the number of unique Account No. must be summed to generate the total for each account for a year.

Tuning and Tracing VSIFT

As a result of using indexed views, SIFT keys are exposed to SQL Server tracing and tuning tools. For example, the SQL Server Profiler can display information about which indexed views are maintained and the cost associated with maintaining them. This makes it easier for you to make informed decisions about which SIFT indexes are required for optimal performance.

Demonstration: Analyzing SIFT Configuration with SQL Server Profiler

Perform the following steps to use SQL Profiler to determine the best SIFT index configuration.

1. In the Windows Taskbar, click Start > All Programs > Microsoft SQL Server 2008.

2. Open Performance Tools. 3. Open SQL Server Profiler. 4. Connect to the NAV-SRV-01 Database Engine. 5. Choose a trace template, for example: Tuning. 6. Go to Events Selection. 7. Expand Performance. 8. Select Showplan XML. By default, this information is not included. 9. Press Run to start the trace.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 14: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-14

There are different possibilities when setting up a new trace. For this example, the output is not saved and you will need to manually stop the trace.

FIGURE 5.2 TRACING SIFT CONFIGURATION

It is possible to customize the SQL Profiler trace with options. For this example, Showplan XML is added to the Standard (default) template. Showplan XML provides greater flexibility in viewing query plans. In addition to the usability benefits, Showplan XML also provides an overview of certain plan specific information, such as cached plan size, memory fractions (grants distributed across operators in the query plan), parameter list with values used during optimization, and missing indexes.

When data is inserted, updated, or deleted in a table, the SIFT keys that have been defined and enabled for the specific table are maintained. Maintaining these SIFT indexes has a performance overhead. The size of the performance overhead depends on the number of keys and the SumIndexFields defined for each table.

Defining Efficient Indexes

There are several things to consider when designing SIFT indexes. It is important to only create the needed SIFT indexes but at the same time be sure that these indexes cover the sum queries required by Microsoft Dynamics NAV.

If a table does not contain a large number of records, there is no need to maintain any SIFT indexes for that table. In this case, set the MaintainSIFTIndex property to No.

Be sure to notice the number of SIFT keys defined in the system to ensure that you only maintain the SIFT keys that are important. Combine SIFT indexes, if possible.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 15: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-15

You do not have to maintain a SIFT key for a total that is only used periodically. Periodically generated totals can easily be generated by a report instead.

FIGURE 5.3 SHOWPLAN XML EXECUTION PLAN DETAILS

Even though indexed views are used to support SIFT indexes, when a sum is requested, the SIFT index that best matches the filter or sum fields will be used. In this case, single SIFT indexes that contain all key fields and all sum fields will be used. If such a SIFT index does not exist, the sum will be calculated from the base table (SIFT indexes will not be used).

As with regular indexes, the key fields in the SIFT index that are used most regularly in queries will be positioned to the left in the SIFT index. As a general rule, the field that contains the greatest number of unique values will be placed on the left, with the field that contains the second greatest number of unique values positioned to the right and so on. Integer fields generally contain the greatest number of unique values. Option fields contain a fairly small number of values.

Even if a specified filter does not supply values for the most left columns in the SIFT index it can still be used and add value. The reason is that the algorithm will use the SIFT index and that the SIFT index/indexed view only contains the sums, so the data that needs to be traversed to calculate a total is very much less than going to the base table.

FIND Instructions Unlike Microsoft Dynamics NAV Classic database server, SQL Server can be characterized as a set-based engine. This means that SQL Server is very efficient when retrieving a set of records from a table, but less so when records are accessed one at a time.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 16: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-16

Cursors

Because SQL Server is set-based, it does not provide a fast way to do this retrieval. SQL Server uses mechanisms called cursors for record-level access. There are different types of cursors which have different capabilities. For example, the forward-only cursor allows fast reading from top to bottom, while the dynamic cursor allows retrieval in either direction.

Typically, the FIND instruction is used to retrieve records in a table or a filtered record set. Often, the FIND instruction is used in combination with a filter instruction such as SETFILTER or SETRANGE (to check whether records exist that meet specific criteria) or in combination with the NEXT instruction (to loop through a set of records). In both cases, the FIND instructions will be translated into a Transact-SQL statement that uses a cursor and returns data.

Compared to retrieving sets of records, cursors are very expensive. When writing C/AL code to retrieve records, it is important to consider the purpose of the code and to use the correct instructions (and, as a consequence, the correct cursors).

By default, the way the dynamic cursors are used is not very efficient. Because cursors have a big effect on performance, handling them in a different way can yield significant improvements. For example, there is no reason to create a cursor for retrieving a single record. To optimize cursors, the following four Microsoft Dynamics NAV commands can be used:

• ISEMPTY • FINDFIRST • FINDLAST • FINDSET

ISEMPTY

The ISEMPTY function allows you to determine whether a C/SIDE table or a filtered set of records is empty.

The following code samples check for the presence of a Master record in the Customer table:

// Code Sample 1 Customer.SETRANGE(Master, TRUE); IF NOT Customer.FIND('-') THEN ERROR('No Master Customer record has been defined.');

// Code Sample 2 Customer.SETRANGE(Master, TRUE); IF Customer.ISEMPTY THEN ERROR('No Master Customer record has been defined.');

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 17: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-17

When executed, the first code sample will be translated into an SQL statement that uses cursors. In addition, if a record exists, it is returned to the client, causing extra network traffic and disk reads. However, in this case, you do not want to retrieve a record. To avoid this, you can use the ISEMPTY instruction, as shown in the second code sample.

When executed, the first code sample will be translated into an SQL statement that uses cursors. In addition, if a record exists, it is returned to the client, causing extra network traffic and disk reads. However, in this case, you do not want to retrieve a record. To avoid this, you can use the ISEMPTY instruction, as shown in the second code sample.

When executed, this code results in the following T-SQL command:

SELECT TOP 1 NULL FROM …

The ISEMPTY instruction will not cause cursors to be used. Note that NULL is used, which means that no record columns are retrieved from the database (as opposed to '*', which would get all columns). This makes it a very efficient command that causes just a few bytes to be sent over the network. This can be a significant improvement as long as subsequent code does not use the values from the found record.

FINDFIRST

Retrieving the first record in a table can also be an unnecessarily expensive command. Consider the following code samples:

// Code Sample 1 Customer.SETRANGE(Master, TRUE); IF NOT Customer.FIND('-') THEN ERROR('No Master Customer record has been defined.');

// Code Sample 2 Customer.SETRANGE(Master, TRUE); IF NOT Customer.FINDFIRST THEN ERROR('No Master Customer record has been defined.');

In the first code sample, the FIND instruction will generate a cursor. To avoid this cost, you can use the FINDFIRST instruction, as shown in code sample 2. The FINDFIRST instruction retrieves the first record in a set based on the current key and filters. As with ISEMPTY, FINDFIRST does not use cursors.

When executed, the following T-SQL statement is generated:

SELECT TOP 1 * FROM ... ORDER BY ...

Note that in this case the * is used, so all columns of the record are returned.

Use this function instead of FIND('-') when you need only the first record.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 18: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-18

WARNING: If using a REPEAT/UNTIL NEXT loop, do not use this command, because the NEXT will need to create a cursor for fetching the subsequent records.

FINDLAST

FINDLAST works in the same manner as the FINDFIRST instruction. The FINDLAST command retrieves the last record in a set (based on the current key and filters), but, like FINDFIRST, FINDLAST does not use cursors. Consider the following two code samples:

// Code Sample 1 Message.SETCURRENTKEY(Date); IF Message.FIND('+') THEN MESSAGE('Last message is dated %1.', FORMAT(Message.Date));

// Code Sample 2 Message.SETCURRENTKEY(Date); IF Message.FINDLAST THEN MESSAGE('Last message is dated %1.', FORMAT(Message.Date));

This second code sample retrieves the last record in the set, and does not use cursors. When executed, this T-SQL is generated:

SELECT TOP 1 * FROM ... ORDER BY ... DESC

You should use this function instead of FIND('+') when you need only the last record in a table or set.

WARNING: If doing a REPEAT/UNTIL NEXT(-1) loop, do not use this command, because the NEXT will have to create a cursor for fetching the subsequent records.

FINDSET

FINDSET retrieves a set of records based on the current key and filter and can be used when you need to browse through a set of records. Very often, the scenario of code sample 1 is used:

// Code Sample 1 IF RecordVariable.FIND('-') THEN REPEAT UNTIL RecordVariable.NEXT = 0; // Code Sample 2 IF RecordVariable.FINDSET THEN REPEAT UNTIL RecordVariable.NEXT = 0;

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 19: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-19

From previous paragraphs, you know that FIND will generate a cursor in code sample 1, which is to be avoided. Therefore, it is better to use the FINDSET instruction, as shown in code sample 2. Unlike the FIND('-') command, FINDSET does not use cursors. When executed, the T-SQL result looks as follows:

SELECT TOP 500 * FROM ...

The value 500 in the code snippet here comes from the database setup. Its default value has been set to 50. The recommended value for this parameter is the average number of sales lines on a sales order.

REPEAT/UNTIL NEXT browses through the records locally on the client machine. This is the recommended way to retrieve sets quickly, without any cursor overhead.

Note that FINDSET only allows you to loop through the record set from the top down. If you want to loop from the bottom up, use FIND('+').

Use this function only when you explicitly want to loop through a record set. You should only use this function in combination with REPEAT/UNTIL.

The complete syntax for the FINDSET instruction is as follows:

Ok := Record.FINDSET([ForUpdate][, UpdateKey])

Although you can use it without, the FINDSET instruction has two optional parameters which might improve performance. The ForUpdate parameter indicates whether you want to modify the records or not. The UpdateKey parameter indicates whether you want to modify a field in the current key. The UpdateKey parameter does not apply when ForUpdate is FALSE. Using FINDSET without parameters corresponds to FINDSET(FALSE, FALSE). You can use it to obtain a read-only record set. This uses no server cursors and the record set is read with a single server call.

NOTE: FINDSET only allows you to loop through the record set from the top down. If you want to loop from the bottom up, use FIND('+').

We recommend that you use FINDSET to loop through a set without updating it, as shown in the following example.

SalesLine.SETFILTER("Purch. Order Line No.",'<>0'); IF SalesLine.FINDSET THEN BEGIN REPEAT CopyLine(SalesLine); UNTIL SalesLine.NEXT = 0; END;

If you set any or both of the parameters to FALSE, you can still modify the records in the set but these updates will not be performed optimally.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 20: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-20

The variations of the FINDSET instructions will be discussed in the next sections.

FINDSET(TRUE)

We recommend that you set the ForUpdate parameter to TRUE to modify any records in the set. If you set the parameter to TRUE, the LOCKTABLE command is issued immediately before the records are read. This variation of FINDSET locks the set that is read, so it is equivalent to a LOCKTABLE followed by FINDSET.

FINDSET(TRUE) uses the read-ahead mechanism to retrieve several records instead of just one. Unlike the FINDSET instruction, FINDSET(TRUE) uses a dynamic cursor. The main purpose of this command is to raise the isolation level before reading the set because the resulting records are to be modified.

This example shows how to use the FINDSET function to loop through a set and update a field that is not within the current key.

SalesLine.SETRANGE("Document Type",DocumentType); SalesLine.SETRANGE("Document No.",DocumentNo); IF SalesLine.FINDSET(TRUE, FALSE) THEN BEGIN REPEAT SalesLine."Location Code" := GetNewLocation(SalesLine); SalesLine.MODIFY; UNTIL SalesLine.NEXT = 0; END;

We recommend that LOCKTABLE be used with FINDSET for small sets, and that the FINDSET(TRUE) command be used for sets larger than 50 records (the Record Set parameter in the Alter Database window).

We do not recommend that you use FINDSET with a large result set. That is a result set that is larger than the Record Set Size parameter. In this case, you should use Find('-'). The reason is that FINDSET means that you are working with a confined set of records and Microsoft Dynamics NAV will use this information to optimize what is being done on SQL server.

A good example of using the FINDSET(TRUE) command is for the read of a big set of records and the need to modify those records. For example, when going through all G/L entries for a specific account, and changing a field value based on the record condition, the filtered set will probably have more than 50 records. This might be done as follows:

GLEntry.SETRANGE("G/L Account No.", "6100"); IF GLEntry.FINDSET(TRUE) THEN REPEAT IF (GLEntry.Amount > 0) THEN BEGIN GLEntry."Debit Amount" := GLEntry.Amount;

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 21: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-21

GLEntry."Credit Amount" := 0; END ELSE BEGIN

GLEntry."Debit Amount" := 0; GLEntry."Credit Amount" := -GLEntry.Amount; END; GLEntry.MODIFY; UNTIL GLEntry.NEXT = 0;

A good example of using the LOCKTABLE and FINDSET command (as opposed to using the FINDSET(TRUE) command) is for the read of a small set of records and the need to modify those records. For example, when going through all sales lines for a specific order, and changing the value of several fields, the filtered set will probably have less than 50 records.

SalesLine.SETRANGE("Document Type","Document Type"::Order); SalesLine.SETRANGE("Document No.",'S-ORD-06789'); SalesLine.LOCKTABLE; IF SalesLine.FINDSET THEN REPEAT SalesLine."Qty. to Invoice" := SalesLine."Outstanding Quantity; SalesLine."Qty. to Ship" := SalesLine."Outstanding Quantity; SalesLine.MODIFY; UNTIL SalesLine.NEXT = 0;

FINDSET(TRUE, TRUE)

This variation of FINDSET(TRUE) allows the modification of a key value of the current sorting order of the set. The following example shows how to use the FINDSET function to loop through a set and update a field that is within the current key.

SalesShptLine.SETRANGE("Order No.",SalesLine."Document No."); SalesShptLine.SETRANGE("Order Line No.",SalesLine."Line No."); SalesShptLine.SETCURRENTKEY("Order No.","Order Line No."); IF SalesShptLine.FINDSET(TRUE, TRUE) THEN BEGIN REPEAT SalesShptLine."Order Line No." := SalesShptLine."Order Line No." + 10000; SalesShptLine.MODIFY; UNTIL SalesShptLine.NEXT = 0; END;

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 22: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-22

Like FINDSET(TRUE), the command uses a dynamic cursor, with the main purpose of raising the isolation level before starting to read the set (because the set needs to be modified). However, it does not use the read-ahead mechanism. Instead it retrieves one record at a time because the set is expected to be invalidated within the loop. Avoid using this command, since the loop code should be changed to a more efficient method of working, such as using a different variable for browsing through the set.

Important to understand is that the isolation level and the cursor type have nothing to do with one another. The isolation level is connection wide whereas the cursor type influences the current statement. Microsoft Dynamics NAV uses a dynamic cursor to get a dynamic result set, a result set that contains our own changes. This is done to be consistent with the old classic database. If Microsoft Dynamics NAV used another cursor type or no cursor at all then it might have to reissue the query whenever the code was changing data in the current table.

A good example of using the FINDSET(TRUE,TRUE) command (as opposed to using FIND command) is for the read of a set of records and the need to modify a key value. This should be avoided. If there is not a way to avoid this, use FINDSET(TRUE,TRUE). For example, going through all sales lines for a specific order, and changing key value, the filtered set will probably have less than 50 records in the set. This can be done as follows:

SalesLine.SETCURRENTKEY("Document Type","Document No.","Location Code"); SalesLine.SETRANGE("Document Type","Document Type"::Order); SalesLine.SETRANGE("Document No.",'S-ORD-06789'); SalesLine.SETFILTER("Location Code",''); IF SalesLine.FINDSET(TRUE,TRUE) THEN REPEAT IF SalesLine.Type = SalesLine.Type::Item THEN SalesLine."Location Code" := 'GREEN'; IF SalesLine.Type = SalesLine.Type::Resource THEN SalesLine."Location Code" := 'BLUE'; SalesLine.MODIFY; UNTIL SalesLine.NEXT = 0;

Note that the example can be easily changed into more efficient code using FINDSET as opposed to FINDSET(TRUE,TRUE) and using a separate variable to modify the records. This can be done as follows:

SalesLine.SETCURRENTKEY("Document Type","Document No.","Location Code"); SalesLine.SETRANGE("Document Type","Document Type"::Order); SalesLine.SETRANGE("Document No.",'S-ORD-06789'); SalesLine.SETFILTER("Location Code",''); SalesLine.LOCKTABLE; IF SalesLine.FINDSET THEN REPEAT SalesLine2 := SalesLine;

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 23: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-23

IF SalesLine.Type = SalesLine.Type::Item THEN SalesLine2."Location Code" := 'GREEN'; IF SalesLine.Type = SalesLine.Type::Resource THEN SalesLine2."Location Code" := 'BLUE'; SalesLine2.MODIFY; UNTIL SalesLine.NEXT = 0;

There is a parameter in Microsoft Dynamics NAV that is used to set up the maximum number of records retrieved from the database (File, Database, Alter, Advanced tab, Caching, Record Set = 50). If the set is bigger than the maximum, Microsoft Dynamics NAV will continue to work but it will replace the reading mechanism with a dynamic cursor. If there is an indication that this will occur, use the 'old' FIND('-') command as opposed to FINDSET.

Use FINDSET for forward direction only; it will not work for REPEAT/UNTIL NEXT(-1).

Also, if the LOCKTABLE command is used prior to the FINDSET, the set is locked, and records can be modified within the loop.

A good example of an efficient use of cursors (using the 'old' FIND command), is for the read of a big set of records, for example all G/L Entries for a specific account, probably with more than 50 records in the set.

GLEntry.SETRANGE("G/L Account No.", "6100"); IF GLEntry.FIND('-') THEN REPEAT UNTIL GLEntry.NEXT = 0;

A good example of using the new FINDSET command (as opposed to using the 'old' FIND command), is for the read of a small set of records, such as all sales lines in a sales order, probably always with less than 50 records. This can be done as follows:

SalesLine.SETRANGE("Document Type","Document Type"::Order); SalesLine.SETRANGE("Document No.",'S-ORD-06789'); IF SalesLine.FINDSET THEN REPEAT TotalAmount := TotalAmount + SalesLine.Amount; UNTIL SalesLine.NEXT = 0;

Keys One of the largest typical Microsoft Dynamics NAV overheads is the cost of indexes. The Microsoft Dynamics NAV database is over-indexed, since customers require certain reports to be ordered in different ways, and the only way to do it is to create a Microsoft Dynamics NAV key to sort data in these specific ways.

SQL Server can sort results quickly if the set is small, so there is no need to keep indexes for sorting purposes only. For example, in the Warehouse Activity Line

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 24: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-24

table, there are several keys that begin with Activity Type and No. fields, such as the following:

• Activity Type,No.,Sorting Sequence No. • Activity Type,No.,Shelf No. • Activity Type,No.,Action Type,Bin Code

The issue here is that these indexes are not needed on SQL Server, because the Microsoft Dynamics NAV code always filters on Activity Type and No. when using these keys. With SQL Server, the Query optimizer looks at the filter and realizes that the clustered index is Activity Type,No_,Line No_ and that the set is small, and that there is no need to use an index to retrieve the set and return it in that specific order. It will use only the clustered index for these operations.

Additionally, the entire functionality is not used by customers, so if they never pick the stock by Sorting Sequence No. for example, there is no need to maintain the index.

Developers should analyze the existing indexes with a focus on use and benefits compared to the overheads, and decide what action is need. Disable the index completely, using the key property Enable, using the KeyGroups property, or using the MaintainSQLIndex property. Indexes that remain active can change structure using the SQLIndex property. Developers can also cluster the table by a different index.

If an index exists, sorting by the fields matching the index will be faster, but modifications to the table will be slower.

When you write a query that searches through a subset of the records in a table, be careful when defining the keys both in the table and in the query so that Microsoft Dynamics NAV can quickly identify this subset. For example, the entries for a specific customer will usually be a small subset of a table that contains entries for all the customers.

The time that is required to complete a query depends on the size of the subset. If a subset cannot be located and read efficiently, performance will deteriorate.

To maximize performance, you must define the keys in the table so that they facilitate the queries that you will have to run. These keys must then be specified correctly in the queries.

For example, you want to retrieve the entries for a specific customer. To do this, you apply a filter to the Customer No. field in the Cust. Ledger Entry table.

SQL Server makes stricter demands than Classic Database Server on the way that keys are defined in tables and on the way they are used in queries. Microsoft Dynamics NAV Classic Database Server has been optimized for low selectivity keys. For example, if there is an index that consists of the Document Type and Customer No. fields and the application filters on the Customer No. field only,

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 25: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-25

Classic Database Server will search through the index branches and retrieve the result set quickly. However, SQL Server is not optimized to do that, so it scans from the beginning to the end of a range and, in many cases, this results in a non-clustered index scan.

To run the query efficiently on SQL Server, you need to define a key in the table that has Customer No. as the first field. You must also specify this key in the query. Otherwise, SQL Server will be unable to answer this query efficiently and will read through the entire table.

Define your keys and queries with SQL Server in mind, as this will ensure that your application can run as efficiently on both server options.

When designing keys, the following guidelines can be considered:

• Redesign keys so that their selectivity becomes higher by putting Boolean, Option, and Date fields toward the end of the index.

• Set the MaintainSIFTIndex property to No on small tables or temporary tables (such as Sales Line, Purchase Line and Warehouse Activity Line).

• Set the MaintainSQLIndex property to No for indexes that are only used for sorting purposes.

• Reduce the number of keys on hot tables. • Use the SQLIndex property to optimize a key on SQL Server.

But be careful with this property. If the SQL Server index differs from the Microsoft Dynamics NAV index it can lead to problems with the ORDER BY and the WHERE CLAUSE not fitting the same index. This is largely a problem with dynamic cursors.

• Reduce the number of records in static tables (by archiving or using data partitioning).

Keys and Performance

Searching for specific data is usually easier if several keys have been defined and maintained for the table holding the desired data. The indexes for each of the keys provide specific views that enable quick flexible searches. However, there are both advantages and drawbacks to using a large number of keys.

If you increase the number of secondary keys marked as active, performance will improve when you read data, but will deteriorate when updating information (because indexes must be maintained for each secondary key).

When you decrease the number of active sortings, performance will slow down when reading data, but updates will be faster.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 26: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-26

The decision whether to use few or many keys is not easy. The choice of appropriate keys and the number of active keys to use should be the best compromise between maximizing the speed of data retrieval and maximizing the speed of data updates (operations that insert, delete, or modify data). In general, it may be worthwhile to deactivate complex keys if they are rarely used.

The overall speed of C/SIDE depends on the following factors:

• The size of the database • The number of active keys • The complexity of the keys • The number of records in your tables • The speed of your computer and its disk system

Key Properties

The keys associated with a table have properties that describe their behavior, just as tables and fields do. When you create a key, C/SIDE automatically suggests several default values for these properties. Depending on the purpose of the key, you may want to change these default values.

Enabled Property

Enabled property simply turns the specific key on and off. It might have been there for temporary reasons and is no longer needed. If a key is not enabled and is referenced by a C/AL code or CALCSUMS function, users will get a run-time error.

KeyGroups Property

Use this property to select the (predefined) key groups to which the key belongs. As soon as developers assign this key to one or more key groups, they can selectively activate or deactivate the keys of various groups by enabling and disabling the key groups.

To make use of the key groups for sorting, choose the Key Groups option on the Database Information window which appears when they select File, Database, Information, and then press the Tables button. There are key groups that are defined already, such as Acc(Dim), Item(MFG), and so on, but users can create more and assign them to keys they want to control this way.

The purpose of key groups is to make it possible to set up a set of special keys that are used rarely (such as for a special report that is run once every year). Since adding lots of keys to tables will eventually decrease performance, using key groups makes it possible to have the necessary keys defined, but only active when they are really going to be used.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 27: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-27

MaintainSQLIndex Property

This property determines whether a SQL Server index that corresponds to the Microsoft Dynamics NAV key should be created (when set to Yes) or dropped (when set to No). A Microsoft Dynamics NAV key is created to sort data in a table by the required key fields. However, SQL Server can sort data without an index on the fields to be sorted. If an index exists, sorting by the fields matching the index will be faster, but modifications to the table will be slower. The more indexes there are on a table, the slower the modifications become.

In situations where a key must be created to allow only occasional sorting (for example, when running infrequent reports), developers can disable this property to prevent slow modifications to the table.

SQLIndex Property

This property allows users to define the fields that are used in the SQL index. The fields in the SQL index can:

• Differ from the fields defined in the key in Microsoft Dynamics NAV.

• Be arranged in a different order.

If the key in question is not the primary key, and the SQLIndex property is used to define the index on SQL Server, the index that is created contains exactly the fields that users specify and may not be a unique index. It will only be a unique index if it contains all the fields from the primary key.

When users define the SQL index for the primary key, it must include all the fields defined in the Microsoft Dynamics NAV primary key. Users can add extra fields and rearrange the fields to suit their needs.

Be careful when using the property SQLIndex. It can backfire because the SQL Server query and the SQL index will per definition have a mismatch, as described at the following location (http://blogs.msdn.com/nav_developer/archive/2009/04/10/beware-the-sql-index-property-on-nav-5-0-sp1.aspx):

Clustered Property

Use this property to determine which index is clustered. By default, the index that corresponds to Microsoft Dynamics NAV primary key will be made clustered.

We recommend that you make sure the primary key and the clustered key is the same. If they are not then SQL Server will add the clustered index fields to every index internally while Microsoft Dynamics NAV will add all the primary key fields causing keys to be very long.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 28: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-28

MaintainSIFTIndex

This property allows you to determine whether SIFT structures should be created in SQL Server to support the corresponding SumIndexFields for the Microsoft Dynamics NAV key.

SumIndexFields are created in Microsoft Dynamics NAV to support FlowField calculations and other fast summing operations. SQL Server can sum numeric data by scanning the table. If the SIFT structures exist for the SumIndexFields, summing the fields is faster, especially for large sets of records, but modifications to the table are slower because the SIFT structures must also be maintained.

In situations where SumIndexFields must be created on a key to enable FlowField calculations, but the calculations are performed infrequently or on small sets of data, you can disable this property to prevent slow modifications to the table.

Also be aware that even the new implementation that uses indexed views will cause blocking in the database. For example if two users update the total for an account on the same date.

Enable/Disable Keys using C/AL Code

To make the information in the tables as useful as possible, many of the tables have several predefined sorting keys.

Keys can be set up as part of a key group, which you can enable and disable without risk. To add a key to a key group, set the KeyGroups property to the name of an existing key group.

Microsoft Dynamics NAV will generally perform better when you disable key groups (because the system does not have to maintain the keys included in the key group).

Adding a large number of keys to database tables decreases performance. However, by making the keys members of predefined key groups you can have the necessary keys defined and only activate them when they will be used.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 29: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-29

Key groups can be maintained in the Database Key Groups window. To open the window, select File > Database Information > Tables and click the Key groups button.

FIGURE 5.4 THE DATABASE KEY GROUPS WINDOW

In this window you can enable and disable existing key groups. You can also add new and delete existing key groups. If you delete an existing key group, all keys that belong to this group will be disabled.

In Microsoft Dynamics NAV 5.0, key groups can also be enabled and disabled using C/AL Code. To do this, the following instructions have been introduced:

• KEYGROUPDISABLE • KEYGROUPENABLE • KEYGROUPENABLED

KEYGROUPDISABLE allows you to disable a key group and all related keys in all tables. KEYGROUPENABLE does the opposite, it allows you to enable a key group and all related keys. KEYGROUPENABLED allows you to check whether a key group is currently enabled.

The following code sample shows how to activate the ABC key group, run some code, and disable the key group again.

KEYGROUPENABLE('ABC'); ... KEYGROUPDISABLE('ABC');

Enabling a key group can take some time, depending on the number of keys and the amount of data.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 30: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-30

Locks, Blocks and Deadlocks When data is read from the database, Microsoft Dynamics NAV, outside transaction or in a Browse/Update No Locks transaction mode, uses the READUNCOMMITTED isolation level, meaning that any other user can modify the records that are currently being read. Data that is read is considered "dirty" because it can be modified by another user. When data is modified, Microsoft Dynamics NAV reads the record again with the UPDLOCK isolation level and compares the timestamp of the record. If the record is 'old,' the following Microsoft Dynamics NAV error displays: "Another user has modified the record after you retrieved it from the database."

The tradeoff is that care must be taken when writing code that modifies the data. This requires that locking and blocking be employed to synchronize access, but deadlocks - a condition where one or more competing processes are queued indefinitely - can occur as a side-effect. The following subtopics discuss strategies for synchronizing data access while avoiding deadlock.

Every write transaction implies an automatic implicit lock and unlock. Explicit locking is also possible using the LOCKTABLE instruction. Explicit locking is necessary to preserve data consistency during complex processes, such as the Posting function.

The isolation level can be changed to a more restrictive setting, such as UPDLOCK. In this level, records that are read are locked, meaning that no other user can modify the record. This is referred to as pessimistic locking, and causes the server to protect the record in case there is a need to modify it - making it impossible for others to modify.

An example of a lock of a customer record is as follows:

Cust.LOCKTABLE; Cust.GET('10000'); // Customer 10000 is locked Cust.Blocked := TRUE; Cust.MODIFY; COMMIT; // Lock is removed

If the record is not locked up front, the following situation can occur:

User A User B Comment

Cust.GET('10000'); User A reads record without any lock.

Cust.GET('10000');

User B reads same record without any lock.

Cust.Blocked := TRUE; Cust.MODIFY; COMMIT:

User B modifies record.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 31: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-31

User A User B Comment

Cust.Blocked := FALSE; Cust.MODIFY;

User A gets an error: "Another user has modified the record after you retrieved it from the database."

ERROR SUCCESS

Blocking

When other users try to lock data that is currently locked, they are blocked and have to wait. If they wait longer than the defined time-out, they receive the following Microsoft Dynamics NAV error: "The ABC table cannot be locked or changed because it is already locked by the user who has User ID XYZ."

If you receive this error, you can change the default time-out with File, Database, Alter, Advanced tab, Lock Timeout check box and Timeout duration (sec) value.

Based on the previous example, where two users try to modify the same record, the data that is intended to be modified can be locked. This prevents other users from doing the same. This is shown in the following example:

User A User B Comment

Cust.LOCKTABLE; Cust.GET('10000');

User A reads record with a lock.

Cust.LOCKTABLE;Cust.GET('10000');

User B tries to read the same record with a lock

waiting... User B waits and is blocked, because the record is locked by user A.

Cust.Blocked := FALSE; Cust.MODIFY;

waiting... User A modifies the record. User B is kept waiting.

COMMIT; Lock is released. Data is now sent to User B.

Cust.Blocked := TRUE; Cust.MODIFY;

User B successfully modifies record.

COMMIT; Lock is released.

SUCCESS SUCCESS

There is a potential situation when blocking cannot be resolved by the server in a good way. The situation arises when one process is blocked because another process has locked some data. The other process is also blocked because it tries to lock the first process data. Only one of the transactions can be finished. SQL Server terminates the other and sends the following error message to the client: "Your activity was deadlocked with another user"

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 32: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-32

For example, consider a case in which two users are working concurrently and trying to get one another's blocked records, as shown in this pseudo code:

User A User B Comment

Cust.LOCKTABLE; Vend.LOCKTABLE;

Cust.LOCKTABLE; Vend.LOCKTABLE;

Indicates that the next read will use UPDLOCK

Cust.FINDFIRST;

Vend.FINDFIRST; A blocks Record1 from the Customer table. B blocks Record 1 from the Vendor table.

... ...

Vend.FINDFIRST;

Cust.FINDFIRST; A wants B's record, while B wants A's record. A conflict occurs.

"Your activity was deadlocked with another user"

SQL Server detects deadlock and arbitrarily chooses one over the other, so one will receive an error.

SUCCESS ERROR

Because SQL Server supports record level locking, there may be a situation where these two activities bypass one another without any problems. Suppose that in this example user A and B try to read the last record in the other table, no conflicts arise, as no records are in contention. The deadlock will only occur if they lock the same rows but in a different order. Note that there would be a deadlock if one of the tables is empty, or contained few records only.

A large number of deadlocks can lead to major customer dissatisfaction, but deadlocks cannot always be avoided completely. To minimize the number of deadlocks, do the following:

• Process tables in the same sequence. • Process records in the same order. • Keep the transaction length to a minimum.

If all code always processed the data in the same order then there would be no deadlocks, only locks. One reason why deadlocks are so expensive is that SQL Server does not immediately discover a deadlock. The initial deadlock discovery frequency is 5 seconds.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 33: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-33

Although locking and blocking are necessary to support concurrency, it can lead to decreased performance, especially when tables are locked longer than necessary. To reduce locking time, you can do the following:

• Keep the lock time to a minimum by locking the resources as late as possible and releasing the locks as soon as possible.

• Test data validity for an entire transaction before starting the transaction.

• Keep transactions as short as possible. • Use adequate keys. • Never allow user input during a transaction. • Test conditions of data validity before the start of locking. • Allow some time gap between heavy processes so that other users

are less affected. • Make sure SQL Server has sufficient memory.

If the transaction is too complex or there is limited time, consider discussing with the customer the possibility of over-night processing of heavy jobs by using a Microsoft Dynamics NAV Application Server. This avoids the daily concurrency complexity and the high costs of rewriting the code.

If the over-night processing is not possible because of the complexity of the processes, as a last resort, revert to serializing the code by ensuring that conflicting processes cannot execute in parallel. This can be done by creating a so-called locking semaphore table that is locked at the beginning of a transaction. As long as the table remains locked, other users cannot start the same transaction. This avoids deadlocks, but at the same time, it affects concurrency.

For more information about locking order rules, see the Performance Audits chapter.

Graphical User Interface When upgrading older installations to SQL Server 2005, users can experience poor performance when they search and filter on data in Microsoft Dynamics NAV and when they open and browse lists.

Two main problems were identified:

• SQL Server has a feature called "parameter sniffing," which may cause suboptimal plans to be used by SQL Server.

• Microsoft Dynamics NAV queries that inherently return an empty result set may cause poor response times.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 34: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-34

Parameter Sniffing

Microsoft Dynamics NAV uses queries that contain parameters on SQL Server.

The first time a Microsoft Dynamics NAV query is executed, SQL Server calculates a plan for accessing the data in the most efficient way. This plan is based on the actual values in the search criteria and parameters. Every time a query is sent to SQL Server, SQL Server makes a query-plan for that query. Then it caches this plan to re-use it for identical queries.

This is known as "parameter sniffing." This may lead to suboptimal performance because the first values sent may not be representative of subsequent queries.

In Microsoft Dynamics NAV 5.0 SP1 the way that queries are sent to SQL Server has been restructured. As of this version, SQL Server will make query plans that are optimized for average parameter values rather than extreme parameter values. This method ensures that SQL Server makes the plan, without forcing it in a certain direction with index hints or the recompile-option.

Microsoft Dynamics NAV 5.0 SP1, issues statements that disables parameter sniffing. This is done by forcing the SQL client to not defer the plan calculation until the statement is executed. This requires an extra database roundtrip the first time an SQL statement is constructed or sent. A built-in statement cache means that the extra roundtrip will only occur once if users are working in an isolated area of the application.

This change insures against sub-optimal plans but at the same time inflicts an extra roundtrip for the database. Also, there may only be queries with parameters that fit the sub-optimal plan, so the new behavior may be worse.

This method guarantees that SQL Server's query plan will not be affected by the parameter values. It means that sometimes, SQL Server is prevented from making the optimal query plan for a certain set of parameter values. But remember that the query plan will be re-used for other parameter values. So, at the expense of having a few highly optimized queries, the method provides optimized queries with better consistency.

Another cost of this method, is that now Microsoft Dynamics NAV requires an extra roundtrip to SQL Server. However, this only happens the first time the query is run. If the same query is run again, Microsoft Dynamics NAV will only run the second query (sp_cursorexecute).

You can revert to the old behavior by modifying the contents in the $ndo$dbproperty tables as follows.

UPDATE [$ndo$dbproperty] SET [diagnostics]=[diagnostics]+1048576

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 35: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-35

Adding the 1048576 value to the Diagnostics column of the row in ndo$dbproperty will turn off the No Deferred Prepare behavior. The behavior will be the same as in Microsoft Dynamics NAV 4.0 SP3.

As an alternative solution, you can manually implement plan guides for poorly performing queries. To implement a plan guide, you should know the combination of the query and the parameters for which you implemented the plan guide.

Another possibility is using the $ndo$dbconfig table to add the OPTION (RECOMPILE) query hint. The OPTION (RECOMPILE) query hint instructs the instance of SQL Server to compile a new query plan for the query instead of using a cached plan.

Each plan guide works for a specified query. However, you can use a $ndo$dbconfig table to add the OPTION (RECOMPILE) query hint for all queries.

To do this, perform the following steps:

1. Run the following script to create the $ndo$dbconfig table in the Microsoft Dynamics NAV database:

-- Step 1 CREATE TABLE [$ndo$dbconfig] (config VARCHAR(512) NOT NULL) -- Step 2 GRANT SELECT ON [$ndo$dbconfig] TO public -- Step 3 INSERT INTO [$ndo$dbconfig] VALUES('UseRecompileForTable="G/L Entry"; Company="CRONUS International Ltd."; RecompileMode=1;')

2. Grant the SELECT permission to the Public role for the $ndo$dbconfig table, as shown in Step 2 in the script here.

3. Use the $ndo$dbconfig table to specify the tables for which you want to add the OPTION (RECOMPILE) query hint. Step 3 in the script here shows how to add the OPTION (RECOMPILE) query hint for queries in the G/L Entry table.

To add the OPTION (RECOMPILE) query hint for other tables, you can create a new line in the $ndo$dbconfig table for each table.

• You do not need to specify the value of the Company parameter if you want to add the OPTION (RECOMPILE) query hint for all companies in the database.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 36: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-36

• The RecompileMode parameter can be a value from 0 to 3. The RecompileMode parameter values represent the following modes: o 0: Do not use the OPTION (RECOMPILE) query hint. o 1: Use the OPTION (RECOMPILE) query hint when you

browse the form in a table. The default value is 1. o 2: Use the OPTION (RECOMPILE) query hint with operations

that are caused by the C/AL code. o 3: Always use the OPTION (RECOMPILE) query hint.

• Use the default value of the RecompileMode parameter unless you

need to use other recompile modes.

RECOMPILE consumes CPU time so it can also degrade performance, especially with recompilemode 3, but also with the other recompilemodes.

Queries Returning Empty Result Sets

-- Step 1 CREATE TABLE [$ndo$dbconfig] (config VARCHAR(512) NOT NULL) -- Step 2 GRANT SELECT ON [$ndo$dbconfig] TO public -- Step 3 INSERT INTO [$ndo$dbconfig] VALUES('UseRecompileForTable="G/L Entry"; Company="CRONUS International Ltd."; RecompileMode=1;')

Index and Rowlock Hinting It is possible to force SQL Server to use a particular index when executing queries for FIND and GET statements. This can be used as a workaround when SQL Server's Query Optimizer picks the wrong index for a query.

What is Index Hinting?

Index hinting can help avoid situations where SQL Server's Query Optimizer chooses an index access method that requires many page reads and generates long-running queries with response times that vary from seconds to several minutes. Selecting an alternative index can give instant 'correct' query executions with response times of milliseconds. This problem usually occurs only for particular tables and indexes that contain certain data spreads and index statistics.

In the rare situations where it is necessary, you can direct Microsoft Dynamics NAV to use index hinting for such problematic queries. When you use index hinting, Microsoft Dynamics NAV adds commands to the SQL queries that are sent to the server. These commands bypass the usual decision-making of SQL

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 37: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-37

Server's Query Optimizer and force the server to choose a particular index access method.

WARNING: This feature should only be used after all the other possibilities have been exhausted, for example, updating statistics, optimizing indexes or re-organizing column order in indexes.

Setup Index Hinting

To set up index hinting, you must first create a configuration parameter table in the Microsoft Dynamics NAV database that contains the index hints. To create the table, you can use the following script:

CREATE TABLE [$ndo$dbconfig] (config VARCHAR(512) NOT NULL) GRANT SELECT ON [$ndo$dbconfig] TO public

Next, you need to enter parameters into the table that will determine some of the behavior of Microsoft Dynamics NAV when it is using this database.

You can add additional columns to this table. The length of the config column should be large enough to contain the necessary configuration values, as explained, but does not have to be 512.

The following examples show how you can add index hints for specific tables.

INSERT INTO [$ndo$dbconfig] VALUES('IndexHint=Yes; Company="CRONUS International Ltd."; Table="Item Ledger Entry"; Key="Item No.","Variant Code"; Search Method="-+";Index=3')

This statement will hint the use of the $3 index of the CRONUS International Ltd_$Item Ledger Entry table for FIND('-') and FIND('+') statements when the Item No.,Variant Code key is set as the current key for the Item Ledger Entry table in the CRONUS International Ltd. company. To disable the hint, either delete the hint or execute the same statement with IndexHint=No;

Note that the following:

• If the company is not supplied, the entry will match all the companies.

• If the search method is not supplied, the entry will match all the search methods.

• If the index ID is not supplied, the index hinted is the one that corresponds to the supplied key. In most cases this is the desired behavior.

• If the company/table/fields are renamed or the table's keys redesigned, the IndexHint entries must be modified manually.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 38: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-38

The following statement will hint the use of the $3 index of the CRONUS International Ltd_$Item Ledger Entry table for every search method when the Item No.,Variant Code key is set as the current key for the Item Ledger Entry table in the CRONUS International Ltd. company.

INSERT INTO [$ndo$dbconfig] VALUES('IndexHint=Yes; Company="CRONUS International Ltd."; Table="Item Ledger Entry"; Key="Item No.","Variant Code"; Search Method=;Index=3')

Rowlock Hinting

SQL Server has an advanced locking mechanism that decides how data will be locked, either by table, by page, or by row. Although SQL Server tends to apply record-level locking, SQL Server can decide to escalate multiple row locks into a page or a table lock to free system resources. This is better for performance, but not for concurrency (as table and page locks might lock too much data).

To prevent SQL Server from choosing a locking method, you can activate the Always Rowlock database option, so that Microsoft Dynamics NAV will send ROWLOCK hints to the SQL Server with every query. By default, the Always rowlock option is not enabled.

Without ROWLOCK hints, SQL Server can decide at what level it will lock. The advantage is that SQL Server requires less memory to maintain all the locks, so performance will increase. The disadvantage is that page locking is not as fine-grained as record locking. Therefore a user may be locking too many records. The probability of getting blocks is greater, so concurrency is reduced.

Activating row locking keeps the lock granularity small and reduces the probability of blocks and locks. The disadvantage is that administering all row locks requires more system memory and creates an additional load on the master database. If you have a high transaction volume dealing with large result sets, row locking can cause an overall decrease of performance (if the master database reacts too slowly due to the high number of lock administrations).

We do not recommend activating this option. If you do activate it, make sure that SQL Server has sufficient memory to maintain the locks as row locks require more memory.

Bulk Insert Analysis of customer feedback has shown that many performance problems are related to locking and long-running transactions. In particular, INSERTs were causing poor performance because of many server roundtrips, update of SIFT tables (one INSERT to the General Ledger Entry table caused 24 additional INSERT calls), and many indexes.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 39: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-39

To resolve the scalability and general performance issues, changes were made to include automatic bulk inserts. The automatic bulk insert feature has nothing to do with SQL Server bulk inserts.

Microsoft Dynamics NAV 5.0 SP1 automatically buffers inserts to send them to SQL Server at the same time. By using bulk inserts, the number of server calls is reduced and performance is improved.

This feature also improves scalability by delaying the insert until the last possible moment in the transaction. This reduces the time that records are locked and also delays the implicit contention on SIFT indexes.

Software developers who want to write high performance code which uses this feature should understand the following. Records are sent to SQL Server:

• When COMMIT is called, either explicitly or when execution of a code unit ends.

• When you call MODIFY or DELETE on the table. • When you call any FIND, CALCFIELDS, or CALCSUMS on the

table.

Records are not buffered if you are using the return value from an INSERT call. For example, if you write "IF (GLEntry.INSERT) THEN", records are not buffered if any of the following conditions are true:

• The table where you insert the records contains BLOB fields. • The table where you insert the records contains Variant fields. • The table where you insert the records contains RecordID fields. • The table where you insert the records contains fields that have the

AutoIncrement property set to Yes.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 40: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-40

Bulk Insert Example

The following code shows a loop of INSERTs to the General Ledger Entry table. Nothing is inserted to the table before the code reaches the COMMIT statement at the end of the example.

IF (JnlLine.FINDSET) THEN BEGIN GLEntry.LOCKTABLE; IF (GLEntry.FindLast) THEN GLEntry."Entry No.":= GLEntry."Entry No."+1 ELSE GLEntry."Entry No.":=1; REPEAT GLEntry."Entry No.":= GLEntry."Entry No." +1; … GLEntry.INSERT; UNTIL JnlLine.NEXT = 0; END; COMMIT; //All INSERTs are sent.

Best Practices This lesson contains some general guidelines on how to optimize Microsoft Dynamics NAV on SQL Server.

Performance Strategies and Tuning Checklist

When you are responsible for maintaining a Microsoft Dynamics NAV database and keeping it running as efficiently as possible, it is best to follow a tuning methodology.

This section describes a basic tuning methodology that can be used as a guideline and can be adapted to your individual needs.

The methodology consists of the following eight steps:

1. Define the problem/issue 2. Monitor the system 3. Analyze monitoring results 4. Create a hypothesis 5. Propose a solution 6. Implement changes 7. Test solution 8. Return to step 2

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 41: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-41

Define the Problem/Issue

You first need to understand and document the problem and the environment.

• Determine the problem. • Document and validate parameters: database size, tuning parameters,

There are many checklists available or you can create your own. • Look at the system as a whole.

This step is important to determine how you will approach the problem or where the bottleneck might be located.

Remember to talk to both the IT staff and the end-users. Inquire what they were doing when the problem occurs. Investigate the problem.

Monitor the System

Monitoring the system is used for discovering the problem or tuning the system. The goal of this step is to collect baseline information and to make an initial determination of the possible problem(s).

Use the following tool(s) to monitor the system:

• Operating system tools: Performance Monitor, Task Manager, Event Viewer.

• SQL Server tools: Error log, system tables, Dynamic Management Views, Activity Monitor, SQL Server Profiler, Database Engine Tuning Advisor.

• Analyze SQL Server, the Operating system and hardware parameters.

Baseline information is very important to determine initial issues and to determine whether changes make an improvement. This step should be documented in detail.

Analyze monitoring results

When you have completed an initial assessment and collected data, you must analyze and interpret this data. This analysis is important because it allows you to determine the problem and its cause. The analysis should be done carefully and should include the following areas of study:

• Analyze monitoring data. • Review error logs. • View customer performance data from their monitoring software.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 42: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-42

This assessment should be documented. It will be the basis of your report to the customer or management. This should also include data about the following:

• CPU utilization. • I/O utilization and response time. • Memory utilization. • Errors reported in the error log. • Wait statistics (if available).

By carefully analyzing performance data, you may be able to determine the problem immediately, or you might be able to create a theory about possible contributing factors of the problem. This step and the next may benefit from having more than one person participate to provide ideas, experience, and guidance.

Create a Hypothesis

When you have analyzed the monitoring and log data, you are ready to populate a theory about the cause of the problem. This may sound more complex than it is actually. Creating a hypothesis is as simple as formulating a theory and documenting it. If you do not document the hypothesis, it can be easy to stray from proper testing of this hypothesis. The goal is to determine what the problem is.

• Create a theory: I/O problem, locking problem. • Document your theory. • Back up the theory with data.

Propose a Solution

After you have created the hypothesis, you are ready to develop a solution to the performance problem. In many cases, you will be unable to immediately solve the problem. Instead, you may need to develop a test to further narrow down the problem. Your test can be designed to split the problem or improve some aspect of the system. The solution consists of the following topics:

• Develop a solution. • Develop a validation plan. • Document expected results.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 43: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-43

Implement Changes

After you have theorized the problem and developed a solution or test, it is time to implement changes. These change implementations can take the following forms:

• A hardware change. • A configuration parameter change. • Adding an index. • Changing C/AL code.

Implementing change should be done very carefully. Changes should be categorized to no risk, moderate risk and high risk. If possible, first test the change on a test system, before you implement it on production.

Test the Solution

The final step is to actually run the test. Some tips and best practices for changes are as follows:

• Change only one thing at a time. • Document the result of the change. • Compare performance after the test to the baseline metrics. • If possible, test the change in a nonproduction environment. • If possible, run load tests.

Return to Step 2

After you have started testing the solution, return to step 2: Monitor the System, to collect data about the state of the system while the test is running. Follow the methodology until you run out of time, budget, or problems.

By documenting each step, you will get better results and be better able to create professional and complete reports on the engagement, the problem, the solution and the results.

Storage Top 10 Best Practices

Correct configuration of I/O subsystems is critical for optimal performance and operation of SQL Server systems. The following are some of the most common best practices that the SQL Server team recommends with respect to storage configuration for SQL Server.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 44: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-44

Understand the I/O characteristics of SQL Server and the specific I/O requirements / characteristics of your application

To be successful in designing and deploying storage for your application, you need to understand your application's I/O characteristics and SQL Server I/O patterns.

Performance monitor is the best place to capture this information for an existing application. Some of the questions you should ask yourself are as follows:

• What is the read versus write ratio of the application? • What are the typical I/O rates (I/O per second, MB/s and size of the

IOs)? Monitor the perfmon counters: o Average read bytes/sec, average write bytes/sec o Reads/sec, writes/sec o Disk read bytes/sec, disk write bytes/sec o Average disk sec/read, average disk sec/write o Average disk queue length

More / faster spindles are better for performance

• Ensure that you have a sufficient number of spindles to support your I/O requirements with an acceptable latency.

• Use filegroups for administration requirements such as backup / restore, partial database availability, and so on.

• Use data files to "stripe" the database across your specific I/O configuration (physical disks, LUNs, and so on).

Try not to "over" optimize the design of the storage; simpler designs generally offer good performance and more flexibility

• Unless you understand the application very well avoid trying to over optimize the I/O by selectively placing objects on separate spindles.

• Be sure to consider the growth strategy up front. As your data size grows, how will you manage growth of data files / LUNs / RAID groups? It is much better to design for this in the beginning than to rebalance data files or LUN(s) later in a production deployment.

Validate configurations before deployment

• Do basic throughput testing of the I/O subsystem before deploying SQL Server. Make sure these tests achieve your I/O requirements with an acceptable latency.

• Understand that the of purpose running the SQLIO tests is not to simulate SQL Server's exact I/O characteristics but to test maximum throughput achievable by the I/O subsystem for common SQL Server I/O types.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 45: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-45

Always put log files on RAID 1+0 (or RAID 1) disks

This provides the following:

• Better protection from hardware failure. • Better write performance.

In general RAID 1+0 provides better throughput for write-intensive applications. The performance gained varies based on the hardware vendor's RAID implementations. The most common alternative to RAID 1+0 is RAID 5. Generally, RAID 1+0 provides better write performance than any other RAID level providing data protection. This includes RAID 5.

Isolate log from data at the physical disk level

• When this is not possible (for example, consolidated SQL environments) consider I/O characteristics and group similar I/O characteristics (all logs) on common spindles.

• Combining heterogeneous workloads (workloads with very different I/O and latency characteristics) can have negative effects on overall performance (for example, placing Exchange and SQL data on the same physical spindles).

Consider configuration of TEMPDB database

• Be sure to move TEMPDB to adequate storage and pre-size after you install SQL Server.

• Performance may benefit if TEMPDB is placed on RAID 1+0 (dependent on TEMPDB usage).

• For the TEMPDB database, create 1 data file per CPU.

Lining up the number of data files with CPUs has scalability advantages for allocation intensive workloads

• We recommend that you have 0.25 to 1 data files (per filegroup) for each CPU on the host server.

• This is especially true for TEMPDB where the recommendation is 1 data file per CPU.

• Dual core processors count as two CPUs; logical processors (hyperthreading) do not.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 46: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-46

Do not overlook some of SQL Server basics

• Data files should be of equal size - SQL Server uses a proportional fill algorithm that favors allocations in files with more free space.

• Pre-size data and log files. • Do not rely on AUTOGROW, instead manage the growth of these

files manually. You may leave AUTOGROW ON for safety reasons, but you should proactively manage the growth of the data files.

Do not overlook storage configuration bases

• Use up-to-date drivers recommended by the storage vendor. • Use storage vendor specific drivers from the manufacture's Web site. • Ensure that the storage array firmware is up to the latest

recommended level. • Use multipath software to achieve balancing across HBAs and LUNs

and ensure this is functioning correctly.

Focus on Application Speed

Performance issues are not always caused by missing indexes. Sometimes the more indexes you define, the more indexes need to be maintained, resulting in faster reads and slower updates.

Many performance issues are caused by long-running transactions. If the C/AL code is not optimized, adding indexes will not solve the problem, and the transaction will still take longer than necessary. Therefore, it is very important to focus on the application speed before adding indexes. Keeping the application as fast as possible will solve or avoid many problems. Instead of only focusing on getting the right results, developers should always keep performance in their mind and focus on getting the right results the right way.

Minimize the Number of Keys

Do not maintain indexes that are only used for sorting purposes. SQL Server will sort the result set. The main focus should be on quickly retrieving the result set. If you, for example, have several indexes that start with the same combination of keys (index fields), you should maintain only one of them on SQL Server. Hopefully you can identify the one that is most used in the most situations. In Microsoft Dynamics NAV, you should then use the MaintainSQLIndex property of the other indexes to specify that they should no longer be maintained on SQL Server (without disabling the key).

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 47: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-47

Avoid having too many indexes on hot tables because each record update means an index update producing more disk I/Os. For example, if the Item Ledger Entry table is growing by 1000 records per day and has 20 indexes, it can easily produce more than 20000 disk I/Os per day. However, if you reduce the number of indexes to for example 5, it greatly reduces the number of disk I/Os. Experience has shown that it is always possible to reduce the number of indexes to between 5 and 7 and even less on hot tables.

When minimizing the number of keys, you can use two methods:

• Keep the existing indexes and gradually disable them one by one, until you experience performance issues. This way, you know which index caused the performance issue and you can enable it again.

• Disable all indexes and activate them one by one. This way, you know which index caused the performance increase and you can disable the other indexes.

Indexes per Table

The following script lists all the indexes in a Microsoft Dynamics NAV database by table:

SELECT OBJECT_NAME(id) AS [Object Name], name AS [Index Name] FROM sysindexes WHERE (name NOT LIKE '_WA_%') AND (id > 255) ORDER BY [Object Name]

Number of Indexes per Table

The following query will show the number of indexes per table in a Microsoft Dynamics NAV database sorted by the number of indexes:

SELECT OBJECT_NAME(id) AS [Object Name], COUNT (id) AS [No. of Indexes] FROM sysindexes WHERE (name NOT LIKE '_WA_%') AND (id > 255) GROUP BY id ORDER BY [No. of Indexes] DESC, [Object Name]

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 48: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-48

Minimize the Number of SIFT Buckets

In versions before Microsoft Dynamics NAV 5.0 SP1, minimize the number of buckets maintained for each SIFT index. There is, for example, no reason to maintain the daily bucket for the Cust. Ledger Entry table if you only post several invoices and payments a month for the same customer. Furthermore, if you have several SIFT indexes defined on a table that you design, investigate whether some of the buckets are already maintained by another index. For example if you have two indexes Customer No.,Currency Code and Customer No.,Open which both maintain Amount (LCY) sums, you could disable the bucket that maintains the totals per Customer No. in one of the indexes.

Consider the following example.

If a SIFT table contains a lot of buckets, every single update of the 'source' table produces a large number of bucket updates. Every time you insert a record into the source table, Microsoft Dynamics NAV must update all buckets, one 'source' record and its indexes (all/some keys). A single insert could produce more than 100 I/Os on the disk subsystem.

Obviously, the smaller the records in the table, the smaller the problems associated with the indexes and the SIFT indexes become.

To see the number of SIFT buckets per table, use the following script.

SET NOCOUNT ON -- DROP TABLE ##SIFTtables CREATE TABLE ##SIFTtables ( [table_name] VARCHAR(255) DEFAULT '', [bucks] INT DEFAULT 0 )

DECLARE @Statement CHAR (255) DECLARE @tname sysname DECLARE Get_Curs CURSOR FOR SELECT name FROM sysobjects WHERE OBJECTPROPERTY(id, N'IsUserTable') = 1 AND name LIKE '%'+'$'+'[0-9]'+'%' ORDER BY name

OPEN Get_Curs

FETCH NEXT FROM Get_Curs INTO @tname WHILE @@FETCH_STATUS = 0 BEGIN INSERT INTO ##SIFTtables (bucks) EXEC('SELECT COUNT(DISTINCT bucket) AS bucks FROM ['+@tname+']') UPDATE ##SIFTtables SET table_name = @tname WHERE table_name = '' FETCH NEXT FROM Get_Curs INTO @tname END

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 49: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-49

CLOSE Get_Curs

DEALLOCATE Get_Curs

SELECT table_name AS [Table Name], bucks AS [No. Of Buckets] FROM ##SIFTtables

However, if your database has no data and/or some SIFT source tables have not been populated, the query will not show any or just a few buckets.

Use Key Groups

When creating new keys, make sure to add keys to a key group. This allows other developers to see what a key is used for. If necessary, you can create additional key groups. By using key groups, you can also enable or disable the keys (manually or with C/AL Code). Disabling a key group often results in better performance, as less indexes need to be maintained.

Key Selectivity

Redesign indexes so their selectivity becomes higher. Remember, do not place Boolean and option fields at the beginning of an index and always put date fields toward the end of the index.

Indexes like Document Type,Customer No., have very low selectivity on the first key. You can create a new index Customer No.,Document Type, and maintain it on SQL Server while turning off the maintenance of the original index on SQL Server.

With the changes in Microsoft Dynamics NAV 5.0 SP1 to avoid parameter sniffing it is no longer important to have the most selective fields to the left in the index because the entire index selectivity is now the important thing. It used to be important because SQL server would only consider the histograms of the first field in the index.

Ask for Assistance

If you encounter performance issues and do not know or cannot manage to locate the cause of the problems, ask for assistance before changing the hardware configuration or the database design.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 50: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-50

Summary In this chapter you learn the key areas in application development that are important for performance. The chapter explains how to create optimized keys and how to read data in an optimal way using FINDFIRST, FINDLAST, and FINDSET. In addition, the chapter explains how you can avoid deadlocks.

Problem solving is considered one of the most complex of all intellectual functions. This section provides tips, techniques, and methods to more easily perform troubleshooting and tuning exercises. With all these tasks, process is very important. It is through a systematic approach that you can determine the cause and solution(s) to any type of problem.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 51: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-51

Test Your Knowledge Test your knowledge with the following questions.

1. Put the following tuning methodology steps in the correct order:

Step:

_____: Return to step 2

_____: Analyze monitoring results

_____: Test the solution

_____: Propose a solution

_____: Create a hypothesis

_____: Monitor the system

_____: Define the issue.

_____: Implement changes

2. Why is the Find As You Type feature bad for performance on SQL Server?

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 52: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-52

3. How is SIFT implemented in Microsoft Dynamics NAV?

4. What do you know about RowLock Hinting?

5. What determines the physical storage order of records in a table?

( ) The Primary Key ( ) The Clustered Index ( ) The Timestamp column ( ) The Record No. column

6. Which statement is recommended when you want to browse a record set and modify a field that is not part of the current sorting?

( ) FINDSET(TRUE, TRUE) ( ) FINDSET(TRUE,FALSE) ( ) FINDSET(FALSE, TRUE) ( ) FINDSET(FALSE, FALSE)

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 53: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-53

7. What is true about the FINDSET statement from a performance point of view? (Select all that apply)

( ) FINDSET should be used to check whether one or more records exist that meet specific criteria.

( ) FINDSET returns a read-only record set. ( ) FINDSET only allows you to browse a record set from the top down. ( ) LOCKTABLE and FINDSET should be used to read large record sets,

as opposed to using FINDSET(TRUE).

Fill in the blanks to test your knowledge of this section.

8. The _______ function allows you to determine whether a C/SIDE table or a filtered set of records is empty.

9. To enable a key group in C/AL code, you must use the ______________ function.

10. In Microsoft Dynamics NAV 5.0 SP1, SIFT tables have been replaced by _____________.

11. _________ should never be used in combination with REPEAT/UNTIL NEXT.

12. The ________ property can be used to optimize a Microsoft Dynamics NAV key on SQL Server.

13. SQL Server prefers keys with a high ___________.

14. A table without a clustered index is called a ____.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 54: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-54

Lab 5.1 - Optimize C/AL Code for Performance In this lab you optimize a piece of C/AL code for performance. Although this lab can be done individually by the students, it is meant to start a class discussion.

The optimization of this piece of code depends on several factors, such as the number of sales headers and the number of sales lines per sales header. Multiple solutions are possible.

Scenario

Using SQL Server Profiler and Windows Performance Monitor, Tim, the IT-manager, has found that a particular function in Microsoft Dynamics NAV causes serious performance issues. Tim asks Mort, the IT Systems Developer, to review the particular function.

Challenge Yourself!

Optimize the following code for best performance:

SalesHdr.SETRANGE("Shipment Date", 0D); SalesHdr.SETFILTER( "Document Type", '%1', SalesHdr."Document Type"::Order); SalesHdr.LOCKTABLE; SalesHdr.FIND('-'); NoOfRecords := SalesHdr.COUNT; IF CONFIRM('Do you want to update all %1 Sales Orders?', FALSE, NoOfRecords) THEN BEGIN REPEAT SalesHdr.TESTFIELD(Status, SalesHdr.Status::Released); SalesLine.SETRANGE( "Document Type", SalesHdr."Document Type"); SalesLine.SETRANGE("Document No.", SalesHdr."No."); SalesLine.SETRANGE("Shipment Date", 0D); WHILE SalesLine.FIND('-') DO BEGIN SalesLine.LOCKTABLE(FALSE, TRUE); IF SalesLine."Shipment Date" = 0D THEN BEGIN SalesLine."Shipment Date" := WORKDATE; SalesLine.MODIFY; END;

IF SalesLine."Qty. Shipped (Base)" <> 0 THEN FoundError := TRUE; COMMIT; END; UNTIL SalesHdr.NEXT = 0; END; COMMIT; IF FoundError = TRUE THEN ERROR('At least one Sales Line was found with %1 <>0.', SalesLine.FIELDCAPTION("Qty. Shipped (Base)"));

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 55: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-55

Need a Little Help?

Discuss the following topics:

1. The use of keys and filters. In case one or more keys must be added, discuss the structure of the keys.

2. Data retrieval (FIND, GET) and repetitive statements. 3. Locking and unlocking (implicit and explicit locking). 4. Data manipulation statements (INSERT, DELETE, MODIFY). Can

multiple DELETE or MODIFY statements be replaced by DELETEALL or MODIFYALL statements?

5. Other statements that can have adverse effect on performance (COUNT).

6. The order the statements are executed.

The following code can be used as an alternative for the previous code:

SalesHdr.SETCURRENTKEY( "No.", "Document Type", Status, "Shipment Date"); SalesHdr.SETRANGE( "Document Type", SalesHdr."Document Type"::Order); SalesHdr.SETRANGE(Status, SalesHdr.Status::Released); SalesHdr.SETRANGE("Shipment Date", 0D); IF SalesHdr.FINDSET THEN IF CONFIRM('Do you want to update all Sales Orders?', FALSE) THEN BEGIN SalesLine.SETCURRENTKEY( "Document No.", "Document Type", "Shipment Date"); REPEAT SalesLine.SETRANGE("Document No.", SalesHdr."No."); SalesLine.SETRANGE("Document Type", SalesHdr."Document Type"); SalesLine.SETRANGE("Shipment Date", 0D); SalesLine.LOCKTABLE; // For small record sets; IF SalesLine.FINDSET THEN // otherwise use FIND('-') REPEAT SalesLine2 := SalesLine; SalesLine2."Shipment Date" := WORKDATE; SalesLine2.MODIFY; IF SalesLine."Qty. Shipped (Base)" <> 0 THEN FoundError := TRUE; UNTIL SalesLine.NEXT = 0; UNTIL SalesHdr.NEXT = 0; END; IF FoundError = TRUE THEN MESSAGE('At least one Sales Line was found with %1 <>0.', SalesLine.FIELDCAPTION("Qty. Shipped (Base)"));

Discuss the differences between both code fragments.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 56: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-56

Lab 5.2a - Find Index Usage In this lab you use dynamic management views to retrieve information about the usage of all indexes. Index usage statistics show how frequent indexes are used and updates can be used to disable specific indexes on SQL Server.

Scenario

Mort finds out that the Microsoft Dynamics NAV database is over-indexed. To know which indexes he can disable on SQL Server, he runs a query showing index usage information.

Challenge Yourself!

Run a query to find index usage information for the Demo Database NAV (6-0) database. Use the dynamic management views as a base. Sort the information so that indexes causing the biggest overhead are listed first.

Need a Little Help?

Perform the following list of steps:

1. Open SQL Server Management Studio. 2. Run the query. 3. Analyze the results.

Step by Step

Open SQL Server Management Studio

1. Open SQL Server Management Studio. 2. Connect to the NAV-SRV-01 Database Engine. 3. In the database dropdown list, select the Demo Database NAV (6-0)

database.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 57: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-57

Run the query

1. Click the New Query button to open a new query window. 2. In the New Query window, enter the following query:

SELECT db_name(database_id) as [DatabaseName], object_name(object_id) as [TableName], * FROM sys.dm_db_index_usage_stats WHERE db_name(database_id) = 'Demo Database NAV (6-0)' and object_name(object_id) LIKE 'CRONUS%' ORDER BY user_updates DESC

3. Click the Execute button to run the query.

Analyze the Query Results

Check the columns user_seeks, user_scans, and user_lookups to understand how often an index is used. Then compare with the column user_updates, to see how often the index is being used.

There are several other columns available, and this query could easily be modified to, for example, ORDER BY user_updates, to see the indexes causing the largest overheads, and then check the actual usage of these indexes.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 58: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-58

Lab 5.2b - Find Unused Indexes In this lab you write a query to retrieve information about unused indexes. The query shows general information about the Microsoft Dynamics NAV keys, such as the number of updates, the number of reads, or when an index was last used for reading. The idea is to show a list of indexes being maintained, but never or rarely being used. In the next lab, you process the information from this query.

Scenario

In the previous lab, Mort has retrieved index usage statistics for the Microsoft Dynamics NAV database. Now he knows how many times the table indexes are used and he can start disabling the keys. However, before disabling used keys, he wants to start by cleaning up keys that are frequently updated and never read.

Challenge Yourself!

Run a dynamic management view-based query that shows the unused indexes for the Demo Database NAV (6-0) database. Sort the information in descending order by number of updates.

Need a Little Help?

Perform the following list of steps:

1. Open SQL Server Management Studio. 2. Run the query. 3. Analyze the results.

Step by Step

Open SQL Server Management Studio

1. Open SQL Server Management Studio. 2. Connect to the NAV-SRV-01 Database Engine 3. In the database dropdown list, select the Demo Database NAV (6-0)

database.

Run the query

1. Click the New Query button to open a new query window. 2. In the New Query window, enter the following query:

IF OBJECT_ID ('z_IUQ_Temp_Index_Keys', 'U') IS NOT NULL DROP TABLE z_IUQ_Temp_Index_Keys; IF OBJECT_ID ('zIUQ_Temp_Index_Usage', 'U') IS NOT NULL DROP TABLE zIUQ_Temp_Index_Usage; -- Generate list of indexes with key list

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 59: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-59

CREATE TABLE z_IUQ_Temp_Index_Keys( [F_Obj_ID] [int] NOT NULL, [F_Obj_Name] [nvarchar] (128) NULL, [F_Ind_ID] [int] NOT NULL, [Index_Column_ID] [int] NOT NULL, [Index_Key] [nvarchar] (128) NULL, [Index_Key_List] [nvarchar] (MAX) NULL, CONSTRAINT [z_IUQ_TempPK] PRIMARY KEY( [F_Obj_ID], [F_Ind_ID], [Index_Column_ID]));

INSERT INTO z_IUQ_Temp_Index_Keys SELECT object_id, object_name(object_id), index_id, Index_Column_ID, index_col(object_name(object_id), index_id,Index_Column_ID), '' FROM sys.index_columns; GO -- populate key string DECLARE IndexCursor CURSOR FOR SELECT F_Obj_ID, F_Ind_ID FROM z_IUQ_Temp_Index_Keys FOR UPDATE OF Index_Key_List; DECLARE @ObjID int; DECLARE @IndID int; DECLARE @KeyString VARCHAR(MAX); SET @KeyString = NULL; OPEN IndexCursor; SET NOCOUNT ON; FETCH NEXT FROM IndexCursor INTO @ObjID, @IndID; WHILE @@fetch_status = 0 BEGIN SET @KeyString = ''; SELECT @KeyString = COALESCE(@KeyString, '') + Index_Key + ', ' FROM z_IUQ_Temp_Index_Keys WHERE F_Obj_ID = @ObjID and F_Ind_ID = @IndID ORDER BY F_Ind_ID, Index_Column_ID; SET @KeyString = LEFT(@KeyString,LEN(@KeyString) - 2); UPDATE z_IUQ_Temp_Index_Keys SET Index_Key_List = @KeyString WHERE CURRENT OF IndexCursor; FETCH NEXT FROM IndexCursor INTO @ObjID, @IndID; END; CLOSE IndexCursor; DEALLOCATE IndexCursor; -- Generate list of Index usage CREATE TABLE zIUQ_Temp_Index_Usage( [F_Table_Name] [nvarchar](128) NOT NULL, [F_Ind_ID] [int] NOT NULL, [F_Index_Name] [nvarchar](128) NULL, [No_Of_Updates] [int] NULL,

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 60: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-60

[User_Reads] [int] NULL, [Last_Used_For_Reads] [datetime] NULL,

[Index_Type] [nvarchar](56) NOT NULL, [last_user_seek] [datetime] NULL, [last_user_scan] [datetime] NULL, [last_user_lookup] [datetime] NULL, [Index_Keys] [nvarchar] (255) NULL);

INSERT INTO zIUQ_Temp_Index_Usage SELECT object_name(US.object_id) Table_Name, US.index_id Index_ID, SI.name Index_Name, US.user_updates No_Of_Updates, US.user_seeks + US.user_scans + US.user_lookups User_Reads, CASE WHEN (ISNULL(US.last_user_seek,'00:00:00.000') >= ISNULL(US.last_user_scan,'00:00:00.000')) AND (ISNULL(US.last_user_seek,'00:00:00.000') >= ISNULL(US.last_user_lookup,'00:00:00.000')) THEN US.last_user_seek WHEN (ISNULL(US.last_user_scan,'00:00:00.000') >= ISNULL(US.last_user_seek,'00:00:00.000')) AND (ISNULL(US.last_user_scan,'00:00:00.000') >= ISNULL(US.last_user_lookup,'00:00:00.000')) THEN US.last_user_scan ELSE US.last_user_lookup END AS Last_Used_For_Reads, SI.type_desc Index_Type, US.last_user_seek, US.last_user_scan, US.last_user_lookup, '' FROM sys.dm_db_index_usage_stats US, sys.indexes SI WHERE SI.object_id = US.object_id AND SI.index_id = US.index_id ORDER BY No_Of_Updates DESC; GO -- Select and join the two tables. SELECT TIU.F_Table_Name Table_Name, --TIU.F_Ind_ID Index_ID, --TIU.F_Index_Name Index_Name, TIK.Index_Key_List, TIU.No_Of_Updates, TIU.User_Reads, CASE WHEN TIU.User_Reads = 0 THEN TIU.No_Of_Updates ELSE TIU.No_Of_Updates / TIU.User_Reads END AS Cost_Benefit,

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 61: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-61

TIU.Last_Used_For_Reads, TIU.Index_Type FROM zIUQ_Temp_Index_Usage TIU, z_IUQ_Temp_Index_Keys TIK WHERE TIK.F_Obj_Name = TIU.F_Table_Name AND TIK.F_Ind_ID = TIU.F_Ind_ID AND

TIK.Index_Column_ID = 1 AND TIU.F_Table_Name NOT IN ('zIUQ_Temp_Index_Usage', 'z_IUQ_Temp_Index_Keys') AND TIU.F_Table_Name LIKE 'CRONUS%' ORDER BY No_Of_Updates DESC; --order by Cost_Benefit desc

3. Click the Execute button to run the query.

Depending on the size of your database, it may take a few minutes to run the query. First time you run it, it is recommended that you do it when the SQL Server is not too busy, until you know how long it takes.

Analyze the Query Results

The query shows you one line for each index in the SQL database. It includes the table name and the list of fields in the index. Note that a non-clustered index also contains the clustered index. For example on SQL Server, the key Document No. in the Cus. Ledger Entry table is Document No.,Entry No. Also note that the indexes shown by SQL Server are not always shown in the same order as you have defined them in Microsoft Dynamics NAV.

The No_Of_Updates column shows the cost of this index (because every update requires a lock as well as a write to the database). The User_Reads column displays how often this index has been used, either from the user interface or by C/AL code. The Cost_Benefit column (which is No_Of_Updates / User_Reads, or No_Of_Updates when User_Reads = 0) allows you to compare the index costs to the benefits. The Last_Used_For_Reads column shows you when an index was actually used for reading.

The query sorts the indexes by No_Of_Updates, with the most updated index (the biggest cost) first. On the last line of the query, you can change the sorting to order by Cost_Benefit DESC.

The query also shows you whether each index is clustered or non-clustered.

The query will create two new tables called z_IUQ_Temp_Index_Keys and zIUQ_Temp_Index_Usage to collect index usage statistics. If you already have tables with these names in your database, the query will overwrite those without warnings.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 62: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-62

NOTE: If you need to run the query again, for example, because you lost the results or because you want to run it with a different sorting, you do not have to run the whole query. In that case, you can simply run the last part of the query - from Select and join the two tables section, and it will run much faster. You need to run the whole query again if you want an updated view of index usage (for example after you have changed some indexes).

The data shown by the query is reset every time SQL Server restarts. If you have recently restarted SQL Server, the query may not show you the most precise picture of how the indexes are being used over time. Also consider that some indexes may only ever be used for example at the end of the month or fiscal year. Although the query shows that an index was not used (since SQL Server was last restarted), this index may still be required for specific jobs.

Typically, indexes with high cost (number of updates) and low benefits are subject to be changed.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 63: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-63

Lab 5.2c - Disable Unused Keys In this lab you disable unused keys in Microsoft Dynamics NAV.

Scenario

When he runs the unused indexes query from the previous lab, Mort notices that the Entry No. index in the Bank Account Ledger Entry table has a high cost and no benefit (user_reads = 0)

FIGURE 5.5 UNUSED INDEXES

He decides to disable this key on SQL Server and change the physical storage order of the ledger entries to Bank Account No., Posting Date.

Challenge Yourself!

Disable maintenance of an Entry No. key on Microsoft SQL Server and change the clustered index for the Bank Account Ledger Entry table to Bank Account No., Posting Date.

Need a Little Help?

Perform the following list of steps:

1. Change the clustered index of the Bank Account Ledger Entry table. 2. Disable maintenance of the Entry No. key on SQL Server. 3. Check the table keys using sp_helpindex.

Step by Step

Change the clustered index of the Bank Account Ledger Entry table

1. Start Microsoft Dynamics NAV 2009 Classic with Microsoft SQL Server.

2. On the Tools menu, select Object Designer. 3. Click Table.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 64: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-64

4. In the list of tables, select table Bank Account Ledger Entry. 5. Click Design. 6. On the View menu, select Keys to open the Keys window. 7. Select the Entry No. key. 8. On the View menu, select Properties to open the Key - Properties

window. 9. Set the Clustered property to No.

FIGURE 5.6 THE KEY PROPERTIES WINDOW

10. In the Keys window, select the Bank Account No., Posting Date key.

11. In the Key - Properties window, set the Clustered property to Yes.

You have now changed the clustered index of the table on SQL Server.

Disable maintenance of the Entry No. key on SQL Server

1. In the Keys window, select the Entry No. key. 2. In the Key - Properties window, set the MaintainSQLIndex

property to No. 3. Close the Keys window. 4. Save and compile the Bank Account Ledger Entry table.

Check the table keys using sp_helpindex

1. Open SQL Server Management Studio. 2. Click the New Query button to open a new query window.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 65: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-65

3. In the Query window, enter the following statement:

sp_helpindex 'CRONUS International Ltd_$Bank Account Ledger Entry'

4. Click the Execute button to run the query. The result will look as follows:

FIGURE 5.7 SP_HELPINDEX RESULTS

You notice that the Bank Account No., Posting Date, Entry No. index now is the clustered index. Note that the Entry No. index is still maintained on SQL Server. This is because the MaintainSQLIndex property must always be Yes for the primary key. Even if you set it to No (as in this lab), Microsoft Dynamics NAV will change the property back to Yes. Remember that the MaintainSQLIndex property can only be used on secondary keys.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 66: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-66

Lab 5.3 - Create a Deadlock Trace In this lab you set up a lock trace in SQL Server Profiler to detect locks and deadlocks in the Microsoft Dynamics NAV database.

Scenario

Recently, Mort receives a lot of requests from users complaining about deadlocks. When they work in Microsoft Dynamics NAV, users get the following error message:

FIGURE 5.8 DEADLOCK ERROR MESSAGE

Mort asks Tim to set up a lock trace, to see when locks and deadlocks occur. The same information can be obtained by using the dm_tran_locks dynamic management view. However, the dm_tran_locks view shows only the current server state (no historical information).

Challenge Yourself!

Set up a deadlock trace in SQL Server Profiler. Create two deadlock situations in Microsoft Dynamics NAV involving two different users and analyze the trace.

Need a Little Help?

Perform the following steps to complete this lab:

1. Open SQL Server Profiler. 2. Define the Trace. 3. Filter the Trace. 4. Open the first Microsoft Dynamics NAV Client session (for the

current Windows user). 5. Open the second Microsoft Dynamics NAV Client session (for

Susan). 6. Run the codeunits in each Microsoft Dynamics NAV session (first

Susan, then Administrator). 7. Run the codeunits again in each Microsoft Dynamics NAV session

(first Administrator, then Susan). 8. Check the Trace. 9. Stop the Trace. 10. Analyze the Deadlock Trace.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 67: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-67

Step by Step

Open SQL Server Profiler

1. Open SQL Server Management Studio. 2. In the Tools menu, select SQL Server Profiler. 3. Connect to the NAV-SRV-01 Database Engine.

Define the Trace

1. In the Trace Properties window, in the trace name, enter Locks and Deadlocks.

2. In the Use the template list, select the TSQL_Locks template. 3. Select the Save to File option. 4. In the Save As dialog box, enter the path and file name for the SQL

Server Profiler trace. In this example, enter C:\PerfLogs\Lab 5.3.trc. 5. Clear the Enable file rollover option. 6. Select the Server processes trace data option.

Filter the Trace

1. On the Events Selection tab, click the Column Filters button. 2. In the Edit Filter window, in the left pane, select DatabaseName. 3. In the right pane, double-click the Like operator. 4. In the text box, enter Demo Database NAV (6-0). 5. Click OK to close the Edit Filter window. 6. Click Run to start the SQL Server Profiler trace.

Next, to simulate the deadlock situation, you need to start two Microsoft Dynamics NAV client sessions and then run a codeunit in each session.

Open the first Microsoft Dynamics NAV Client session (for the current Windows user)

1. In the Windows Taskbar, click Start > All Programs > Microsoft Dynamics NAV 2009 Classic with Microsoft SQL Server to start the Microsoft Dynamics NAV client.

2. On the Tools menu, select Object Designer. 3. Click Codeunit. 4. Create a new codeunit 123456703, Locking - Administrator with the

following code:

Cust.LOCKTABLE; IF Cust.FINDFIRST THEN BEGIN Cust.Name := Cust.Name; Cust.Address := Cust.Address;

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 68: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-68

Cust.MODIFY; END;

Vend.LOCKTABLE; SLEEP(30000); IF Vend.FINDFIRST THEN;

Open the second Microsoft Dynamics NAV Client session (for Susan)

1. In the Windows Taskbar, click Start > All Programs. 2. Right-click Microsoft Dynamics NAV 2009 Classic with

Microsoft SQL Server and select Run as. 3. In the Run as window, select The following user. 4. In the User name field, enter CONTOSO\Susan. 5. In the Password field, enter pass@word1. 6. Click OK to run the Microsoft Dynamics NAV 2009 Classic client. 7. On the Tools menu, select Object Designer. 8. Click Codeunit. 9. Create a new codeunit 123456702, Locking - Susan, with the

following code:

Vend.LOCKTABLE; IF Vend.FINDFIRST THEN BEGIN Vend.Name := Cust.Name; Vend.Address := Vend.Address; Vend.MODIFY; END;

Cust.LOCKTABLE; SLEEP(30000); IF Cust.FINDFIRST THEN;

Run the codeunits in each Microsoft Dynamics NAV session (first Susan, then Administrator)

1. In Susan's session, click Run to execute the codeunit 123456702. 2. Immediately, switch to the current Windows user's session. 3. Click Run to execute codeunit 123456703. 4. Wait for the deadlock error message to appear in the current session.

The current Windows user is now chosen as a deadlock victim.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 69: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-69

Run the codeunits again in each Microsoft Dynamics NAV session (first Administrator, then Susan)

1. In the current Windows user's session, click Run to execute codeunit 123456703.

2. Switch to Susan's session. 3. Click Run to execute codeunit 123456702. 4. Wait for the deadlock error message to appear in Susan's session.

Susan is now chosen as a deadlock victim.

Check the Trace

Switch to the SQL Server Profiler window. While the codeunits are running, events are added to the SQL Server Profiler trace. The LoginName column shows that the database activity is caused by CONTOSO\SUSAN and CONTOSO\Administrator.

Stop the Trace

In the SQL Server Profiler, right-click the trace window and select Stop Trace.

Analyze the Deadlock Trace

1. In the SQL Server Profiler trace, select the first event with Deadlock graph in the EventClass column.

FIGURE 5.9 DEADLOCK GRAPH EVENT

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 70: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-70

2. In the SQL Server Profiler trace, select the second event with Deadlock graph in the EventClass column.

3. Compare the data for both Deadlock graph events.

If another user runs either of the two codeunits while a deadlock situation is ongoing, a lock time-out event will appear in the SQL Server Profiler and the user will get a corresponding error message.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 71: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-71

Quick Interaction: Lessons Learned Take a moment and write down three Key Points you have learned from this chapter

1.

2.

3.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 72: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-72

Solutions Test Your Knowledge

1. Put the following tuning methodology steps in the correct order:

Step:

8 : Return to step 2

3 : Analyze monitoring results

7 : Test the solution

5 : Propose a solution

4 : Create a hypothesis

2 : Monitor the system

1 : Define the issue.

6 : Implement changes

2. Why is the Find As You Type feature bad for performance on SQL Server?

MODEL ANSWER:

When Find As You Type is enabled, Microsoft Dynamics NAV will send a LIKE query to the SQL Server for every keystroke. This causes unnecessary queries to be sent to the SQL Server.

3. How is SIFT implemented in Microsoft Dynamics NAV?

MODEL ANSWER:

SIFT was originally implemented on SQL Server by using extra summary tables called SIFT Tables, that were maintained through table triggers directly in the table definitions on SQL Server. When an update was performed on a table that contains SIFT indexes, a series of additional updates were necessary to update the associated SIFT tables. In Microsoft Dynamics NAV 5.0 SP 1, Microsoft replaced SIFT tables with V-SIFT, which are indexed views.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 73: Na2009 enus sql_05

Chapter 5: Improving Application Performance

5-73

4. What do you know about RowLock Hinting?

MODEL ANSWER:

Rowlock hinting is a tuning technique that can be used to influence SQL Server's default locking behavior. When you activate rowlock hinting, rowlock hints will be sent to SQL Server, to make sure SQL Server always locks data by row. Rowlock hinting can be activated by checking the Always Rowlock database option. Rowlock hinting is to be avoided because of its high memory requirements.

5. What determines the physical storage order of records in a table?

( ) The Primary Key (•) The Clustered Index ( ) The Timestamp column ( ) The Record No. column

6. Which statement is recommended when you want to browse a record set and modify a field that is not part of the current sorting?

( ) FINDSET(TRUE, TRUE) (•) FINDSET(TRUE,FALSE) ( ) FINDSET(FALSE, TRUE) ( ) FINDSET(FALSE, FALSE)

7. What is true about the FINDSET statement from a performance point of view? (Select all that apply)

( ) FINDSET should be used to check whether one or more records exist that meet specific criteria.

(√) FINDSET returns a read-only record set. (√) FINDSET only allows you to browse a record set from the top down. ( ) LOCKTABLE and FINDSET should be used to read large record sets,

as opposed to using FINDSET(TRUE).

Fill in the blanks to test your knowledge of this section.

8. The ISEMPTY function allows you to determine whether a C/SIDE table or a filtered set of records is empty.

9. To enable a key group in C/AL code, you must use the KEYGROUPENABLE function.

10. In Microsoft Dynamics NAV 5.0 SP1, SIFT tables have been replaced by indexed views

11. FINDFIRST should never be used in combination with REPEAT/UNTIL NEXT.

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement

Page 74: Na2009 enus sql_05

SQL Server Installation and Optimization for Microsoft Dynamics® NAV 2009

5-74

12. The SQLIndex property can be used to optimize a Microsoft Dynamics NAV key on SQL Server.

13. SQL Server prefers keys with a high selectivity

14. A table without a clustered index is called a heap

Microsoft Official Training Materials for Microsoft Dynamics ® Your use of this content is subject to your current services agreement