简体   繁体   中英

Overwrite values every time vs query to see if value has changed

I have a column that I would like to update only if the value I pass in to a stored procedure is different than the value in the column. It happens to be an NVARCHAR(255) column, if that matters.

What are the pros and cons of writing this value every time? What are the pros and cons of checking the value first and writing only if what I have passed in is different than what is in the database?

My simplified example where I'm doing a comparison before writing:

-- @URL and @ContentName are parameters of the sproc    

SET @ExistingURL =
    (SELECT TOP 1 C.URL
    FROM Content C
    WHERE ContentName = @ContentName)

-- update only if the parameter and existing value are different
IF(@ExistingURL != @URL)
BEGIN
    UPDATE Content
    SET URL = @URL
    WHERE ContentName = @ContentName
END

It seems like your query could be re-written as follows, without the need to put anything into a local variable.

UPDATE dbo.Content
  SET URL = @URL
  WHERE ContentName = @ContentName
  AND URL <> @URL;

Assuming that ContentName is unique, this will affect 0 or 1 rows. There is no need to make the logic more complex by pulling the check for the URL out into a separate query. The "write only if needed" part is taken care of by the WHERE clause.

Now, if you were to extend this to more than one column to update, what you're after isn't so simple (and I don't think it's worth it). Let's say, for example, that there was another column name title , and you had a variable coming in called @title . You predict that it will be smart to only update URL when it has changed, and only update title when it has changed. You could accomplish this in a couple of ways, in theory (and keep in mind, this all assumes that both the variables and the columns are not nullable - if they are, it doesn't change these approaches, just makes the syntax a little more daunting):

  1. run two independent updates

     UPDATE dbo.Content SET URL = @URL WHERE ... -- actual where clause AND URL <> @URL; UPDATE dbo.Content SET title = @title WHERE ... -- same where clause AND title <> @title; 
  2. update conditionally

     UPDATE dbo.Content SET URL = CASE WHEN URL <> @URL THEN @URL ELSE URL END, title = CASE WHEN title <> @title THEN @title ELSE title END WHERE ... -- same where clause AND (URL <> @URL OR title <> @title); 

I suspect that the latter won't perform any better than the more natural way to write this, since you're locking and writing to the row anyway (but I don't have any infallible proof of that other than the endorsement from Mike in the comments above):

UPDATE dbo.Content
  SET URL = @URL, title = @title
WHERE ... -- same where clause
AND (URL <> @URL OR title <> @title);

In other words, just update all values, regardless of which ones have actually changed.

Nominally a write is takes twice as long as a read.
A write will place a lock so it could potentially hold up reads.
It will place an entry in the transaction log.
What if you have a trigger on that value.

As a matter of practice I test for <> on any active database.

Taking a lock on every row could be a big hit if table is active.
Your update has to acquire the locks.
Other queries have to wait on your locks (unless they accept dirty reads).

If you are only actually updating 10% of the rows on a big active table and blinding updating all by not testing for <> then that is a big hit.

If you are updating 99% of the rows then you would take a small hit.
Testing for <> takes time.

By checking you could take a small hit but by not checking you (and others) could take a big hit.

Example update that checks for <>

Update [docSVsys] set [ftsStatus] = 5 where [ftsStaus] <> 5 

The lock is on the entire row - not the individual column. If you are updating more than 1 value then what still matters is % of rows.

Update [docSVsys] set [ftsStatus] = 5, [wfID] = 3 
where [ftsStatus] <> 5 or [wfID] <> 3

If only one of the values changed updating both is not not a hit as it had to acquire the update lock on the row.

Actual statement posted after many hours.

As Aaron stated the left joins do nothing.

As it stands that first query returns @URL or null.
And it is not based on ContentName.

So looking at

if(@ExistingURL != @URL)

That would only be true if no row in the table had that value.

A unique constraint would not be the same as it would only allow one null.

I am just surprised your question is not. This update fails?

There is a simple pattern to solve this problem:

update my_table set
my_col = 'newValue'
where id = 'someKey'
and my_col != 'newValue';

This query has the performance of an indexed read, and importantly locks no rows, if the value doesn't need updating.

Only if the value needs updating does the where clause hit a row and lock it (albeit briefly) for update.

You can extend this to handle multiple columns using the same logic:

update my_table set
my_col1 = 'newValue1',
my_col2 = 'newValue2'
where id = 'someKey'
and (my_col1 != 'newValue1'
    or my_col2 != 'newValue2');

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM