2

I'm writing simple text log Entries to an SQL-table like so:

private SqlConnection sqlcon;

// ...

try {
    sqlcon.Open();

    SqlCommand cmd = sqlcon.CreateCommand();
    cmd.CommandText = this.logString;

    cmd.ExecuteNonQuery();
} 
/* catch SqlException, InvalidOperationException, Exception */ 
/* finally sqlcon.Close() */

Asynchronous Processing is set to false, so i thought the log entries would appear in the table in the order of execution. But that is not the case, if 2 or more log entries are fired in about less than 5ms.

So my guess is that ExecuteNonQuery runs on a different thread and somehow the different events get mixed up. I've already tried using Asynchronous Processing with BeginExecuteNonQuery and EndExecuteNonQuery, but it messes up my code and is not really working anyway.

So my question is: Is there any way to ensure, that the nonQueries are being executed in exactly the order they were triggered?

edit: Maybe it's important, i'm using a timestamp like so

cmd.Parameters.Add("timestamp", SqlDbType.DateTime ).Value = System.DateTime.Now;
Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
klamann
  • 1,697
  • 17
  • 28
  • And when you uncomment the finally part? – H H Apr 11 '12 at 10:06
  • these aren't commented, i just included them in comments so you know i didn't forget them and i don't need to waste screen space with useless implementation details. the full implementation is: catch(SqlException e) { Console.WriteLine("Logging to SQL failed: {0}", e); } catch(InvalidOperationException e) { Console.WriteLine("Logging to SQL failed: {0}", e); } catch(Exception e) { Console.WriteLine("Logging to SQL failed: {0}", e); } finally { sqlcon.Close(); } – klamann Apr 11 '12 at 10:09
  • Well, i actually DO add a timestamp, using System.DateTime.Now, and when i order by this timestamp, some close entries are getting mixed up... – klamann Apr 11 '12 at 10:12
  • You're now wasting mind-space, better to (ab)use the screen. – H H Apr 11 '12 at 10:13
  • 1
    Updated my answer, DateTime.Now isn't good if you log with 5 ms frequency – Adriano Repetti Apr 11 '12 at 10:23

4 Answers4

2

Asynchronous?

If you open/close the connection each time you can't be really sure of that, it's somehow equivalent (please forgive me, I know this isn't really exact) to call BeginExecuteNonQuery (it was the method used before MARS). Actually each request is synchronous but, if you log something with a 5 ms interval, you can't be sure about the order SQL Server will serve that requests (DB guys here will think about the old problem of IDENTITY columns).

TimeStamp

Then the solution should be to use a TimeStamp column and to order results based on that column (write order doesn't matter). It's the perfect solution (as pointed by other answers do not forget to use the right data type to store the data).

How to get the actual time? You can do it on client-side (as in your example) using DateTime.Now function or on server-side using SYSDATETIME (as pointed by @PetrAbdulin). It's a perfect solution if you do not need high resolution.

PRECISION

Both functions rely on Windows system timer and it's resolution isn't granted to be more than 10 ms (then, if you really need 5 ms granularity, you should avoid them).

On MSDN you can read that DateTime.Now on Windows NT has a resolution of 10 ms, the SYSDATETIME calls GetSystemTimeAsFileTime, the FILETIME structure has a 100 ms precision but the timer itself isn't granted to achieve that result!!! In some conditions you may even get a 1 ms tick but it's not reliable at all. In the documentation about the StopWatch timer you can read:

The timer used by the Stopwatch class depends on the system hardware and operating system. IsHighResolution is true if the Stopwatch timer is based on a high-resolution performance counter. Otherwise, IsHighResolution is false, which indicates that the Stopwatch timer is based on the system timer.

What does it mean? A system timer is never granted to have a high resolution (do not be confused by the precision of the structure used to store the time).

It could be a problem or not, it depends for what you'll use your logs. If you have to log the list of file copied from one folder to another during a backup then you may not need such precision. If your logs may be used for legal stuffs (yes, I know they should not have any legal value) or to debug a subtle threading issue then you'll need it.

SOLUTIONS

If you need a high resolution timer (and, on Windows, you may consider anything < 10 ms as high resolution) you have to deal with Performance Counters. Take a look at this great article about timing. Of course you do not need all that stuff but it points the problem.

In .NET you may use DateTime.Now with a StopWatch (checking the IsHighResolution property). Read this good post here on SO about the use of the StopWatch to increase the precision of DateTime.Now.

COMMON ERRORS

First do not confuse the precision of the data type used to store the time with the resolution of the timer. It doesn't matter how much your timer is precise if you store that value in a low resolution field but to use a high resolution field doesn't transform your coarse timer into a high resolution one.

Moreover you should not use the local time as TimeStamp, you'll confuse your logs when the system time will change because of daylight saving. Think about this:

               00:00   02:00   02:01   03:00   02:00
Log              #1      #2      #3              #4
System time                             -1

Now when'll you read your log you'll get this order: 1, 2, 4, 3. For this reason you should NEVER use DateTime.Now and SYSDATETIME for your logs, you should always prefer the DateTime.UtcNow and SYSUTCDATETIME functions (moreover performances of DateTime.UtcNow are a little bit better).

Community
  • 1
  • 1
Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
  • +1 for including a timestamp. If you have that then the order of insert doesn't matter. – Rory Hunter Apr 11 '12 at 10:20
  • I'm already using a timestamp, but the problem exists anyway. I Guess the problem really is that I'm always using a new Connection for each query. I thought this would be a good practice because i wouldn't lock the database for too long, but it seems this could be the cause of the problem... I'll try it with a single always-open connection and report back if it worked. – klamann Apr 11 '12 at 10:26
  • Are you sure about the resolution of DateTime? I grabbed DateTime.Now twice between a Thread.Sleep(1) and had only 1.95 ms change between the two values. Also, MSDN states "Time values are measured in 100-nanosecond units called ticks [...]" – sisve Apr 11 '12 at 10:34
  • Yes, resolution of DateTime isn't more than 10/15 ms and anyway far away from one thick (you can _store_ a value with that precision but you can't have that precision from DateTime.Now). See http://msdn.microsoft.com/en-us/library/system.datetime.now.aspx. – Adriano Repetti Apr 11 '12 at 10:37
  • _My_ Windows 7 has sub-millisecond resolution, but I cant find it documented anywhere. I can do a tight loop reading DateTime.Now until the Ticks property changes, and get steady increments of 9761 ticks = 0.9761 milliseconds. – sisve Apr 11 '12 at 10:55
  • @SimonSvensson yes, possibly but MSDN says "The resolution of this property depends on the system timer" and its "approximate resolution is 10 ms". So, even if it _could_ have a good resolution when CPU usage is low, it's not reliable at all if he needs a precise timing. – Adriano Repetti Apr 11 '12 at 11:19
  • Thanks for this great and detailed post, i never knew that System time was so unprecise by default. The reason why for my needs SYSDATETIME is enough is that the log entries are executed synchronous, so the time is measured when the SQL-query is executed, and for now the gap between each query is large enough. I know, to be prepared for faster systems it would be good to solve this now, but for my simple application it is not of paramount importance. Same counts for the daylight saving time issue. If the program grows larger, i might consider it :) – klamann Apr 11 '12 at 13:38
1

What if you use SQL Server SYSDATETIME to insert timestamps? I think that ExecuteNonQuery is synchronous, but maybe something wrong with your timestamp.

There is a good document about time resolution in Windows.

Petr Abdulin
  • 33,883
  • 9
  • 62
  • 96
  • this solved the issue. Sorry, if i had realized that my ordering problem resulted from entries with exactly the same timestamps, i could have saved you some time :) - it wasn't a concurrency-issue after all... – klamann Apr 11 '12 at 10:59
  • @Klamann SYSDATETIME has (as DateTime.Now) a **10/15 ms resolution**. You can't use it if you need a granularity of 5 ms or less (it may works or not depending on a lot of factors, I guess it may matter or not depending what kind of log you need to save). Again another note: **Do not use LOCAL time** but UTC time or you'll write **wrong** logs because of changes of daylight saving time. – Adriano Repetti Apr 11 '12 at 11:28
1

SQL Server has a datetime precision of 3 milliseconds. Do those entries that's listed out-of-order have the same timestamp? (With milliseconds ending with 0, 3 or 7).

You need a field with higher precision, like the datetime2 data type (available from SQL Server 2008), or an incremental counter for the items to be correctly sorted.

sisve
  • 19,501
  • 3
  • 53
  • 95
  • Yes, they really do have the exact same timestamp. What alternative is there to System.DateTime.Now? – klamann Apr 11 '12 at 10:53
0

Why aren't you using transactions? if the data is important then you should use SqlTransaction.

Why do you need ordering? Do you have a pk on your table? how is it inserted, or is the timestamp your pk?

By the way, please don't cache the connections. .net is doing that for you better. use using like in this msdn example.

  • One more option is to add a lock on the method inserting the log, or caching all the messages and bulkloading them. Another is to use log4net SqlAppender: http://logging.apache.org/log4net/release/config-examples.html. – Renana Yacobi Apr 11 '12 at 10:56