|
database
newsgroups
|
|||||||||||||||||||||||
|
|||||||||||||||||||||||
Transaction in SPHi,
Is it necessary to put Begin Tran right before another calling SP? e.g. Begin Tran Exec SPName If @@Error <>0 Rollback Tran Else Commit Tran or I should do all the transactions inside SPName? Thanks Ed It really depends on what you want included in your transaction. YOu can nest
transactions, if that is necessary, but realize that rolling back gets either a) harder/impossible or b) more time consuming or c) both when you commit many smaller transactions inside of a transaction that fails. -- Show quoteGregory A. Beamer MVP; MCP: +I, SE, SD, DBA *************************** Think Outside the Box! *************************** "Ed" wrote: > Hi, > Is it necessary to put Begin Tran right before another calling SP? > e.g. > > Begin Tran > Exec SPName > If @@Error <>0 > Rollback Tran > Else > Commit Tran > > or > > I should do all the transactions inside SPName? > > Thanks > Ed I just find out that if there are anything wrong insdie the called SP, the
@@Error on the calling SP is always 0 unless expects parameter was not supplied for the calling SP Ed Show quote "Cowboy (Gregory A. Beamer) - MVP" wrote: > It really depends on what you want included in your transaction. YOu can nest > transactions, if that is necessary, but realize that rolling back gets either > a) harder/impossible or b) more time consuming or c) both when you commit > many smaller transactions inside of a transaction that fails. > > -- > Gregory A. Beamer > MVP; MCP: +I, SE, SD, DBA > > *************************** > Think Outside the Box! > *************************** > > > "Ed" wrote: > > > Hi, > > Is it necessary to put Begin Tran right before another calling SP? > > e.g. > > > > Begin Tran > > Exec SPName > > If @@Error <>0 > > Rollback Tran > > Else > > Commit Tran > > > > or > > > > I should do all the transactions inside SPName? > > > > Thanks > > Ed
Show quote
"Ed" <E*@discussions.microsoft.com> wrote in message Stored procedures don't reliably set @@error. There are ofted statements in news:AB324AD4-9311-400A-B7A7-747CB42EF441@microsoft.com... > Hi, > Is it necessary to put Begin Tran right before another calling SP? > e.g. > > Begin Tran > Exec SPName > If @@Error <>0 > Rollback Tran > Else > Commit Tran > the procedure after the one on which the error occured, in which case @@error will have been cleared. In case of an error the stored procedure should return a non-zero return code. begin tran declare @rc int exec @rc = SPNAme if @rc <> 0 rollback tran else commit tran But I would recommend that you don't put any transaction handling in your stored procedures. It's really tricky to get right. It's generally easier to control transaction scope at the client level. Look at it this way: Who knows when to commit? Only the client does. David To ensure maximum concurrency and throughput, you want to keep your
transactions as small as possible. With this in mind, if your procedure does calculations prior to issuing an insert/update/delete statement or uses one or more cursors, you should wait until the calculations have been computed or the updates have been cached in a table variable before you apply locks. Of course, this raises the possibility that another transaction may change the data while the calculations are being computed, so you have to check for that possibility in the statement(s) that apply the locks. I always add error handling to my stored procedures, and if any error occurs return that value in the return code of the stored procedure, for example: CREATE PROCEDURE procName (paramList) AS BEGIN DECLARE @_ERROR INT, @_ROWCOUNT INT, @_TRANCOUNT INT DECLARE @TRANNAME CHAR(20) SET @TRANNAME = 'procNameTran' SET @_TRANCOUNT = @@TRANCOUNT SET NOCOUNT ON -- perform some lengthy calculations -- now that the calculations are done, wrap the updates in a transaction IF @_TRANCOUNT > 0 SAVE TRANSACTION @TRANNAME -- if a transaction is already in place, create a savepoint ELSE BEGIN TRANSACTION @TRANNAME -- Show quoteSET NOCOUNT OFF UPDATE ... SELECT @_ERROR = @@ERROR, @_ROWCOUNT = @@ROWCOUNT IF @_ERROR != 0 GOTO ERROR -- fail if a sql server error occurred IF @_ROWCOUNT > 0 -- not a problem if no rows updated, just skip the update to the second table BEGIN UPDATE ... SELECT @_ERROR = @@ERROR, @_ROWCOUNT = @@ROWCOUNT IF @_ERROR != 0 OR @_ROWCOUNT != 0 GOTO ERROR -- problem if no rows or a sql server error END -- most of my procedures end with this code COMMIT TRANSACTION @TRANNAME RETURN 0 ERROR: IF @@TRANCOUNT > @_TRANCOUNT ROLLBACK TRANSACTION @TRANNAME IF @_ERROR != 0 RETURN @_ERROR RETURN -1 -- a failure occurred that did not generate an SQL Server error, maybe another tran changed a row END Note: I never use dynamic cursors. I populate a cursor using WITH(NOLOCK), avoid table accesses within the body of the fetch loop, cache the results of a cursor in a table variable and issue the updates using a set-based operation. This minimizes the overhead of using a cursor. To verify that no other transaction changed a row that I've used to perform my calculations, I read out the maximum ROWVERSION of each result set used to perform the calculations while or immediately before I perform the calculations, and then re-read and compare them within the transaction immediately before issuing the updates This places a shared lock on the tables used to perform the calculations, ensuring integrity, and provided none of the rows have changed, the maximum ROWVERSION for each result set will be the same, and that indicates that the calculations are correct and the update should proceed. "Ed" <E*@discussions.microsoft.com> wrote in message news:AB324AD4-9311-400A-B7A7-747CB42EF441@microsoft.com... > Hi, > Is it necessary to put Begin Tran right before another calling SP? > e.g. > > Begin Tran > Exec SPName > If @@Error <>0 > Rollback Tran > Else > Commit Tran > > or > > I should do all the transactions inside SPName? > > Thanks > Ed OOPS, I forgot a statement. To avoid confusion:
-- most of my procedures end with this code IF @@TRANCOUNT > @_TRANCOUNT COMMIT TRANSACTION @TRANNAME -- only commit if a new transaction was created RETURN 0 ERROR: IF @@TRANCOUNT > @_TRANCOUNT ROLLBACK TRANSACTION @TRANNAME -- only rollback if a new transaction was created IF @_ERROR != 0 RETURN @_ERROR RETURN -1 -- a failure occurred that did not generate an SQL Server error, maybe another tran changed a row END Using transaction save points is a useful tool, since the most likely scenario in a collision is that another transaction modified a row used to calculate the results, so recovery from a collision is as simple as calling the procedure again using the same parameters. You should know that if a transaction is only partially rolled back, the locks obtained by the transaction are held until the entire transaction commits. This means that other transactions must wait until your transaction has completed before they are able to perform their updates. This ensures that if a collision occurs, your transaction will be next in line; however, the downside is that the locks obtained are held during recalculation, thereby adding to the cost of a collision. Of course, collisions are usually rare, so it doesn't affect overall performance significantly. It may appear that the additional read will negatively impact performance, but if your server has enough memory, it is most likely that the information read during the calculation phase will still be in memory when the locks need to be applied, so the impact on overall performance is negligible. That performance impact is usually far outweighed by the improved concurrency that results from defering locks until you absolutely need them. It's important that the SELECTS that follow BEGIN TRANSACTION that obtain the locks be issued in the same order in every procedure (that includes triggers and udf's too). This minimizes the possibility of deadlocks. One other note: it is important to populate the UI using at least a READ COMMITTED transaction isolation level. If a dirty read occurs, incorrect information could be presented to a user, and worse yet, the user may waste time changing it, and then see an error message when he tries to save his changes. An error message due to a legitimate collision (someone else changed something while he was working on it) is usually OK, because if you track changes, you can indicate which user changed it in the error message. Other than that, the procedure above applies to both presentation tier and middle-tier updates. Show quote "Brian Selzer" <br***@selzer-software.com> wrote in message news:#074MU#fFHA.2484@TK2MSFTNGP15.phx.gbl... > To ensure maximum concurrency and throughput, you want to keep your > transactions as small as possible. With this in mind, if your procedure does > calculations prior to issuing an insert/update/delete statement or uses one > or more cursors, you should wait until the calculations have been computed > or the updates have been cached in a table variable before you apply locks. > Of course, this raises the possibility that another transaction may change > the data while the calculations are being computed, so you have to check for > that possibility in the statement(s) that apply the locks. > > I always add error handling to my stored procedures, and if any error occurs > return that value in the return code of the stored procedure, for example: > > CREATE PROCEDURE procName (paramList) AS > BEGIN > DECLARE @_ERROR INT, @_ROWCOUNT INT, @_TRANCOUNT INT > DECLARE @TRANNAME CHAR(20) SET @TRANNAME = 'procNameTran' > SET @_TRANCOUNT = @@TRANCOUNT > > SET NOCOUNT ON > -- perform some lengthy calculations > > -- now that the calculations are done, wrap the updates in a transaction > IF @_TRANCOUNT > 0 > SAVE TRANSACTION @TRANNAME -- if a transaction is already in place, > create a savepoint > ELSE > BEGIN TRANSACTION @TRANNAME > -- > SET NOCOUNT OFF > UPDATE ... > SELECT @_ERROR = @@ERROR, @_ROWCOUNT = @@ROWCOUNT > IF @_ERROR != 0 GOTO ERROR -- fail if a sql server error occurred > IF @_ROWCOUNT > 0 -- not a problem if no rows updated, just skip the > update to the second table > BEGIN > UPDATE ... > SELECT @_ERROR = @@ERROR, @_ROWCOUNT = @@ROWCOUNT > IF @_ERROR != 0 OR @_ROWCOUNT != 0 GOTO ERROR -- problem if no rows or > a sql server error > END > > -- most of my procedures end with this code > COMMIT TRANSACTION @TRANNAME > RETURN 0 > > ERROR: > IF @@TRANCOUNT > @_TRANCOUNT ROLLBACK TRANSACTION @TRANNAME > IF @_ERROR != 0 RETURN @_ERROR > RETURN -1 -- a failure occurred that did not generate an SQL Server > error, maybe another tran changed a row > END > > > Note: I never use dynamic cursors. I populate a cursor using WITH(NOLOCK), > avoid table accesses within the body of the fetch loop, cache the results of > a cursor in a table variable and issue the updates using a set-based > operation. This minimizes the overhead of using a cursor. To verify that > no other transaction changed a row that I've used to perform my > calculations, I read out the maximum ROWVERSION of each result set used to > perform the calculations while or immediately before I perform the > calculations, and then re-read and compare them within the transaction > immediately before issuing the updates This places a shared lock on the > tables used to perform the calculations, ensuring integrity, and provided > none of the rows have changed, the maximum ROWVERSION for each result set > will be the same, and that indicates that the calculations are correct and > the update should proceed. > > > > "Ed" <E*@discussions.microsoft.com> wrote in message > news:AB324AD4-9311-400A-B7A7-747CB42EF441@microsoft.com... > > Hi, > > Is it necessary to put Begin Tran right before another calling SP? > > e.g. > > > > Begin Tran > > Exec SPName > > If @@Error <>0 > > Rollback Tran > > Else > > Commit Tran > > > > or > > > > I should do all the transactions inside SPName? > > > > Thanks > > Ed > > OOPS, I screwed up again. I should have looked at one of my procedures
before posting. When I'm writing a new sp I usually copy an old one and modify it. Sorry! This should be correct: CREATE PROCEDURE procName (paramList) AS BEGIN DECLARE @_ERROR INT, @_ROWCOUNT INT, @_TRANCOUNT INT SET @_TRANCOUNT = @@TRANCOUNT DECLARE @TRANNAME CHAR(20) -- this starts out NULL, indicating that the transaction hasn't started SET NOCOUNT ON -- perform some lengthy calculations -- now that the calculations are done, wrap the updates in a transaction SET @TRANNAME = 'procNameTran' -- we're starting the update phase IF @_TRANCOUNT > 0 SAVE TRANSACTION @TRANNAME -- if a transaction is already in place, create a savepoint ELSE BEGIN TRANSACTION @TRANNAME /* add SELECT ... WITH(REPEATABLEREAD) statements here obtain shared locks for rows used to calculate updates and to verify that none of the rows changed since they were read out during the calculation phase and to ensure that they won't change until the transaction completes, and SELECT ... WITH(UPDLOCK) statements to obtain update locks for rows that are to be updated. make sure the locks are obtained in the same order in every procedure. */ SET NOCOUNT OFF UPDATE ... SELECT @_ERROR = @@ERROR, @_ROWCOUNT = @@ROWCOUNT IF @_ERROR != 0 GOTO ERROR -- fail if a sql server error occurred IF @_ROWCOUNT > 0 -- not a problem if no rows updated, just skip the update to the second table BEGIN UPDATE ... SELECT @_ERROR = @@ERROR, @_ROWCOUNT = @@ROWCOUNT IF @_ERROR != 0 OR @_ROWCOUNT != 0 GOTO ERROR -- problem if no rows or a sql server error END -- most of my procedures end with this code IF @@TRANCOUNT > @_TRANCOUNT COMMIT TRANSACTION @TRANNAME -- only commit if a new transation was initiated RETURN 0 ERROR: IF @TRANNAME IS NOT NULL AND @@TRANCOUNT > 0 ROLLBACK TRANSACTION @TRANNAME -- undo any changes made by this procedure IF @_ERROR != 0 RETURN @_ERROR RETURN -1 -- a failure occurred that did not generate an SQL Server error, maybe another tran changed a row END Show quote "Brian Selzer" <br***@selzer-software.com> wrote in message news:#074MU#fFHA.2484@TK2MSFTNGP15.phx.gbl... > To ensure maximum concurrency and throughput, you want to keep your > transactions as small as possible. With this in mind, if your procedure does > calculations prior to issuing an insert/update/delete statement or uses one > or more cursors, you should wait until the calculations have been computed > or the updates have been cached in a table variable before you apply locks. > Of course, this raises the possibility that another transaction may change > the data while the calculations are being computed, so you have to check for > that possibility in the statement(s) that apply the locks. > > I always add error handling to my stored procedures, and if any error occurs > return that value in the return code of the stored procedure, for example: > > CREATE PROCEDURE procName (paramList) AS > BEGIN > DECLARE @_ERROR INT, @_ROWCOUNT INT, @_TRANCOUNT INT > DECLARE @TRANNAME CHAR(20) SET @TRANNAME = 'procNameTran' > SET @_TRANCOUNT = @@TRANCOUNT > > SET NOCOUNT ON > -- perform some lengthy calculations > > -- now that the calculations are done, wrap the updates in a transaction > IF @_TRANCOUNT > 0 > SAVE TRANSACTION @TRANNAME -- if a transaction is already in place, > create a savepoint > ELSE > BEGIN TRANSACTION @TRANNAME > -- > SET NOCOUNT OFF > UPDATE ... > SELECT @_ERROR = @@ERROR, @_ROWCOUNT = @@ROWCOUNT > IF @_ERROR != 0 GOTO ERROR -- fail if a sql server error occurred > IF @_ROWCOUNT > 0 -- not a problem if no rows updated, just skip the > update to the second table > BEGIN > UPDATE ... > SELECT @_ERROR = @@ERROR, @_ROWCOUNT = @@ROWCOUNT > IF @_ERROR != 0 OR @_ROWCOUNT != 0 GOTO ERROR -- problem if no rows or > a sql server error > END > > -- most of my procedures end with this code > COMMIT TRANSACTION @TRANNAME > RETURN 0 > > ERROR: > IF @@TRANCOUNT > @_TRANCOUNT ROLLBACK TRANSACTION @TRANNAME > IF @_ERROR != 0 RETURN @_ERROR > RETURN -1 -- a failure occurred that did not generate an SQL Server > error, maybe another tran changed a row > END > > > Note: I never use dynamic cursors. I populate a cursor using WITH(NOLOCK), > avoid table accesses within the body of the fetch loop, cache the results of > a cursor in a table variable and issue the updates using a set-based > operation. This minimizes the overhead of using a cursor. To verify that > no other transaction changed a row that I've used to perform my > calculations, I read out the maximum ROWVERSION of each result set used to > perform the calculations while or immediately before I perform the > calculations, and then re-read and compare them within the transaction > immediately before issuing the updates This places a shared lock on the > tables used to perform the calculations, ensuring integrity, and provided > none of the rows have changed, the maximum ROWVERSION for each result set > will be the same, and that indicates that the calculations are correct and > the update should proceed. > > > > "Ed" <E*@discussions.microsoft.com> wrote in message > news:AB324AD4-9311-400A-B7A7-747CB42EF441@microsoft.com... > > Hi, > > Is it necessary to put Begin Tran right before another calling SP? > > e.g. > > > > Begin Tran > > Exec SPName > > If @@Error <>0 > > Rollback Tran > > Else > > Commit Tran > > > > or > > > > I should do all the transactions inside SPName? > > > > Thanks > > Ed > >
Other interesting topics
|
|||||||||||||||||||||||