I'm working on SQL Server 2008 and I'm trying to execute a stored procedure which updates a table and executes another stored procedure on a linked server.
The point is it works when no update is made, just like this:
[test_DTC] on [Server1]
CREATE PROCEDURE [dbo].[test_DTC]
#UserId int,
#Status tinyint
AS
BEGIN
EXEC [Server2].[Database].[dbo].[test_DTC];
END
GO
[test_DTC] on [Server2]
CREATE PROCEDURE [dbo].[test_DTC]
AS
BEGIN
PRINT 'Done'
END
GO
Execute on Server1:
EXEC [test_DTC]
Result:
Done
But when I include the UPDATE on Server1 procedure, it fails.
[test_DTC] on [Server1]
CREATE PROCEDURE [dbo].[test_DTC]
#UserId int,
#Status tinyint
AS
BEGIN
UPDATE
Users
SET
Status=#Status
WHERE
UserId=#UserId;
EXEC [Server2].[Database].[dbo].[test_DTC];
END
GO
[test_DTC] on [Server2]
CREATE PROCEDURE [dbo].[test_DTC]
AS
BEGIN
PRINT 'Done'
END
GO
Execute on Server1:
EXEC [test_DTC]
Result
Provider OLE DB "SQLNCLI10" from linked server "[Server2]" returned message "The transaction has already been implicitly or explicitly committed". Msg 7391, Level16, State 2, Procedure [Server2].[Database].[dbo].[test_DTC], Line 19
The operation could not be performed because OLE DB provider "SQLNCLI10" for linked server "Server2" was unable to begin a distributed transaction.
Thanks for your help
I have found a solution for this is the MSDN Blog
it says
The reason is that when transactions propagate from one machine to another they include their machine name/DNS name along with it. When it arrives on the other machine, it will use this name to attempt to communicate back to the originator machine. If this communication fails then distributed transactions will not work in the system.
Microsoft has provided a Detailed Article on the same
Related
I want to execute an stored procedure in Server1.DB1, this stored procedure will execute inside another stored procedure using dynamic SQL, it will be in Server1.DB2.
I need to use begin/end transaction to make sure everything is executed or everything fail.
The question is: will the transaction work in this case using dynamic SQL pointed to a the different database?
Like
BEGIN TRANSACT
--Set Status to "In Progress"
SET #Qry = N'EXEC '+ #DB2 + '.[dbo].[StatusUpdate] #Id, #Status'
SET #QryParams = N'#Id INT, #Status INT'
EXEC SP_EXECUTESQL #Qry,
#QryParams,
#Id = #Id,
#Status = #InProgress
INSERT DATA LOCALLY IN A TABLE
UPDATE DATA LOCALLY IN A TABLE
END TRANSACT
I'm using SQL Server 2014.
It depends on REMOTE_PROC_TRANSACTIONS definition:
Specifies that when a local transaction is active, executing a remote
stored procedure starts a Transact-SQL distributed transaction managed
by Microsoft Distributed Transaction Coordinator (MS DTC).
If it's ON:
The instance of SQL Server making the remote stored procedure call is
the transaction originator and controls the completion of the
transaction. When a subsequent COMMIT TRANSACTION or ROLLBACK
TRANSACTION statement is issued for the connection, the controlling
instance requests that MS DTC manage the completion of the distributed
transaction across the computers involved.
Otherwise remote stored procedure calls are not made part of a local transaction.
Several important notes:
Using distributed transaction is risky thus should be carefully used.
This feature is deprecated.
I have two different SQL 2008 servers, I don't have permission to create a linked server in any of them.
i created a trigger on server1.table1 to insert the same record to the remote server server2.table1 using OPENROWSET
i created a stored procedure to insert this record, and i have no issue when i execute the stored procedure. it insert the recored into the remote server.
the problem is when i call the stored procedure from trigger, i get an error message.
can anyone help me please to solve this issue
Stored Procedure:
USE [DB1]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
alter PROCEDURE [dbo].[InsertIntoRemoteTable]
#Description nvarchar(50)
AS
insert into
OPENROWSET(
'SQLNCLI', 'Server=Server2;UID=MySRV2user;PWD=MySRV2Password',
'SELECT Description FROM [RemoteDB].[dbo].[Table_1]')
SELECT #Description
Trigger:
ALTER TRIGGER [dbo].[InsertTable1]
ON [DB1].[dbo].[Table_1]
for insert
AS
BEGIN
SET NOCOUNT ON;
DECLARE #Desc nvarchar(50)
Select #Desc = i.Descr from INSERTED i;
EXEC InsertIntoRemoteTable #Desc
END
When i try to insert a new description value in the table i got the following error message:
"No row was updated
the data in row 1 was not committed
Error source .Net SqlClient Data provider.
Error Message: the operation could not be performed because OLE DB
provider "SQLNCLI10" for linked server "(null)" returned message "The partner transaction manager has disabled its support for remote/network transaction.".
correct the errors entry or press ESC to cancel the change(s).
can anyone help please on this
thanks
I think you have over complicated this. There is really no need for a stored procedure here to do this. You can do this insert into another database directly in your trigger with a simple insert statement. This removes a ton of complexity and it is set based.
ALTER TRIGGER [dbo].[InsertTable1]
ON [DB1].[dbo].[Table_1]
for insert
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO RemoteDB].[dbo].[Table_1](Description)
Select i.Descr
from INSERTED i;
END
I have a remote stored procedure that i am running:
EXECUTE Contoso.Frob.dbo.Grobber #StartDate='20140513', #EndDate='20140518'
and this remote stored procedure returns a rowset:
EmployeeID EmployeeName StartDateTime EndDateTime
---------- -------------- ------------- -----------------------
619 Guyer, Kirsten 2014-05-13 19:00:00.000 2014-05-13 19:00:00.000
...
Excellent. Perfect. Good. Sweet.
Now that i have these results, i need to store them in a table. Any kind of table. I don't care what kind of table:
physical table
temporary table
global temporary table
table variable
I just need them stored so that i can process them. The problem is that when i try to insert the results into a table, whether it be:
a physical table
INSERT INTO EmployeeSchedule
EXECUTE Contoso.Frob.dbo.Grobber #StartDate='20140513', #EndDate='20140518'
temporary table
INSERT INTO #EmployeeSchedule
EXECUTE Contoso.Frob.dbo.Grobber #StartDate='20140513', #EndDate='20140518'
a global temporary table
INSERT INTO ##EmployeeSchedule
EXECUTE Contoso.Frob.dbo.Grobber #StartDate='20140513', #EndDate='20140518'
a table variable
INSERT INTO #EmployeeSchedule
EXECUTE Contoso.Frob.dbo.Grobber #StartDate='20140513', #EndDate='20140518'
SQL Server insists (nay, demands) that it begin a distributed transaction:
OLE DB provider "SQLNCLI10" for linked server "Contoso" returned message "The partner transaction manager has disabled its support for remote/network transactions.".
Msg 7391, Level 16, State 2, Line 41
The operation could not be performed because OLE DB provider "SQLNCLI10" for linked server "Contoso" was unable to begin a distributed transaction.
Why not just...
Now, making changes to the Contoso server is not an option. Why? Doesn't matter. Pretend that Jack Bauer will make an appearance and Guantanamo anyone who tries to modify Contoso. This means i cannot enable or reconfigure MSDTC on \\Contoso.
Did you try using READ UNCOMMITTED?
Yes.
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED
INSERT INTO #EmployeeSchedule
EXECUTE wclnightdb.NGDemo.dbo.tbtGetSchedule #StartDate, #EndDate
The partner transaction manager has disabled its support for remote/network transactions.
And:
INSERT INTO #EmployeeSchedule
WITH (NOLOCK)
EXECUTE wclnightdb.NGDemo.dbo.tbtGetSchedule #StartDate, #EndDate
Sorry. No nolock. Nolock is a no no:
Msg 1065, Level 15, State 1, Line 15
The NOLOCK and READUNCOMMITTED lock hints are not allowed for target tables of INSERT, UPDATE, DELETE or MERGE statements.
I always could give up on SQL Server
If i were doing this in a programming environment, it would be fairly easy to fix:
using (IDataReader rdr = ADOHelper.Execute(conn, "EXECUTE Contoso.Frob.dbo.Grobber #StartDate='20140513', #EndDate='20140518'")
{
while (rdr.Read())
{
InsertRowIntoTable(conn, rdr);
}
}
Although that would require me to create a binary, ship it, and schedule it. I'm looking for the option that works with SQL Server (so SQL Agent can schedule the job).
Bonus Reading
SET REMOTE_PROC_TRANSACTIONS (Transact-SQL)
How do I use the results of a stored procedure from within another?
How can one iterate over stored procedure results from within another stored procedure....without cursors?
SQL Server insists (nay, demands) that it begin a distributed
transaction:
If you can't configure your servers to use distributed transactions for whatever reason, you can tell it not to.
USE [master]
GO
EXEC master.dbo.sp_serveroption
#server = N'Contoso',
#optname = N'remote proc transaction promotion',
#optvalue = N'false'
GO
Or in SSMS GUI:
I don't know all implications of turning off this option, but at least now my INSERT ... EXEC [LinkedServer]... works.
Two options to try would be:
Since you already have a Linked Server set up, use it with OPENQUERY, as in:
SELECT column1, column2 FROM OPENQUERY(Contoso, 'EXECUTE Frob.dbo.Grobber #StartDate=''20140513'', #EndDate=''20140518''')
If the returned columns will remain consistent, create a SQLCLR Table-Valued Function. This assumes that the remote proc is Read-Only (i.e. SELECT-only). But unlike T-SQL functions, SQLCLR functions can execute Stored Procedures using the connection string "Context Connection = True;" as long as the Stored Procedure is SELECT-only (i.e. does not change the state of the DB through DML, DDL, etc).
How about this:
-- Either create a job that runs your remote sql via a SQLSMD command, or just run something like this:
EXEC master..xp_cmdshell 'SQLCMD -S Server\SQLSERVERDEV2005 -i"c:\DML.sql"'
(It might be easier with a job because you can modify the job step easily via sp_update_jobstep to get the right values in for your parameters)
-- Output the result of the sqlcmd into a file
-- Load the file into a table via bulk import.
I'm working with SQL Server 2012 Express.
I'm using Service Broker to run a stored procedure asynchronously.
The activation procedure has to access another database to execute another stored procedure. This is the code:
CREATE PROCEDURE [dbo].[GetNewCodes]
#gintNewCodes bigint,
#presNewCodes tinyint,
#levelNewCodes bigint,
#quantityNewCodes smallint
AS
-- Get new codes from INCIC database.
DECLARE #return_value int,
#xmlGenerated xml,
#xmlString NVARCHAR(MAX)
SET NOCOUNT ON;
-- Set that this stored procedure is running
update dbo.RunningSPs with (serializable) set conf_value = 1
where sp_name = N'GetNewCodes'
if ##rowcount = 0
begin
insert dbo.RunningSPs(sp_name, conf_value) values (N'GetNewCodes', 1)
end
EXEC #return_value = [INCIC].[dbo].[ReadCodeBuffer]
#gint = #gintNewCodes,
#pres = #presNewCodes,
#level = #levelNewCodes,
#quantity = #quantityNewCodes,
#xmlGenerated = #xmlGenerated OUTPUT
SET #xmlString = cast(#xmlGenerated as nvarchar(max))
-- Process these new codes on TRZ.
EXEC dbo.ProcessCodes #XmlString = #xmlString
-- Update that we are not running this procedure any more.
update dbo.RunningSPs with (serializable) set conf_value = 0
where sp_name = N'GetNewCodes'
if ##rowcount = 0
begin
insert dbo.RunningSPs(sp_name, conf_value) values (N'GetNewCodes', 0)
end
The problem is here: [INCIC].[dbo].[ReadCodeBuffer], and the error message is:
Error: 50000
Unrecoverable error in procedure GetNewCodes: 916: The server principal "sa" is not able to access the database "INCIC" under the current security context.
I have followed this tutorial to implement Service, queue and activation stored procedure.
How can I fix this problem?
Read Call a procedure in another database from an activated procedure.
The problem is that activated procedures are run under an EXECUTE AS USER context and as such are subject to database impersonation restrictions (they are sandboxed within the database). Is all explained in Extending Database Impersonation by Using EXECUTE AS.
The solution is to sign your activated procedure and create an user derived from the signing certificate in the target database, and grant this derived user the required permissions. The first link shows a full example.
Remus Rusanu's answer is clearly definitive and correct. (Anyone dealing with Broker Services knows his expertise and invaluable blog.)
I just wanted to document my experience, since google will direct to this question when searching on "The server principal "sa" is not able to access the database..."
In my case, I was call another database from within an activated procedure, but invoking a sql statement directly, not a stored procedure.
Originally, the cross-database call worked fine without a signed certificate and the use of impersonation. Then, after a small syntax change, it started returning the above error message.
Here is what worked without the need for a signed certificate:
IF EXISTS (SELECT name FROM sys.databases WHERE name = N'MyOtherDb')
and here is what provoked the security exception:
IF DB_ID(N'MyOtherDb') IS NOT NULL
Imagine I have a procedure in database A on SQL Server 2008R2+.
USE A
GO
CREATE PROCEDURE dbo.My_Test
AS
BEGIN
PRINT ORIGINAL_DB_NAME()
PRINT DB_NAME()
END
GO
Now I call that procedure while in database B.
USE B
GO
EXEC A.dbo.My_Test
It will return this:
A
A
ORIGINAL_DB_NAME() gives you the default database from your login if there is one. In this case it's A but it could be anything, it's not the most recent/current/super-context database.
Now imagine in My_Test you want to do a RESTORE DATABASE B. This works if you called it while you were in master or A, but fails with an error if you were in B (because that database is in use).
Msg 3102, Level 16, State 1, Line 2
RESTORE cannot process database 'B' because it is in use by this session. It is recommended that the master database be used when performing this operation.
I am planning to catch and handle that exception, but I found the problem interesting as there doesn't appear to be a way to "discover" that you were in B ahead of time. And you can't do a USE master in the procedure to avoid the problem either (use statements aren't valid in procedures).
Is it possible to find that you were in database B ahead of time?
1/ Send DB name as parameter to your stored proc
or
2/ To make SP run in the context of current connection you need create your SP on master database and make it a system object.
USE MASTER
GO
-- name should start with "sp_"
CREATE PROCEDURE dbo.sp_My_Test
#OriginalDB SYSNAME
AS
BEGIN
PRINT ORIGINAL_DB_NAME()
PRINT DB_NAME()
END
GO
EXEC sp_ms_marksystemobject 'sp_My_Test'
GO
USE Test2
GO
EXEC master..sp_My_Test #OriginalDB = DB_NAME