"It depends"
If you add a column that does not require adding data to the rows, then it can be quite quick.
For example, adding an int or char requires physical row movements. Adding a nullable varchar with no default shouldn't (unless the NULL bitmap needs to expand)
You need to try it on a restored copy of production to get an estimate
Creating a new table, copying, renaming may take longer if you have to re-add indexes and keys on a billion row table.
I have changed billion row tables that took a few second to add a nullable column.
Did I say to take a backup first?
...why with clustered index, the deadlock is still there (though hit rate seems to be dropped)
The question isn't precisely clear (e.g. how many updates and to which id
values are in each transaction) but one obvious deadlock scenario arises with multiple single-row updates within a single transaction, where there is an overlap of [id]
values, and the ids are updated in a different [id]
order:
[T1]: Update id 2; Update id 1;
[T2]: Update id 1; Update id 2;
Deadlock sequence: T1 (u2), T2 (u1), T1 (u1) wait, T2 (u2) wait.
This deadlock sequence might be avoided by updating strictly in id order within each transaction (acquiring locks in the same order on the same path).
When use the clustered index, there is an exclusive lock on the key as well as an exclusive lock on RID when do update, which is expected; while there are two exclusive lock on two different RID if non-clustered index is used, which confuses me.
With a unique clustered index on id
, an exclusive lock is taken on the clustering key to protect writes to the in-row data. A separate RID
exclusive lock is required to protect the write to the LOB text
column, which is stored on a separate data page by default.
When the table is a heap with only a nonclustered index on id
, two things happen. First, one RID
exclusive lock relates to the heap in-row data, and the other is the lock on the LOB data as before. The second effect is that a more complex execution plan is required.
With a clustered index and a simple single-value equality predicate update, the query processor can apply an optimization that performs the update (read and write) in a single operator, using a single path:
The row is located and updated in a single seek operation, requiring only exclusive locks (no update locks are needed). An example locking sequence using your sample table:
acquiring IX lock on OBJECT: 6:992930809:0 -- TABLE
acquiring IX lock on PAGE: 6:1:59104 -- INROW
acquiring X lock on KEY: 6:72057594233618432 (61a06abd401c) -- INROW
acquiring IX lock on PAGE: 6:1:59091 -- LOB
acquiring X lock on RID: 6:1:59091:1 -- LOB
releasing lock reference on PAGE: 6:1:59091 -- LOB
releasing lock reference on RID: 6:1:59091:1 -- LOB
releasing lock reference on KEY: 6:72057594233618432 (61a06abd401c) -- INROW
releasing lock reference on PAGE: 6:1:59104 -- INROW
With only a nonclustered index, the same optimization cannot be applied because we need to read from one b-tree structure and write another. The multi-path plan has separate read and write phases:
This acquires update locks when reading, converting to exclusive locks if the row qualifies. Example lock sequence with the schema given:
acquiring IX lock on OBJECT: 6:992930809:0 -- TABLE
acquiring IU lock on PAGE: 6:1:59105 -- NC INDEX
acquiring U lock on KEY: 6:72057594233749504 (61a06abd401c) -- NC INDEX
acquiring IU lock on PAGE: 6:1:59104 -- HEAP
acquiring U lock on RID: 6:1:59104:1 -- HEAP
acquiring IX lock on PAGE: 6:1:59104 -- HEAP convert to X
acquiring X lock on RID: 6:1:59104:1 -- HEAP convert to X
acquiring IU lock on PAGE: 6:1:59091 -- LOB
acquiring U lock on RID: 6:1:59091:1 -- LOB
releasing lock reference on PAGE: 6:1:59091
releasing lock reference on RID: 6:1:59091:1
releasing lock reference on RID: 6:1:59104:1
releasing lock reference on PAGE: 6:1:59104
releasing lock on KEY: 6:72057594233749504 (61a06abd401c)
releasing lock on PAGE: 6:1:59105
Note the LOB data is read and written at the Table Update iterator. The more complex plan and multiple read and write paths increase the chances of a deadlock.
Finally, I can't help but notice the data types used in the table definition. You should not use the deprecated text
data type for new work; the alternative, if you really need the ability to store up to 2GB of data in this column, is varchar(max)
. One important difference between text
and varchar(max)
is that text
data is stored off-row by default, while varchar(max)
stores in-row by default.
Use Unicode types only if you need that flexibility (e.g. it is hard to see why an IP address would need Unicode). Also, choose appropriate length limits for your attributes - 255 everywhere seems unlikely to be correct.
Additional reading:
Deadlock and livelock common patterns
Bart Duncan's deadlock troubleshooting series
Tracing locks can be done in a variety of ways. SQL Server Express with Advanced Services (2014 & 2012 SP1 onward only) contains the Profiler tool, which is a supported way to view the details of lock acquisition and release.
Best Answer
Because dropping of a NCI is already as much online as it gets. Is a metadata only operation. There is not even data deletion, a dropped index rowset is simply deallocated, ie. the same operation as truncate does.
Dropping a clustered index, on the other hand, implies a rebuild and is a size-of-data operation, so it does make sense to have an online alternative.