<?xml version="1.0" encoding="UTF-8" ?>
<?xml-stylesheet type="text/xsl" href="http://blogs.msdn.com/utility/FeedStylesheets/rss.xsl" media="screen"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:wfw="http://wellformedweb.org/CommentAPI/"><channel><title>SQL Server Compact - Compact &amp; Capable : Performance</title><link>http://blogs.msdn.com/sqlservercompact/archive/tags/Performance/default.aspx</link><description>Tags: Performance</description><dc:language>en-US</dc:language><generator>CommunityServer 2.1 SP1 (Build: 61025.2)</generator><item><title>After moving the database from one platform to other, the first SqlCeConnection.Open() takes more time</title><link>http://blogs.msdn.com/sqlservercompact/archive/2009/04/01/after-moving-the-database-from-one-platform-to-other-the-first-sqlceconnection-open-takes-more-time.aspx</link><pubDate>Wed, 01 Apr 2009 15:16:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:9529517</guid><dc:creator>SQLCEBLOG</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/sqlservercompact/comments/9529517.aspx</comments><wfw:commentRss>http://blogs.msdn.com/sqlservercompact/commentrss.aspx?PostID=9529517</wfw:commentRss><description>&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;B&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 12pt"&gt;&lt;/SPAN&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;B&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 12pt"&gt;&lt;/SPAN&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;B&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 12pt"&gt;If you move a SQLCE database from one platform to other, it's&amp;nbsp;first SqlCeConnection.Open()&amp;nbsp;takes more time and also increases the database file size.&lt;/SPAN&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;U&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 12pt"&gt;&lt;/SPAN&gt;&lt;/U&gt;&amp;nbsp;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;U&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 12pt"&gt;The Reason&lt;/SPAN&gt;&lt;/U&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 12pt"&gt;: F&lt;/SPAN&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;or an index on string type columns, SQLCE uses &lt;A href="http://msdn.microsoft.com/en-us/library/dd318700(VS.85).aspx"&gt;&lt;FONT color=#800080&gt;LCMapString&lt;/FONT&gt;&lt;/A&gt; API to get the normalized sort key. LCMapString API behavior will be different for different NLS sort versions. &lt;A href="http://msdn.microsoft.com/en-us/library/dd318105(VS.85).aspx"&gt;&lt;FONT color=#800080&gt;NLS sort version&lt;/FONT&gt;&lt;/A&gt; is different for Desktop Windows OS and WM/WCE. During the first database connection open, SQLCE rebuilds all indexes if there is a NLS sort version mismatch with the last accessed OS platform. Index recreation requires space on database file and hence the database file size will be increased. You can use &lt;A href="http://msdn.microsoft.com/en-us/library/system.data.sqlserverce.sqlceengine.compact(VS.85).aspx"&gt;&lt;FONT color=#800080&gt;Compact()&lt;/FONT&gt;&lt;/A&gt;/&lt;A href="http://msdn.microsoft.com/en-us/library/system.data.sqlserverce.sqlceengine.shrink(VS.85).aspx"&gt;&lt;FONT color=#800080&gt;Shrink()&lt;/FONT&gt;&lt;/A&gt; API to get the database file with approximately old file size. &amp;nbsp;Because of this index recreation, first database connection open takes more time as it has to rebuild all indexes. &amp;nbsp;&lt;?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" /&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;B&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;Note&lt;/SPAN&gt;&lt;/B&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;: If your database doesn’t&amp;nbsp;have any indexes you will not see above mentioned symptoms on first connection open.&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;B&gt;&lt;U&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;&lt;o:p&gt;&lt;SPAN style="TEXT-DECORATION: none"&gt;&lt;/SPAN&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/U&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;B&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;&lt;/SPAN&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;B&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;&lt;/SPAN&gt;&lt;/B&gt;&amp;nbsp;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;B&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;Don’t inter-op SQLCE 3.0 databases between devices and desktop:&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/B&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;SQLCE started supporting desktops only from SQLCE 3.1 (3.0.5300.0) release. SQLCE 3.0 doesn’t have index rebuilding logic and the index re-creation was added during SQLCE 3.1 (3.0.5300.0) release as it is required to support platform inter-op for SQLCE databases. &amp;nbsp;On desktop you can open SQLCE 3.0 databases, which were created on devices. Again If you open these databases on SQLCE 3.0 devices, your device app might receive an error/exception.&lt;/SPAN&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoNormal&gt;&lt;SPAN style="COLOR: #1f497d; mso-ascii-font-family: Calibri; mso-hansi-font-family: Calibri; mso-bidi-font-weight: bold; mso-themecolor: text2; mso-bidi-font-style: italic"&gt;&lt;U&gt;For example:&lt;/U&gt; Opening&amp;nbsp;a SQLCE 3.0&amp;nbsp;database with SSMS will use SQLCE 3.1 bits on desktop and hence it would drop and recreate index data. When this file is copied back to device, index data may not be valid and receive an error/exception.&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoNormal&gt;&lt;o:p&gt;&lt;FONT color=#000000&gt;&amp;nbsp;&lt;/FONT&gt;&lt;/o:p&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoNormal&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;Namaskaram!&lt;/SPAN&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;SPAN style="FONT-FAMILY: 'Calibri','sans-serif'; COLOR: #1f497d; FONT-SIZE: 11pt"&gt;Manikyam Bavandla&lt;U&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/U&gt;&lt;/SPAN&gt;&lt;/P&gt;
&lt;P style="MARGIN: 0in 0in 0pt" class=MsoPlainText&gt;&lt;o:p&gt;&lt;/o:p&gt;&lt;/SPAN&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=9529517" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/sqlservercompact/archive/tags/ADO.NET/default.aspx">ADO.NET</category><category domain="http://blogs.msdn.com/sqlservercompact/archive/tags/Engine/default.aspx">Engine</category><category domain="http://blogs.msdn.com/sqlservercompact/archive/tags/OLEDB/default.aspx">OLEDB</category><category domain="http://blogs.msdn.com/sqlservercompact/archive/tags/Troubleshooting/default.aspx">Troubleshooting</category><category domain="http://blogs.msdn.com/sqlservercompact/archive/tags/Performance/default.aspx">Performance</category></item><item><title>Sync Services optimizations</title><link>http://blogs.msdn.com/sqlservercompact/archive/2008/10/06/sync-services-optimizations.aspx</link><pubDate>Mon, 06 Oct 2008 15:40:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8977777</guid><dc:creator>SQLCEBLOG</dc:creator><slash:comments>0</slash:comments><comments>http://blogs.msdn.com/sqlservercompact/comments/8977777.aspx</comments><wfw:commentRss>http://blogs.msdn.com/sqlservercompact/commentrss.aspx?PostID=8977777</wfw:commentRss><description>&lt;P&gt;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; There are some optimizations that can be done for an "Sync Services" sync scenario. These are,&amp;nbsp;(might not be an exhaustive list) &lt;/P&gt;
&lt;OL&gt;
&lt;LI&gt;Small sync chunks (using smaller SyncGroups which are only absolutely necessary)&lt;/LI&gt;
&lt;LI&gt;Index creation on tracking and filtering columns&lt;/LI&gt;
&lt;LI&gt;Transfer encryption of data payload on web&lt;/LI&gt;
&lt;LI&gt;Batching the changes&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/LI&gt;
&lt;LI&gt;Filtering the data at various stages to reduce network load&lt;/LI&gt;&lt;/OL&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Sync (synonym for "Sync Services sync", in this post) interfaces with the providers using DataSet objects. That is, sync uses DataSet to store and retrieve changes happening at client and server. DataSets are known to be not very memory efficient, and the amount of memory consumed by these, is a bit of a concern when one is using these in device clients in Sync Services.&amp;nbsp;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Sync happens in chunks, using the SyncGroups that one supplies. That is, all data in a SyncGroup is logically one sync, and corresponds to one DataSet. Since sync DataSets are one per SyncGroup, and these are needed only till the corresponding SyncGroup commits, we can optimize the memory taken by DataSets, using smaller SyncGroups. Divide sync tables into various SyncGroups, so that, each group has it’s own (smaller) DataSet, and it is disposed (by garbage collector) once the SyncGroup changes are committed. However, to ensure logical consistency, you should always keep the tables which are related (by a foreign key constraint, for example), in the same SyncGroup. &lt;BR&gt;&amp;nbsp;&lt;BR&gt;Example: &lt;BR&gt;Tables Order and OrderDetails are related. &lt;BR&gt;Tables Customers and ShippingDetails are related. But, these are unrelated to the first set. &lt;BR&gt;&amp;nbsp;&lt;BR&gt;SyncGroup customerSyncGroup = new SyncGroup("Customers");&lt;BR&gt;&amp;nbsp;&lt;BR&gt;SyncTable customerSyncTable = new SyncTable("Customer");&lt;BR&gt;customerSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;&lt;BR&gt;customerSyncTable.SyncDirection = SyncDirection.DownloadOnly;&lt;BR&gt;customerSyncTable.SyncGroup = customerSyncGroup;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;SyncTable orderShipSyncTable = new SyncTable("ShippingDetails");&lt;BR&gt;orderShipSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;&lt;BR&gt;orderShipSyncTable.SyncDirection = SyncDirection.DownloadOnly;&lt;BR&gt;orderShipSyncTable.SyncGroup = customerSyncGroup;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;SyncGroup orderSyncGroup = new SyncGroup("Orders");&lt;BR&gt;&amp;nbsp;&lt;BR&gt;SyncTable orderSyncTable = new SyncTable("Order");&lt;BR&gt;orderSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;&lt;BR&gt;orderSyncTable.SyncDirection = SyncDirection.DownloadOnly;&lt;BR&gt;orderSyncTable.SyncGroup = orderSyncGroup;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;SyncTable orderDetailSyncTable = new SyncTable("OrderDetails");&lt;BR&gt;orderDetailSyncTable.CreationOption = TableCreationOption.DropExistingOrCreateNewTable;&lt;BR&gt;orderDetailSyncTable.SyncDirection = SyncDirection.DownloadOnly;&lt;BR&gt;orderDetailSyncTable.SyncGroup = orderSyncGroup;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Rather than having a single SyncGroup with all 4 tables, one can create two sync groups. One for the first set, and the second for the other set.&amp;nbsp;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Also, to increase the performance of enumerating process at server, one can create indexes on the tracking and filtering columns. As an example, let us say one has two tracking timestamp columns __CreateTime and __UpdateTime. Since, our server side changes enumerating queries always have filtering based on these columns, one should create indexes on these, so that, enumerating server changes becomes faster, and, sync performance increases.&amp;nbsp;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; In case of device syncs, transferring the data on the network/internet is also going to cost more time/resources. Since, transfer of data from a web service/server machine to a device is time/bandwidth consuming, one should look at compressing data sent/received. This is mostly the case, when data being synched is large (Something like, initial synching SQL Compact with SQL Server). One can use System.IO.Compression or your own implementation of compression/decompression algorithm. (More details at &lt;A href="http://msdn.microsoft.com/en-us/library/system.io.compression.aspx" mce_href="http://msdn.microsoft.com/en-us/library/system.io.compression.aspx"&gt;http://msdn.microsoft.com/en-us/library/system.io.compression.aspx&lt;/A&gt;).&amp;nbsp;Another blog post that refers to this issue and proposes solutions is at, (&lt;A href="http://blogs.msdn.com/mahjayar/archive/2008/10/01/dbsyncprovider-improving-memory-performance-in-wcf-based-synchronization.aspx"&gt;http://blogs.msdn.com/mahjayar/archive/2008/10/01/dbsyncprovider-improving-memory-performance-in-wcf-based-synchronization.aspx&lt;/A&gt;)&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Sync Services&amp;nbsp;Server side supports batching. Please refer to &lt;A href="http://msdn.microsoft.com/en-us/library/bb902828.aspx" mce_href="http://msdn.microsoft.com/en-us/library/bb902828.aspx"&gt;http://msdn.microsoft.com/en-us/library/bb902828.aspx&lt;/A&gt; for more details.&amp;nbsp;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Sometimes, we may have server tables holding and synching data, that is alien to clients. For example, server table has a large GPS map of some place (in custom format) per row, that is associated with the data in other columns in the row. The map is stored as an image at client side. But, since, the data is of proprietary format, client can’t interpret the data, and it is not used anyway at client side. And also, since, it is large data, lot of network bandwidth is consumed and response time is compromised for transferring this across. It is better, if one can filter these columns out at server side, rather than sending to client.&amp;nbsp;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Another scenario is as follows: The client devices of a Sync application are used by Salesmen of a company, and the server side is the company office, where data pertaining to each salesman is stored in a SQL Server database. When the client devices sync, they are mostly concerned about the data relevant to them alone, and not to other sales persons. Here also, one needs to filter rows from the table.&amp;nbsp;&lt;BR&gt;&amp;nbsp;&lt;BR&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; To facilitate filtering of rows/columns at client/server sides, one can use a number of strategies. For filtering data sent from client (or at the client side), one can use the callback architecture of "Sync Services". For server side, since you have more control there, one can choose to strip down the DataSet, when one is sending it over the wire/use callbacks to strip off DataSet data/client can supply queries to server side adapters, which filter data. &lt;/P&gt;
&lt;P&gt;Explaining each of these options:&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Callback architecture of sync: Sync provides various callbacks during the process of Synching. One can hook in the callbacks for accessing/modifying the dataset generated at every stage of sync. For example, one can add/drop some columns to the dataset, after changee enumeration is done at client side. After changing the dataset, effectively, the changes being applied to client database are changed. The same provision is present at server side also. &lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; This callback architecture can be used to modify the DataSet applied/sent to server, on the fly. This is one type of filtering, possible at both client and server. &lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; Server side gives more options when sending data over the wire. First of all, server chnages are enumerated within the "Sync Services" application itself. That is because, server change enumeration is done by queries supplied by the Sync Services client. So, which columns to select from server, can be controlled from the client side, when specifying queries like, SelectIncrementalInserts etc... This is another way of programmatically filtering server data. &lt;/P&gt;
&lt;P&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; One more option to filter data in a N-tier architecture is that, filtering the wire transferred content. In an N-tier architecture, client and server are in two different, but connected machines. Typically, client machine has a proxy server provider, which delegates calls to enumeraion/application of changes etc... to a web service. Web service talks to a backend data store, and services the data and operation requests. In this setting, interesting possibilities like, use of compression over the wire, and filtering over the wire etc... come up. When server side data store gives data, it can do some filtering. Also, the client side proxy can be used to filter irrelevant data. This is another type of filtering.&lt;/P&gt;
&lt;P&gt;Example for callback architecture:&lt;BR&gt;For client side DataSet filtering (client side), use SelectingChanges&amp;nbsp;before the enumeration and ChangesSelected&amp;nbsp;after the enumeration&lt;BR&gt;For server’s data set filtering (client side), ApplyingChanges before the application of changes and ChangesApplied&amp;nbsp;after the application&lt;BR&gt;For filtering server side changes (at server side):SelectingChanges before the enumeration and ChangesSelected&amp;nbsp;after the enumeration&lt;BR&gt;For client’s data filtering (at server side):ApplyingChanges&amp;nbsp;before the application of changes and ChangesApplied&amp;nbsp;after the application&lt;BR&gt;&amp;nbsp;&lt;BR&gt;In every one of these methods, eventArgs.GroupMetadata and eventArgs.Context.DataSet are the ones need to be changed. &lt;/P&gt;
&lt;P&gt;&amp;nbsp;Thanks&lt;/P&gt;
&lt;P&gt;Udaya Bhanu, &lt;/P&gt;
&lt;P&gt;SDE II, SQL Compact&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8977777" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/sqlservercompact/archive/tags/ADO.NET/default.aspx">ADO.NET</category><category domain="http://blogs.msdn.com/sqlservercompact/archive/tags/SyncServices/default.aspx">SyncServices</category><category domain="http://blogs.msdn.com/sqlservercompact/archive/tags/Performance/default.aspx">Performance</category></item><item><title>Query performance</title><link>http://blogs.msdn.com/sqlservercompact/archive/2008/04/30/query-performance.aspx</link><pubDate>Wed, 30 Apr 2008 08:15:00 GMT</pubDate><guid isPermaLink="false">91d46819-8472-40ad-a661-2c78acb4018c:8441485</guid><dc:creator>SQLCEBLOG</dc:creator><slash:comments>2</slash:comments><comments>http://blogs.msdn.com/sqlservercompact/comments/8441485.aspx</comments><wfw:commentRss>http://blogs.msdn.com/sqlservercompact/commentrss.aspx?PostID=8441485</wfw:commentRss><description>&lt;P&gt;Understanding the reason for slowness of a SQL query and then tuning to boost it is a slightly complicated process, but it can be extremely rewarding in some cases. In this post, I am going to list down some ways in which you can tune the query performance for SQL Server Compact, along with pointers to relevant online resources. If you are a seasoned database developer, you probably know most of the tricks already.&lt;/P&gt;
&lt;P&gt;Before I jump into it, an overview of the basics first.&lt;/P&gt;
&lt;P&gt;&lt;B&gt;Basics:&lt;/B&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;B&gt;&amp;nbsp;Displaying actual and estimated Execution plans for SQL Server Compact: &lt;/B&gt;You should be familiar with Execution Plans (also called as query plans and showplans).&lt;/LI&gt;
&lt;UL&gt;
&lt;LI&gt;For non-parameterized queries: There are several easy ways to do this&lt;/LI&gt;
&lt;UL&gt;
&lt;LI&gt;When database is on a &lt;B&gt;docked device or desktop&lt;/B&gt;: &lt;A href="http://msdn2.microsoft.com/en-us/library/ms172428%28SQL.100%29.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/ms172428%28SQL.100%29.aspx"&gt;Generate a query plan using SQL Server Management Studio&lt;/A&gt; (SSMS) that gives a nice graphical view of the query plans. (You can connect to the database on device as long as it is docked. No need to copy the file to desktop.)&lt;/LI&gt;
&lt;LI&gt;When database is on a &lt;B&gt;device&lt;/B&gt;:&lt;B&gt; &lt;/B&gt;&lt;A href="http://msdn2.microsoft.com/en-us/library/ms172893%28SQL.100%29.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/ms172893%28SQL.100%29.aspx"&gt;Generate a query plan using Query Analyzer&lt;/A&gt;. This generates a .sqlplan file that can be viewed graphically in SQL Server Management Studio as before (preferable) or opened as a plain XML file.&lt;/LI&gt;&lt;/UL&gt;
&lt;LI&gt;For Parameterized (as well as non-parameterized) queries:&lt;/LI&gt;
&lt;UL&gt;
&lt;LI&gt;Use the TSQL Statements &lt;A href="http://msdn2.microsoft.com/en-us/library/bb896157%28SQL.100%29.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/bb896157%28SQL.100%29.aspx"&gt;SET SHOWPLAN_XML &lt;/A&gt;&amp;nbsp;(for estimated execution plan) and&amp;nbsp; &lt;A href="http://msdn2.microsoft.com/en-us/library/bb896155%28SQL.100%29.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/bb896155%28SQL.100%29.aspx"&gt;SET STATISTICS_XML&lt;/A&gt; (for actual execution plan), followed by &lt;A href="http://technet.microsoft.com/en-us/library/bb896154%28SQL.100%29.aspx" mce_href="http://technet.microsoft.com/en-us/library/bb896154%28SQL.100%29.aspx"&gt;SELECT @@SHOWPLAN&lt;/A&gt; to generate the query plans.&amp;nbsp; Store the results as an XML file. (&lt;B&gt;Tip&lt;/B&gt;: To view the query plans graphically, just rename the file with a .sqlplan extension and it is ready to be opened in SSMS like an ordinary non-parameterized query plan.)&lt;/LI&gt;&lt;/UL&gt;&lt;/UL&gt;
&lt;LI&gt;Indexes and statistics on indexes:&lt;/LI&gt;
&lt;UL&gt;
&lt;LI&gt;One of the important tasks of the query optimizer is to select the right index to execute the query. An optimizer can make better decisions if it has histogram data (i.e. statistics) about the distribution of values for an index.&lt;/LI&gt;
&lt;UL&gt;
&lt;LI&gt;CREATING/UPDATING/DELETING statistics on an index: SQL Server Compact 3.5 creates statistics on all indexes by default and updates them as required. (You can surely tweak these settings, but it is advisable not to. See &lt;A href="http://msdn2.microsoft.com/en-us/library/ms174436%28SQL.100%29.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/ms174436%28SQL.100%29.aspx"&gt;CREATE STATISTICS&lt;/A&gt; /&lt;A href="http://msdn2.microsoft.com/en-us/library/ms174025%28SQL.100%29.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/ms174025%28SQL.100%29.aspx"&gt;UPDATE STATISTICS&lt;/A&gt;/&lt;A href="http://msdn2.microsoft.com/en-us/library/ms173254%28SQL.100%29.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/ms173254%28SQL.100%29.aspx"&gt;DROP STATISTICS&lt;/A&gt;). &lt;BR&gt;&lt;/LI&gt;
&lt;LI&gt;To view the statistics distribution, the built in stored procedure &lt;A href="http://msdn2.microsoft.com/en-us/library/ms174108%28SQL.100%29.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/ms174108%28SQL.100%29.aspx"&gt;sp_show_statistics&lt;/A&gt; is pretty handy.&amp;nbsp;&lt;/LI&gt;
&lt;LI&gt;Statistics were not created by default on primary key indexes till version 3.1, so you might want to check this and add them yourself :- ).&lt;/LI&gt;&lt;/UL&gt;&lt;/UL&gt;
&lt;LI&gt;... A lot of other things too J, but this will do for the time being.&lt;/LI&gt;&lt;/UL&gt;&lt;B&gt;The Meat:&lt;/B&gt; 
&lt;P&gt;If you are trying to improve the performance of your application that uses SQL Server Compact, make sure you have checked the following resources on MSDN already (In this post I will not go into any of them):&lt;/P&gt;
&lt;UL&gt;
&lt;LI&gt;&lt;A href="http://msdn2.microsoft.com/en-us/library/ms172432%28SQL.100%29.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/ms172432%28SQL.100%29.aspx"&gt;Database Design and Performance&lt;/A&gt;: This page talks in some detail about the following:&lt;/LI&gt;
&lt;UL&gt;
&lt;LI&gt;Database denormalization&lt;/LI&gt;
&lt;LI&gt;Variable and fixed sized columns&lt;/LI&gt;
&lt;LI&gt;Effect of row and key lengths&lt;/LI&gt;&lt;/UL&gt;
&lt;LI&gt;&lt;A href="http://msdn2.microsoft.com/en-us/library/ms172984%28SQL.100%29.aspx" mce_href="http://msdn2.microsoft.com/en-us/library/ms172984%28SQL.100%29.aspx"&gt;Query Performance Tuning&lt;/A&gt;: This page explains some of the following:&lt;/LI&gt;
&lt;UL&gt;
&lt;LI&gt;Improving indexes by creating selective indexes, choosing the columns in a multi-column index, issues in indexing a small table etc.&lt;/LI&gt;
&lt;LI&gt;When to create indexes&lt;/LI&gt;
&lt;LI&gt;Using parameterized queries where possible.&lt;/LI&gt;
&lt;LI&gt;Rewrite queries where possible&lt;/LI&gt;
&lt;LI&gt;Using base table cursor (CommandType.TableDirect) mode for simple queries.&lt;/LI&gt;&lt;/UL&gt;&lt;/UL&gt;
&lt;P&gt;(The article on &lt;A href="http://www.microsoft.com/technet/prodtechnol/sql/2000/maintain/ssceqpop.mspx" mce_href="http://www.microsoft.com/technet/prodtechnol/sql/2000/maintain/ssceqpop.mspx"&gt;Query Process Overview and Performance Tuning approaches for SQL Server CE 2.0&lt;/A&gt; covers the same points as above, but I found it better than the MSDN links J. It is relevant for SQL Server Compact 3.5 too.) &lt;/P&gt;
&lt;P&gt;Now here are some additional ways you can improve performance:&lt;/P&gt;
&lt;P&gt;&lt;B&gt;1.&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/B&gt;&lt;B&gt;Recompile Stale query plans&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;&amp;lt;StoryTime&amp;gt;Sometime back I got a complaint about slow performance of our database even though the user was using parameterized queries and had created the correct indexes everywhere. The sequence in which he was performing operations was something like this:&lt;/P&gt;
&lt;P&gt;-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Create the full database schema (tables, indexes etc). &lt;/P&gt;
&lt;P&gt;-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Prepare SqlCeCommand objects for *all* queries that will ever be used by the application.&lt;/P&gt;
&lt;P&gt;-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Populate the database with actual data. Until now all tables were empty.&lt;/P&gt;
&lt;P&gt;-&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; Execute the queries using previously prepared command objects&amp;nbsp; &lt;/P&gt;
&lt;P&gt;What's wrong with this?&lt;/P&gt;
&lt;P&gt;&amp;nbsp;A prepared command object (SqlCeCommand) holds a query plan. The query optimizer generated this query plan and had optimized it for the state of database when the plan was generated. In this user's case, the database had just empty tables at that point in time. The optimizer therefore inferred that using a table scan is the best way of executing the query (as opposed to using any index). As a result, all his query plans used just table scans instead of the right indexes even when the table size increased!&amp;nbsp; &lt;/P&gt;
&lt;P&gt;&amp;lt;/StoryTime&amp;gt;&lt;/P&gt;
&lt;P&gt;Moral of the story: Caching query plans is good, but only as long as the state of database when they are compiled is representative of the average state of database during execution. If your data is changing rapidly, it is better to recompile the queries every once in a while. &lt;/P&gt;
&lt;P&gt;So, beware of stale query plans!&lt;/P&gt;
&lt;P&gt;Note that query plans can be cached for both parameterized and non-parameterized queries and you can run into this problem in either case. (Another insider hack: SqlCeCommand.Prepare doesn't really prepare the query plan. It just marks the command object for plan (re)compilation and the plan is compiled when SqlCeCommand.ExecuteXXX is called next time.) &lt;/P&gt;
&lt;P&gt;&lt;B&gt;2.&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/B&gt;&lt;B&gt;Impression of using parameterized queries without really using them&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;&lt;I&gt;Question:&lt;/I&gt; What's wrong (rather sub-optimal) with the following code:&lt;/P&gt;
&lt;P&gt;SqlCeCommand cmd = con.CreateCommand();&lt;/P&gt;
&lt;P&gt;cmd.CommandText = "SELECT * FROM table1 WHERE C1_DECIMAL = @p1";&lt;/P&gt;
&lt;P&gt;cmd.Parameters.Add("@p1", SqlDbType.Decimal);&lt;/P&gt;
&lt;P&gt;cmd.Parameters["@p1"].Value = 3.5;&lt;/P&gt;
&lt;P&gt;SqlCeDataReader rdr = cmd.ExecuteReader();&lt;/P&gt;
&lt;P&gt;while (rdr.Read()) { /* read the data */}&lt;/P&gt;
&lt;P&gt;rdr.Close();&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;cmd.Parameters["@p1"].Value = 335.01;&lt;/P&gt;
&lt;P&gt;rdr = cmd.ExecuteReader();&lt;/P&gt;
&lt;P&gt;while (rdr.Read())&amp;nbsp;{&amp;nbsp; /* read the data */&amp;nbsp;&amp;nbsp; }&lt;/P&gt;
&lt;P&gt;rdr.Close();&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;I&gt;Answer:&lt;/I&gt; The query plan gets compiled twice, once for every ExecuteReader call! &lt;/P&gt;
&lt;P&gt;&lt;I&gt;Reason:&lt;/I&gt; First time the plan is compiled, the parameter's precision is 2 and scale is 1 (as inferred from its value ‘3.5'). The query plan therefore uses this precision and scale values. The only other values it can accept are those that fit within this range. &lt;/P&gt;
&lt;P&gt;When the query is executed again, the parameter's precision is 5 and scale is 2 (as inferred from its value ‘335.01'). Since it doesn't fit in the parameter as inferred during first plan compilation, the plan is recompiled silently! &lt;/P&gt;
&lt;P&gt;&lt;I&gt;Solution:&lt;/I&gt; If you can anticipate the range (size, precision or scale) of the parameter values, then specify it explicitly. The plan will be generated based on the specified range then. This holds true for all character, binary and numeric types. (The flip side is that for any parameter value that does not fit into the specified range, an error will be thrown.) So the above code can be modified as follows:&lt;/P&gt;
&lt;P&gt;SqlCeCommand cmd = con.CreateCommand();&lt;/P&gt;
&lt;P&gt;cmd.CommandText = "SELECT * FROM [TABLE1] WHERE [C1_DECIMAL] = @p1";&lt;/P&gt;
&lt;P&gt;cmd.Parameters.Add("@p1", SqlDbType.Decimal);&lt;/P&gt;
&lt;P&gt;&lt;B&gt;cmd.Parameters["@p1"].Precision = 10;&amp;nbsp; &lt;/B&gt;// Playing safe by taking a larger range!&lt;/P&gt;
&lt;P&gt;&lt;B&gt;cmd.Parameters["@p1"].Scale = 5;&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;cmd.Parameters["@p1"].Value = 3.5;&lt;/P&gt;
&lt;P&gt;SqlCeDataReader rdr = cmd.ExecuteReader();&lt;/P&gt;
&lt;P&gt;while (rdr.Read()) { /* read the data */}&lt;/P&gt;
&lt;P&gt;rdr.Close();&lt;/P&gt;
&lt;P&gt;cmd.Parameters["@p1"].Value = 335.00;&lt;/P&gt;
&lt;P&gt;rdr = cmd.ExecuteReader();&lt;/P&gt;
&lt;P&gt;while (rdr.Read()){ /* read the data */ }&lt;/P&gt;
&lt;P&gt;rdr.Close();&lt;/P&gt;
&lt;P&gt;&lt;BR&gt;&lt;/P&gt;
&lt;P&gt;&lt;B&gt;3.&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/B&gt;&lt;B&gt;Query optimizer did not choose the best index&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;The query optimizer usually does a good job of choosing the index. However, it does it on a best effort basis and there can be cases where it doesn't pick the right index. In such cases, it makes sense to use index hints and outsmart the optimizer:&lt;/P&gt;
&lt;P&gt;E.g.&amp;nbsp; ...FROM [TABLE1] AS T1 JOIN [TABLE2] AS T2...&amp;nbsp; can be rewritten with index hints as&lt;/P&gt;
&lt;P&gt;...FROM [TABLE1] AS T1 WITH (INDEX(IX_On_Table1)) &lt;/P&gt;
&lt;P&gt;&amp;nbsp;JOIN [TABLE2] AS T2 WITH (INDEX(IX_On_Table2))...&lt;/P&gt;
&lt;P&gt;How do you know if your index is indeed better than the index chosen by optimizer?&lt;/P&gt;
&lt;P&gt;Well, the easiest and brute force method is to run the query with and without an index hint and see which is better.&amp;nbsp; I often use the Query Analyzer on device and SSMS on desktop to get the execution times. Another way is to generate query plans for both queries and study them for better predicate matches, index ordering etc.&lt;/P&gt;
&lt;P&gt;&lt;B&gt;4.&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; &lt;/B&gt;&lt;B&gt;Query optimizer did not choose the best join order&lt;/B&gt;&lt;/P&gt;
&lt;P&gt;As in the case of choosing a wrong index, an optimizer can sometimes choose a wrong join order too. Again a corner case, but not impossible to run into. In such cases, you can specify the join order explicitly:&lt;/P&gt;
&lt;P&gt;E.g. ...FROM [TABLE1] AS T1 INNER JOIN [TABLE2] AS T2 ON ...&amp;nbsp; can be rewritten with a forced join order as&amp;nbsp;&lt;/P&gt;
&lt;P&gt;...FROM [TABLE1] AS T1 &lt;/P&gt;
&lt;P&gt;&amp;nbsp;INNER JOIN [TABLE2] AS T2 ON ...&lt;/P&gt;
&lt;P&gt;&amp;nbsp;OPTION (FORCE ORDER)&lt;/P&gt;
&lt;P&gt;&amp;nbsp;As with any sort of query hint, you should be extra careful that you are doing the right thing. For instance, the join order chosen for a query can change between multiple runs due to several reasons. The cardinality of the join tables could have changed significantly, or the data distribution could have changed. So even though the join order that you selected at the time of deployment was optimal, it may not remain so all the time. Ordinarily the optimizer would decide the join order on fly and therefore can adjust to such changes. &lt;/P&gt;
&lt;P&gt;Not so when the join order has been forced.&lt;/P&gt;
&lt;P&gt;That's all for the time being.&lt;/P&gt;
&lt;P&gt;-Pragya Agarwal&amp;nbsp;&lt;/P&gt;
&lt;P mce_keep="true"&gt;&amp;nbsp;&lt;/P&gt;&lt;img src="http://blogs.msdn.com/aggbug.aspx?PostID=8441485" width="1" height="1"&gt;</description><category domain="http://blogs.msdn.com/sqlservercompact/archive/tags/QueryProcessor/default.aspx">QueryProcessor</category><category domain="http://blogs.msdn.com/sqlservercompact/archive/tags/Troubleshooting/default.aspx">Troubleshooting</category><category domain="http://blogs.msdn.com/sqlservercompact/archive/tags/Performance/default.aspx">Performance</category></item></channel></rss>