I've got a large T-SQL script that opens and closes a few transactions. I have read that committing a batch with a GO statement effectively clears all variables out from it's scope.
Given the script below, will #MyImportantVariable be defined after the transaction is committed?
Is this an issue, if so, how do I overcome it?
DECLARE #MyImportantVariable INT;
SET #MyImportantVariable = 42;
DECLARE #Counter INT;
SET #Counter = 0
DECLARE UpdateGarmentCursor CURSOR FAST_FORWARD
FOR
SELECT
MyColumn
FROM
MyWonderfulTable
BEGIN TRANSACTION
WHILE ##TRAN_STATUS = 0
BEGIN
-- Do interesting work in here
SET #Counter = #Counter +1
IF(#Counter>10)
BEGIN
COMMIT TRANSACTION
-- What happens here to #MyImportantVariable?
SET #Counter = 0
BEGIN TRANSACTION
END
END
-- Close the transaction one last time
COMMIT TRANSACTION
The variable will still exist.
Your example contains no GO commands, only transactions.
GO signals the end of a batch... batches and transactions are two different things entirely.
From MSDN:
GO is not a Transact-SQL statement; it is a command recognized by the
sqlcmd and osql utilities and SQL Server Management Studio Code
editor.
SQL Server utilities interpret GO as a signal that they should send
the current batch of Transact-SQL statements to an instance of SQL
Server. The current batch of statements is composed of all statements
entered since the last GO, or since the start of the ad hoc session or
script if this is the first GO.
Related
I have a MySQL database and a bunch of stored procedures.
I have a test SQL file which calls the stored procedures to check that they succeed on sample data and populate a test database with example records.
I would also like to have a set of statements which, in pseudocode, would be
SELECT (CALL SomeProc('invalid argument') EMITS ERROR) AS SomeProcCheck;
In this imaginary example, SomeProc is written as
CREATE STORED PROCEDURE SomeProc (arg TEXT)
BEGIN
IF (IsNotValid(arg))
THEN
SIGNAL SQLSTATE '45000';
END IF;
INSERT INTO Foo (...);
END
I want my test database init script to verify that failure branches are being hit under the right circumstances.
Can I do this within MySQL?
Use a continue handle and gobble the error, if your call succeeds then signal an error:
DECLARE signalled INT DEFAULT 0;
BEGIN
DECLARE CONTINUE HANDLER FOR SQLSTATE '45000'
BEGIN
SET signalled = 1;
END;
CALL SomeProc('invalid argument');
END
IF (signalled = 0)
THEN
SIGNAL SQLSTATE '45000';
END IF;
Note the scope of the handler so it doesn't handle the second SIGNAL in case the error condition did not happen.
I think an exit handler without the signalled flag and subsequent test would also work, but I'm not sure whether the exit is global or just the scope of the handler... this brings me to I don't have a MySql db to test this so sorry for any syntax errors/bugs.
I dont remember exactly the specific but using something along the lines of start transaction and set autocommit and rollback if something fails into your transaction
im trying to make a multiple statement query like this :
// without the second insert the query works fine.
// i need 2 querys to work because later, i'll do inserts on different kind of tables.
// that's why i need 2 querys, not a single query which insert 2 records.
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+');');
SQL.Add('insert into client (name,age) values ('+QuotedStr('doe')+','+QuotedStr('21')+');');
ExecSQL;
end;
i got this error message : SQL Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'insert into client (name,age) values ('doe','21')' at line 2;
i already check the manual, The components TZQuery and TZUpdateSql (from zeos lib ) provide the possibility to execute multiple statements, internally.
EDIT [SOLVED]
Thank you GregD, after run several tests, transactions works fine for me!
that's how i use, to help others in the future.
try
ZConnection.AutoCommit := True;
ZConnection.StartTransaction;
With ZQuery Do
begin
SQL.Clear;
SQL.Add('insert into clients (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+')');
ExecSQL;
SQL.Clear;
SQL.Add('insert into clients (name,age) values ('+QuotedStr('doe')+','+QuotedStr('21')+')');
ExecSQL;
end;
ZConnection.Commit;
except
ZConnection.Rollback
end;
This is how AutoCommit property actually works in Zeos:
when AutoCommit is True, then the transactions are commited automatically after each executed SQL statement, but you can use the StartTransaction command explicitely to prevent this auto commiting, until you explicitely call Commit.
when AutoCommit is False, you should not call StartTransaction. Then the transaction is started automatically, but it will not commit automatically after every executed statement.
procedure StartTransaction The StartTransaction procedure starts a new transaction within the connected database. It should be only used when AutoCommit property is TRUE. Whenever you try to call it with AutoCommit set to false, an SInvalidOpInNonAutoCommit will be raised. This behaviour is expected, as StartTransaction should be used as a escape to the AutoCommit mode. When you call StartTransaction, the AutoCommit is "turned off", and then, when you call Commit or Rollback, the AutoCommit is "turned on" again. If you're working with AutoCommit set to false, new transactions are created automatically and you choose how you will close them (Commit or Rollback).
procedure Commit Commit current statements to the database. Should be used only in non-AutoCommit mode (where every statement is auto-commited, making this procedure useless) or when you are in AutoCommit mode and want to finish a transaction opened by StartTransaction procedure. Commiting finishes the current transaction, if there's any. If you don't want to save your satatements to the database, you should use the Rollback procedure.
procedure Rollback Rolls back all previous statements in current transaction. Should be used only in non-AutoCommit mode (where every statement is auto-commited, making this procedure useless) or when you are in AutoCommit mode and want to finish a transaction opened by StartTransaction procedure. The Rollback finishes the current transaction, if there's any. If you don't want to loose your satatements, you should use the Commit procedure.
I have no idea about Zeos and multiple statements, but that's not really the problem here. You've caused a major security issue with your query (SQL injection) and a slow method of performing them at all (concatenated strings that can't be cached and reused).
If you properly stop using string concatenation to form your queries, and use parameterized statements instead, you don't need to worry about the multiple statements at all:
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age)');
SQL.Add('values (:Name, :Age);'
ParamByName('Name').AsString := 'John';
ParamByName('Age').AsInteger := 20;
ExecSQL;
ParamByName('Name').AsString := 'Doe';
ParamByName('Age').AsInteger :- 21;
ExecSQL;
end;
The query will now run faster (because the DBMS can compile it once and reuse it multiple times (the "caching" I mentioned), you don't have the SQL injection risk any longer, and the multiple statements are no longer needed.
Try this code and let us know if the same problem arises:
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+'),('+QuotedStr('doe')+','+QuotedStr('21')+');');
ExecSQL;
end;
This way you can also speed up the MySQL handling of this INSERT query, as it does in one batch and not twice.
EDIT #1:
I'm not an expert in Zeos, but with other languages, you could try to execute the query one by one:
with ZQuery1 do
begin
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('john')+','+QuotedStr('20')+');');
ExecSQL;
SQL.Clear;
SQL.Add('insert into client (name,age) values ('+QuotedStr('doe')+','+QuotedStr('21')+');');
ExecSQL;
end;
EDIT #2: Transactions
One question on Stackoverflow has many good examples about using transactions in MySQL. Although, the examples are written for PHP, I'm sure you could find some good pointers there. Make sure that your tables on the MySQL server are InnoDB not MyISAM.
I'm not an expert in ZEOS either, but looking at the source, have you set MultiStatements property of TZUpdateSQL to true?
Have you tried TZSQLProcessor? Said that the component was made for such needs ( as in ZSqlProcessor.pas unit):
{**
Implements a unidatabase component which parses and executes SQL Scripts.
}
Let's say I have two stored procedures, Outer and Inner:
CREATE PROCEDURE dbo.Outer
AS
BEGIN
SET NOCOUNT ON;
SET XACT_ABORT ON;
BEGIN TRAN
EXEC Inner
-- Perform additional processing (which should not occur if there is
-- a ROLLBACK in Inner)
...
COMMIT
END;
GO
The Outer stored procedure turns on XACT_ABORT and starts an explicit transaction. It then calls the Inner stored procedure inside the transaction.
CREATE PROCEDURE dbo.Inner
AS
BEGIN
DECLARE #Id INT=(SELECT Id FROM SomeTable WHERE ...);
IF (#Id IS NOT NULL)
ROLLBACK;
INSERT INTO SomeTable(...)
VALUES (...);
END;
GO
The Inner stored procedure performs a check to see if something is present and if it is wants to rollback the entire transaction started in the Outer stored procedure and abort all further processing in both Inner and Outer.
The thing that happens instead of what I expect as outlined above, is that I get the error message:
In Inner:
Transaction count after EXECUTE indicates a mismatching number of
BEGIN and COMMIT statements. Previous count = 1, current count = 0.
In Outer:
The COMMIT TRANSACTION request has no corresponding BEGIN TRANSACTION.
Clearly, the ROLLBACK even with XACT_ABORT turned on does not stop the flow of execution. Putting a RETURN statement after the ROLLBACK gets us out of Inner, but execution in Outer continues. What do I need to do in order to cause the ROLLBACK to stop all further processing and effectively cause an exit out of Outer?
Thanks for any help.
Issuing the rollback does not abort the batch (Regardless of the XACT_ABORT setting). The batch will automatically abort in your case if an error is thrown with the high enough severity - either system or custom generated via RAISERROR.
You have to implement Exception Handling
Begin Try
Set NOCOUNT ON
SET XACT_ABORT ON
Begin Tran
--Your Code
Commit Tran
End Try
Begin Catch
Rollback Tran
End Catch
I am implementing many SSB working on two different instances. They are data push pattern based on asynchronous triggers.
My SQL Info is as shown below:
Microsoft SQL Server Management Studio 10.50.2500.0
Microsoft Analysis Services Client Tools 10.50.2500.0
Microsoft Data Access Components (MDAC) 6.1.7601.17514
Microsoft MSXML 3.0 4.0 5.0 6.0
Microsoft Internet Explorer 9.0.8112.16421
Microsoft .NET Framework 2.0.50727.5448
Operating System 6.1.7601
My scenarios are mainly as shown below
Multiple Records are inserted as Bulk in table or One Record.
This data is sent to another DB.
The activation procedure starts between BEGIN TRAN and END TRAN.
It validates this message.
If validation not succeeded, this message should be removed from Queue and ACk is sent back to infrom that message was invalid using a different SSB objects.
Else, ACK is sent infroming message is read successfully.
Then, Activation Procedure call another Procedure to process the message body.
This USP_Process_Records is between BEGIN TRAN AND END TRAN too.
For so many reasons, this procedure might fail according to some buisness needs I've.
Either it'll Pro SQL Server 2008 Service Broker.
So in the Activation Procedure, it'll either go into failure condition for the USP_Process_Records or go to BEGIN CATCH part and rollback the transaction and send failure ACK.
At the end, I found that the previous read success ack isnt' sent at all and the second is sent normally.
So I am very confused for Transaction Management in Broker Service.
Should I use use BEGIN TRAN for each separated task and remove it from Receive and Process UPS?
Should I use TRY, CATCH inside USP_Process_Records too and return errors to USP_Receive_Records?
Should I modify my TRY, CATCH blocks ar Receive to avoid this issues
At the end, I want All acks to be sent even if something went wrong after and want to avoid Poison messages and rolling back at all.
Thanks in advance.
-BTW I've used rusanu blog for Broker Service Error Handling and Read Pro SQL Server 2008 Service Broker Transaction Management part.
Find below sample for USP.
--USP_Receive_Records
BEGIN TRY
BEGIN TRAN
WHILE 1=1
BEGIN
SELECT #ReplyMessage = NULL, #TargetDlgHandle = NULL
WAITFOR (RECEIVE TOP(1)
#TargetDlgHandle=Conversation_Handle
,#ReplyMessage = CAST(message_body AS XML)
,#ReplyMessageName = Message_Type_Name
FROM Q_Service_Receive), TIMEOUT 1000
IF #TargetDlgHandle IS NULL
BREAK
--Check if the message has the same message type expected
IF #ReplyMessageName=N'Service_Msg'
BEGIN
--Send Batch Read Success ACK
--Send ACK Here
EXEC [dbo].[USP_ACKMsg_Send] #ACKMsg, #Service_Msg;
--Handle ACK Send failed!
-- Execute the USP_Service_Msg_Process for the batch rows
EXECUTE USP_Service_Msg_Process #ReplyMessageName, #RC OUTPUT;
--Case Processing Succeeded
IF #RC=0
BEGIN
--Send Batch Read Success ACK
END
--SEND ACK Processing failed with Return Code to define cause of the error
ELSE
BEGIN
--Send Batch Processing Failed ACK
END
END
END CONVERSATION #TargetDlgHandle;
END
COMMIT TRAN;
END TRY
BEGIN CATCH
if (XACT_STATE()) = -1
BEGIN
rollback transaction;
END;
if (XACT_STATE()) = 1
BEGIN
DECLARE #error int, #message nvarchar(4000), #handle uniqueidentifier;
SELECT #error = ERROR_NUMBER(), #message = ERROR_MESSAGE();
END conversation #handle with error = #error description = #message;
COMMIT;
END
END CATCH
END
--USP_Process_Records
BEGIN TRAN
While(#nCount <= #nodesCount)
BEGIN
IF(#S_HIS_Status = '02')
BEGIN
-- check N_Paid_Trans_ID is not nuul or zero or empty
IF( #N_GET_ID IS NULL OR #N_GET_ID = 0 OR #N_GET_ID = '')
BEGIN
SET #RC = 8
RETURN;
END
EXECUTE USP_Handle_Delivered_Service #N_GET_ID, #RC OUTPUT
SELECT #myERROR = ##ERROR--, #myRowCount = ##ROWCOUNT
IF #myERROR <> 0 OR #RC <> 0
BEGIN
ROLLBACK TRAN
END
END
--A lot of similar cases
END TRAN
You are mixing BEGIN TRY/BEGIN CATCH blocks with old style ##ERROR checks. It makes both error handling and transaction handling pretty much impossible to manage. Consider this snippet of code:
SELECT #myERROR = ##ERROR--, #myRowCount = ##ROWCOUNT
IF #myERROR <> 0 OR #RC <> 0
BEGIN
ROLLBACK TRAN
END
Can you follow the control flow and the transaction flow involved here? The code is executing in the context of being called from a TRY/CATCH block, so the ##ERROR case should never occur and the control flow should jump to the CATCH block. But wait, what if the procedure is called from a different context when there is no TRY/CATCH block? then the ##ERROR case can be taken but that implies the control flow continues! Even when a TRY/CATCH contest is set up, if #RC is non-zero the transaction is rolled back but control flow continues to the next statements which will now execute in the context of per-statement standalone transactions since the overall encompassing transaction has rolled back! In other words in such case you may send an response Ack to a message you did not receive (you just rolled it back!). No wonder you are seeing cases when behavior seems erratic.
I recommend you stick with only one style of error handling (and the only sane style is BEGIN TRY/BEGIN CATCH blocks). Do not rollback intentionally in case of application logic error, but instead use RAISERROR and rely on the CATCH block to rollback as necessary. Also do style your procedure after the template shown at Exception handling and nested transactions. This template allows for message-by-message decision to rollback to a safepoint in the transaction in case of error (ie. commit your RECEIVE batch of messages, even if some of the messages occurred an error in processing).
I am trying to find a method to rollback a transaction after it has been executed.
For example:
DECLARE #tsubaki VARCHAR(25);
SET #tsubaki = 'A Transaction';
BEGIN TRANSACTION #tsubaki
UPDATE dbo.Maka SET id = 400, name = 'inu' --notice I didn't put there where clause
COMMIT TRANSACTION
Later on I realize that I updated everything in the databse Maka instead of just the one record I originally intended.
Now I try to write code to roll it back before the update:
DECLARE #tsubaki VARCHAR(25);
SET #tsubaki = 'A Transaction';
ROLLBACK TRANSACTION #tsubaki;
Doesn't work. Bottom line: I am looking for a way to rollback a sql transaction in MS-SQL Server 2008 after the transaction has been commit and the sql has ran.
Thanks in advance.
You can't do that from T-SQL code. You will have to restore to a point in time from the log file. Note that the restore will "undo" everything to a point in time, including your transaction.
In the future you should ALWAYS back up your db before any manual update, small or large. You can also cover yourself with a little trick. Write out your update/delete code like this:
SELECT * FROM dbo.Maka
-- UPDATE dbo.Maka SET id = 400, name = 'inu'
WHERE some_identifier = some_value
Run the SELECT version first which is innocuous and when you can verify the record(s) to be updated select the code from the WHERE clause up to the UPDATE and run it.