With(XLock,RowLock) does not lock row exclusively - sql

I have a table that has a column named "Is_Locked".
I open 2 SSMS and in every one create a new Query with this script:
BEGIN TRAN Nima1
BEGIN TRY
DECLARE #a INT
SELECT #a=COUNT(*)
FROM dbo.Siahe WITH(XLOCK,ROWLOCK)
WHERE TedadDaryaii=8
AND Is_Locked=1
IF #a = 0
BEGIN
UPDATE Siahe
SET Is_Locked = 1
WHERE ShMarja = 9999
END
COMMIT TRAN Nima1
END TRY
BEGIN CATCH
ROLLBACK TRAN Nima1
END CATCH
but if all Is_Lock field Is false then both query execute and Select Statement does not lock the rows exclusively.
Why?

If #a = 0 then there were 0 matching rows from your first query. All 0 of those rows are exclusively locked. I'm a bit confused by your different where conditions in your select and update statements. If the same where conditions were used in both, I'd suggest something like:
UPDATE Siahe
SET Is_Locked = 1
WHERE
Is_Locked = 0 and
/* Other Conditions */
IF ##ROWCOUNT = 1
BEGIN
PRINT 'We got the lock'
END
ELSE
BEGIN
PRINT 'Someone else has the lock'
END

Related

SQL Server : update rows takes a long time

We use SQL Server 2017. We need to a 1 time only update on a table that has about 1.5 million rows.
Before the update, we changed the database recovery model temporarily from "Full" to "Simple", and run this query to commit transaction every 50,000 rows:
declare #LastCount int
set ROWCOUNT 50000
set #LastCount = 1
while (#LastCount > 0)
begin
begin tran
update myTbl
set SendPath = replace(SendPath,'\\Server1\myFolder\','\\Server2\myFolder') ,
ReceivePath = replace(ReceivePath,'\\Server1\myFolder\','\\Server2\myFolder')
set #LastCount = ##ROWCOUNT
commit tran
end
set ROWCOUNT 0
The above statement takes a long time to execute (when I stopped it after 2 hours, it's still not finished yet).
Since the database is now a Simple Recovery Model, do I still need to do the commit tran for every x number of records ?
Or, can I simply just execute this query without the begin tran and commit tran ?
update myTbl
set SendPath = replace(SendPath,'\\Server1\myFolder\','\\Server2\myFolder') ,
ReceivePath = replace(ReceivePath,'\\Server1\myFolder\','\\Server2\myFolder')
Based on the recommendations, I updated the query to this, and now it runs fast:
declare #LastCount int
set ROWCOUNT 50000
set #LastCount = 1
while (#LastCount > 0)
begin
begin tran
update myTbl
set SendPath = replace(SendPath,'\\Server1\myFolder\','\\Server2\myFolder') ,
ReceivePath = replace(ReceivePath,'\\Server1\myFolder\','\\Server2\myFolder')
where left(SendPath,31) = '\\Server1\myFolder\'
set #LastCount = ##ROWCOUNT
commit tran
end
set ROWCOUNT 0
Thank you

A lot of queries get suspended. What should I do?

I'm handling an application used by around 2000 people. Everyday users insert around 300 rows/user at the same time (around 7 am to 11 am). I handle it using stored procedures that contain only insert statements, but I use begin tran to prevent the primary key getting duplicate.
Currently suspended transactions frequently happen, so my stored procedure takes around 1-2minutes to be done and this causes our users to wait for a long time to insert every data.
I already checked:
Disk speed normal around read : 600mb/s write 744mb/s.
Procesor usage between 20 - 40 % with 10 cores.
Memory usage only 6gb, I used 12gb of memory.
Check from sys.dm_exec_requests,sp_who2, and sys.dm_os_waiting_tasks.
Result from number 3 is I found that my stored procedure suspends each other (same stored procedures different executor)
This is my stored procedures (Sorry, for the naming, because it is confidential for my company):
ALTER PROC [dbo].[SP_DESTIONATION_TABLE_INSERT]
{params}
WITH RECOMPILE
AS
BEGIN
BEGIN TRAN InsertT
DECLARE #ERRORNMBR INT
SET #ERRORNMBR = 0
IF #T = ''
BEGIN
-------------------------------------------------
DECLARE #TCID VARCHAR(15)
SELECT #TCID = ID
FROM DESTIONATION_TABLE
WHERE NIK = #NIK AND
CUSTOMERID = #CUSTOMERID AND
CUSTOMERTYPE = #CUSTOMERTYPE AND --edit NvA 20180111
DATEDIFF(day,DATE,#DATE) = 0
--IF THERE IS ALREADY A CALL IN SERVER
IF #TCID IS NOT NULL
BEGIN
IF #INTERFACE <> 'WEB' BEGIN
--GET EXISTING CALL ID
SET #ID = #TCID
BEGIN TRAN UBAH
UPDATE DESTIONATION_TABLE
SET
columns=value
WHERE ID = #ID
AND employeeid = #employeeid
AND CUSTOMERID = #CUSTOMERID
SET #ERRORNMBR = #ERRORNMBR + ##ERROR
IF #ERRORNMBR = 0
BEGIN
COMMIT TRAN UBAH
SELECT
columns
FROM DESTIONATION_TABLE WHERE ID = #ID
END
ELSE
BEGIN
ROLLBACK TRAN UBAH
END
END
COMMIT TRAN InsertT
RETURN
END
--------------------------------------------------
-- CHECK #DEVICECONTROLID
IF #DEVICECONTROLID IS NOT NULL
AND #INTERFACE <> 'WEB'
AND EXISTS(SELECT 1 FROM DESTIONATION_TABLE WHERE DEVICECONTROLID = #DEVICECONTROLID)
BEGIN
IF NOT EXISTS(SELECT 1 FROM DESTIONATION_TABLE_TEMP WHERE DEVICECONTROLID = #DEVICECONTROLID)
BEGIN
INSERT INTO DESTIONATION_TABLE_TEMP
(COLUMNS)
VALUES
(VALUES)
END
SELECT * FROM DESTIONATION_TABLE WHERE _DEVICECONTROLID = #_DEVICECONTROLID
END
ELSE
BEGIN
some logic to make primary key formula{string+date+employeeid+increment}
END
END
ELSE
BEGIN
BEGIN TRAN UBAH
IF #PARAMS = 'WEB'
BEGIN
UPDATE DESTIONATION_TABLE
SET
COLUMNS = PARAMS
WHERE ID = #ID
END
ELSE IF PARAMS = 'MOBILE'
BEGIN
UPDATE DESTIONATION_TABLE
SET
COLUMNS = PARAMS
WHERE ID = ID
END
SET #ERRORNMBR = #ERRORNMBR + ##ERROR
IF #ERRORNMBR = 0
BEGIN
COMMIT TRAN UBAH
SELECT
COLUMNS
FROM DESTIONATION_TABLE WHERE ID = ID
END
ELSE
BEGIN
ROLLBACK TRAN UBAH
END
END
COMMIT TRAN InsertT
END
I need a suggestion, what should I check next to get what's wrong with my server is.
Is begin tran is the issue here?
I'm not an expert but a googling of "BEGIN TRAN" seems to show that it "locks a table until the transaction is committed with a "COMMIT TRAN" ... meaning that it is a blocking process. So if you're table is locked when all these inserts are attempting to execute, some of them would obviously not be successful.
https://www.mssqltips.com/sqlservertutorial/3305/what-does-begin-tran-rollback-tran-and-commit-tran-mean/
I would suggest building a work queue on its own thread that listens for INSERTS/UPDATES. Then the queue can empty into the DB at it's leisure.

Speed up simple update statement in postgres for 1 million rows

I have a very simple sql update statement in postgres.
UPDATE p2sa.observation SET file_path = replace(file_path, 'path/sps', 'newpath/p2s')
The observation table has 1513128 rows. The query so far has been running for around 18 hours with no end in sight.
The file_path column is not indexed so I guess it is doing a top to bottom scan but it seems a bit excessive the time. Probably replace is also a slow operation.
Is there some alternative or better approach for doing this one off kind of update which affects all rows. It is essentially updating an old file path to a new location. It only needs to be updated once or maybe again in the future.
Thanks.
In SQL you could do a while loop to update in batches.
Try this to see how it performs.
Declare #counter int
Declare #RowsEffected int
Declare #RowsCnt int
Declare #CodeId int
Declare #Err int
DECLARE #MaxNumber int = (select COUNT(*) from p2sa.observation)
SELECT #COUNTER = 1
SELECT #RowsEffected = 0
WHILE ( #RowsEffected < #MaxNumber)
BEGIN
SET ROWCOUNT 10000
UPDATE p2sa.observation
SET file_path = replace(file_path, 'path/sps', 'newpath/p2s')
where file_path != 'newpath/p2s'
SELECT #RowsCnt = ##ROWCOUNT ,#Err = ##error
IF #Err <> 0
BEGIN
Print 'Problem Updating the records'
BREAK
END
ELSE
SELECT #RowsEffected = #RowsEffected + #RowsCnt
PRINT 'The total number of rows effected :'+convert(varchar,#RowsEffected)
/*delaying the Loop for 10 secs , so that Update is completed*/
WAITFOR DELAY '00:00:10'
END
SET ROWCOUNT 0

Error Handling in Sybase

Is there a way to handle errors in SYBASE, such as the TRY-CATCH block you can use in MS SQL Server, Oracle, etc?
I've searched the web and the only option I found was the global variable ##error, but it didn' work as I expected, for example, the following code:
begin tran
update table1
set name = 'new name'
where name = 'old name'
update table2
set id = 1
where id = 30
-- suppose id has a unique constraint and there's already a row with id = 1
IF ##error = 0
begin
print 'commited'
commit
end
else
begin
print 'rolled back'
rollback
end
The will indeed rollback somehow, because the name I've changed on table1 keeps the old value as I've tested here, but it doesn't print the messages, or execute any instructions I put after the instructions that causes the error
Can anyone help me in this? Do you know how does Sybase error handling actually works?
1st solution.
You can't catch an exception this way on Sybase. Before you update you have to check data:
if not exists
(
select 1 from table2
where id = 1
)
begin
update table2
set id = 1
where id = 30
end
else
begin
print 'rolled back'
rollback
end
2nd solution.
You can also put an update command to procedure, then you can catch an exception.
Create procedure:
create procedure myproc
as
begin
update table2
set id = 1
where id = 30
end
and run it as below:
begin tran
update table1
set name = 'new name'
where name = 'old name'
exec myproc
IF ##error = 0
begin
print 'commited'
commit
end
else
begin
print 'rolled back'
rollback
end

T-SQL: How to return 0 rows in from stored procedure, and how to use XACT_ABORT and TRY/CATCH

I'm writing a stored procedure and I want to return 0 records when something fails. I can't seem to figure out how to just return 0 rows? I've used SELECT NULL but this returns 1 row with a NULL in row 1 col 1. I have also tried not specifying any SELECT statements in my error code path but when testing the value of ##ROWCOUNT after the call to the SP, it returned 1. I think this may be because the ##ROWCOUNT was never reset from the SELECT statement earlier in the SP (in the EXISTS()). Any advice would be appreciated.
Also, I've got XACT_ABORT set to ON, but I have also used a TRY/CATCH block to ensure I return the correct error "return value" from the stored procedure. Is this okay? If there is an error, will the XACT_ABORT override the TRY/CATCH or will my error code path still lead to the correct return values being returned?
-- Setup
SET NOCOUNT ON; -- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
SET XACT_ABORT ON; -- SET XACT_ABORT ON rollback transactions on errors
DECLARE #return int; SET #return = 1; -- Default to general error
-- Start transaction
BEGIN TRANSACTION
BEGIN TRY
IF NOT EXISTS(SELECT NULL FROM [MyTable] WHERE [Check] = 1)
BEGIN
-- Insert new record
INSERT INTO [MyTable] (Check, Date) VALUES (1, GETDATE());
SELECT SCOPE_IDENTITY() AS [MyValue]; -- Return 1 row
SET #return = 0; -- Success
END
ELSE
BEGIN
-- Fail
SELECT NULL AS [MyValue]; -- Want to return 0 rows not 1 row with NULL
SET #return = 2; -- Fail error
END
END TRY
BEGIN CATCH
-- Error
ROLLBACK TRANSACTION;
SELECT NULL AS [MyValue]; -- Want to return 0 rows not 1 row with NULL
SET #return = 1; -- General error
END CATCH
-- End transaction and return
COMMIT TRANSACTION
RETURN #return;
To return 0 rows, you can do:
SELECT TOP 0 NULL AS MyValue
Personally, I'd use an OUTPUT parameter for this sproc to return the ID back out instead of returning a resultset - that's just my preference though. Then just set that output parameter to e.g. -1 as default to indicate nothing done.
this is how I'd do it:
CREATE PROCEDURE YourProcedure
AS
( #NewMyValue int OUTPUT --<<<<<use output parameter and not a result set
)
BEGIN TRY
--<<<<put everything in the BEGIN TRY!!!
-- Setup
SET NOCOUNT ON; -- SET NOCOUNT ON added to prevent extra result sets from interfering with SELECT statements.
SET XACT_ABORT ON; -- SET XACT_ABORT ON rollback transactions on errors
DECLARE #return int
--<<init multiple variables in a select, it is faster than multiple SETs
--set defaults
SELECT #return = 1 -- Default to general error
,#NewMyValue=NULL
-- Start transaction
BEGIN TRANSACTION --<<<put the transaction in the BEGIN TRY
--<<<lock rows for this transaction using UPDLOCK & HOLDLOCK hints
IF NOT EXISTS(SELECT NULL FROM [MyTable] WITH (UPDLOCK, HOLDLOCK) WHERE [Check] = 1)
BEGIN
-- Insert new record
INSERT INTO [MyTable] (Check, Date) VALUES (1, GETDATE());
SELECT #NewMyValue=SCOPE_IDENTITY() --<<<set output parameter, no result set
,#return = 0; -- Success
END
ELSE
BEGIN
-- Fail
--<<no need for a result set!!! output parameter was set to a default of NULL
SET #return = 2; -- Fail error
END
COMMIT TRANSACTION --<<<commit in the BEGIN TRY!!!
END TRY
BEGIN CATCH
-- Error
IF XACT_STATE()!=0 --<<<only rollback if there is a bad transaction
BEGIN
ROLLBACK TRANSACTION
END
--<<any insert(s) into log tables, etc
--<<no need for a result set!!! output parameter was set to a default of NULL
SET #return = 1; -- General error
END CATCH
-- End transaction and return
RETURN #return;
GO