您是否认为在存储过程中始终围绕SQL语句进行事务是一种好习惯?我只是想在我的公司中优化这个遗留应用程序,我发现的一件事是每个存储过程都有BEGIN TRANSACTION
。即使是具有单个select或update语句的过程也只有一个。我认为如果执行多个动作而拥有BEGIN TRANSACTION
会很好,但不仅仅是一个动作。我可能错了,这就是为什么我需要别人来建议我。谢谢你的时间,伙计们。
由于每个SQL语句都以原子方式执行,即完全没有必要。就好像它已经在自己的交易中运行一样。事实上,打开不必要的事务可能会导致锁定增加,甚至死锁。只要与数据库的连接打开并且干扰同一连接中的其他事务,忘记将COMMIT与BEGIN匹配就可以使事务处于打开状态。
这样的编码几乎肯定意味着编写代码的人在数据库编程方面不是很有经验,并且确实存在可能存在其他问题的气味。
我不知道不仅仅为这些语句使用自动提交事务的任何好处。
在任何地方使用显式事务的可能缺点可能是它只会增加代码的混乱,因此在使用显式事务来确保多个语句的正确性时不太容易看到。
此外,除非小心谨慎,否则会增加交易保持锁定的风险(例如使用SET XACT_ABORT ON)。
还有一个小的性能影响,如@8kb's answer所示。这说明了使用visual studio profiler的另一种方式。
(针对空表测试)
CREATE TABLE T (X INT)
SET NOCOUNT ON
DECLARE @X INT
WHILE ( 1 = 1 )
BEGIN
BEGIN TRAN
SELECT @X = X
FROM T
COMMIT
END
SET NOCOUNT ON
DECLARE @X INT
WHILE ( 1 = 1 )
BEGIN
SELECT @X = X
FROM T
END
他们两人最终都花时间在CMsqlXactImp::Begin
和CMsqlXactImp::Commit
上,但对于明确的交易案例,它在这些方法中花费的执行时间比例要大得多,从而减少了做有用工作的时间。
+--------------------------------+----------+----------+
| | Auto | Explicit |
+--------------------------------+----------+----------+
| CXStmtQuery::ErsqExecuteQuery | 35.16% | 25.06% |
| CXStmtQuery::XretSchemaChanged | 20.71% | 14.89% |
| CMsqlXactImp::Begin | 5.06% | 13% |
| CMsqlXactImp::Commit | 12.41% | 24.03% |
+--------------------------------+----------+----------+
我能看到的唯一可能原因是,如果您因SQL失败以外的原因而需要回滚事务。
但是,如果代码是字面上的话
begin transaction
statement
commit
然后我看到绝对没有理由使用显式事务,它可能是因为it's always been done that way而完成的。
一个优点是你可以添加另一个INSERT(例如),它已经安全了。
然后,如果存储过程调用另一个存储过程,您还会遇到嵌套事务的问题。内部回滚将导致错误266。
如果每次调用都是简单的CRUD而没有嵌套那么它就没有意义了:但如果你在TXN之前嵌套或有多次写入,那么拥有一致的模板是件好事。
您提到过您将优化此旧版应用。
为提高性能,您可以做的第一件也是最简单的事情之一是删除所有只执行SELECT的存储过程的BEGIN TRAN和COMMIT TRAN。
这是一个简单的测试来演示:
/* Compare basic SELECT times with and without a transaction */
DECLARE @date DATETIME2
DECLARE @noTran INT
DECLARE @withTran INT
SET @noTran = 0
SET @withTran = 0
DECLARE @t TABLE (ColA INT)
INSERT @t VALUES (1)
DECLARE
@count INT,
@value INT
SET @count = 1
WHILE @count < 1000000
BEGIN
SET @date = GETDATE()
SELECT @value = ColA FROM @t WHERE ColA = 1
SET @noTran = @noTran + DATEDIFF(MICROSECOND, @date, GETDATE())
SET @date = GETDATE()
BEGIN TRAN
SELECT @value = ColA FROM @t WHERE ColA = 1
COMMIT TRAN
SET @withTran = @withTran + DATEDIFF(MICROSECOND, @date, GETDATE())
SET @count = @count + 1
END
SELECT
@noTran / 1000000. AS Seconds_NoTransaction,
@withTran / 1000000. AS Seconds_WithTransaction
/** Results **/
Seconds_NoTransaction Seconds_WithTransaction
--------------------------------------- ---------------------------------------
14.23600000 18.08300000
您可以看到与交易相关的确定开销。
注意:这是假设您的这些存储过程没有使用任何特殊的隔离级别或锁定提示(用于处理悲观并发)。在那种情况下,你会想要保留它们。
因此,为了回答这个问题,我只会留下您正在尝试保留数据修改完整性的事务,以防代码,SQL Server或硬件出现错误。
我只能说将这样的事务块放到每个存储过程中可能是新手的工作。
事务应仅放在具有多个插入/更新语句的块中,除此之外,不需要在存储过程中放置事务块。
执行多次插入/更新/删除时,最好有一个事务来确保操作的原子性,并确保执行所有操作任务或不执行任务。
对于单个插入/更新/删除语句,它取决于您正在执行的操作类型(以及从业务层角度来看)以及它的重要性。如果您在单次插入/更新/删除之前执行某些计算,那么更好地使用事务,可能会在检索插入/更新/删除数据后更改某些数据。
除非您尝试涵盖以下方案,否则默认情况下不应在每个存储过程中使用BEGIN TRANSACTION / COMMIT语法: