'보미아빠, 석이'에 해당되는 글 529건
- 2012.12.29 windows 설치 usb 만들기
- 2012.12.28 Remote Access Server 1
- 2012.12.18 자동 통계 업데이트에 인한 recompile 유무 테스트
- 2012.12.08 Discontinued Database Engine Functionality in SQL Server 2012
- 2012.12.03 ad-hoc query plan guide 2
- 2012.12.02 adventureworks
- 2012.11.23 15 sec....
- 2012.11.08 view push predicate
- 2012.10.28 paging.....
- 2012.10.18 순서 지켜서 나오게 하기
Remote Access Server
Sath, The PPP adapter is created by Dial-Up Networking. Un-installing
Dial-Up Networking (Control Panel | Add/Remove Programs | Windows
Setup | Communications) will remove it. It will also prevent the
computer from making a dial-up Internet connection.
이라고 합니다.
http://www.windowskb.com/Uwe/Forum.aspx/windows-me-networking/1557/PPP-Adapter
사보텐더님 고맙습니다
망할 잡놈해커가
레지스트리키에 해당 스냅인을 disable 시켜 놔서
라우터및원격억세스 관리자가 실행이 안되었었습니다
다시말해
HKEY_CURRENT_USER\Software\Policies\Microsoft\MMC 항목에
restrict_run 항목을 지웠더니 .. 관리자가 실행이 되었고
서비스를 멈출 수 있었습니다
ARP 스푸핑 공격을 당한거라고 하는데
어디서 어떻게 고쳐야 할지 .. 아직 감감이네요 ..
고맙습니다
ARP 스푸핑(ARP spoofing)은 근거리 통신망(LAN) 하에서 주소 결정 프로토콜(ARP) 메시지를 이용하여 상대방의 데이터 패킷을 중간에서 가로채는 맨 인 더 미들 공격 기법이다.
로컬 영역 네트워크에서 각 장비의 IP 주소와 MAC 주소간의 대응은 ARP 프로토콜을 통해 이루어진다. 이때 공격자가 의도적으로 특정 IP 주소와 자신의 MAC 주소로 대응하는 ARP 메시지를 발송하면, 그 메시지를 받은 장비는 IP 주소를 공격자 MAC 주소로 인식하게 되고, 해당 IP 주소로 보낼 패킷을 공격자로 전송하게 된다. 이 때 공격자는 그 패킷을 원하는 대로 변조한 다음 원래 목적지 MAC 주소로 발송하는 공격을 할 수도 있다.
흔히 사용되는 공격 방식은 게이트웨이 IP를 스푸핑하는 것으로, 이 경우 외부로 전송되는 모든 패킷이 공격자에 의해 가로채거나 변조될 수 있다. 또는, 두 노드에 각각 ARP 스푸핑을 하여 두 장비의 통신을 중간에서 조작하는 기법도 자주 사용된다.
[2007년 6월] ARP Spoofing의 습격, 그리고 방어 전략
이번에 학교 네트워크에 전체적으로 ARP 스푸핑 웜이 돌아다니고 있어 네트워크가 불안 불안하다..
역시나 연구실에서도 몇개가 발견되고..
웹브라우저를 사용하면 이상한 사이트로 거쳐서 돌아온다거나 하는 사태가 발생해서..
여러가지로 알아보고 대응책을 강구해보았다.
0. 기본적인 도구 사용법
- 관리자로 접속해서 도구 사용
XP의 경우 관리자 계정으로 로그인하면 되며,
비스타의 경우 오른쪽 클릭해서 "관리자 권한으로 실행"을 누르면
관리자 모드로 실행된다.
- 윈도우 커맨드창 (명령 프롬프트) 실행하기
여기에 'cmd'라고 입력하고 [엔터]하면 커맨드창이 실행된다.
비스타의 경우 그냥 [엔터]가 아니라
[Ctrl]+[Shift]+[Enter]로 실행해야 관리자 권한으로 실행된다.
1. ARP 스푸핑 공격의 개요
- ARP 테이블이란
하지만 실제 네트워크 장비들이 데이터를 전송할 때는
IP보다는 MAC주소(하드웨어 주소)를 사용해서 전송하게 되는데
ARP 테이블에 적혀진 IP와 MAC 주소의 매핑을 보고
해당 IP주소에 해당하는 MAC 주소를 가진 장비로 데이터를 전달한다.
- ARP 정보 교환의 약점
그래서 악의를 가진 장비가 잘못된 ARP 정보를 네트워크에
지속적으로 뿌리면 네트워크는 일대 혼란이 오게 된다.
- 공격 방법
감염된 PC는 모든 IP패킷을 자신에게 오게하기 위해 게이트웨이로 가장하고
게이트웨이의 IP주소에 자신의 MAC주소를 매핑시켜 전파한다.
그렇게 되면 모든 패킷이 감염된 PC를 거치게 되므로
감염된 PC는 모든 데이터를 변조하고 웜바이러스를 삽입하여
네트워크 전체에 빠르게 확산된다.
웜바이러스의 제작자가 목적한 대로 여러 기능이 추가되면
다른 스파이웨어를 자동으로 다운로드 받게한다던지
방화벽이나 백신을 무력화하는 등 매우 다양한 피해를 입힌다.
2. 감염 사실 확인
- 기본적인 증상 확인
서핑을 하다보면 실제로 자신의 페이지가 아님에도 특정 주소로 먼저 접속을 시도한다.
(예를 들어 분명 주소창에 news.naver.com을 쳤는데 twwen.com을 먼저 거친다던가 하는..)
허가하지 않은 이상한 파일을 자동으로 다운로드하려고 하거나
1.js, 2.js하는 식의 이상한 이름의 스크립트를 실행한다.
- 커맨드창에서 arp 명령 사용해서 확인하기
Microsoft Windows [Version 5.2.3790] (C) Copyright 1985-2003 Microsoft Corp. C:\Documents and Settings\Administrator>arp -a Interface: 155.230.90.97 --- 0x2 Internet Address Physical Address Type 155.230.88.1 00-04-96-15-36-90 dynamic 155.230.88.5 00-04-96-20-b9-b9 dynamic C:\Documents and Settings\Administrator>
엔트리 종류(Type)는 "동적(dynamic)"과 "정적(static)" 두가지 종류를 가진다.
그 중 동적 엔트리는 끊임없이 오고가는 arp 패킷에 의해 결정되는 것으로
ARP 스푸핑 공격을 당하게 되면 이 동적 엔트리가 사용되는 것보다 많이 화면에 표시되게 된다.
또 정상적인 장비와 감염된 장비가 서로 MAC 주소를 갱신하려 노력하기 때문에
MAC 주소가 주기적으로 끊임없이 바뀌는 현상도 발생한다.
즉, 위의 'arp -a' 명령을 지속적으로 실행시켜보면 감염되거나 공격 당한 PC에서
게이트웨이의 MAC 주소가 수시로 바뀜을 확인할 수 있다.
C:\Documents and Settings\Administrator>arp -a Interface: 155.230.90.97 --- 0x2 Internet Address Physical Address Type 155.230.88.1 00-04-96-15-36-90 dynamic 155.230.88.5 00-04-96-20-b9-b9 dynamic C:\Documents and Settings\Administrator>arp -a Interface: 155.230.90.97 --- 0x2 Internet Address Physical Address Type 155.230.88.1 00-04-96-15-36-90 dynamic 155.230.88.5 00-16-17-6a-f1-5a dynamic C:\Documents and Settings\Administrator>
- XArp, arpwatch 사용해서 확인하기
(리눅스에서는 gui arpwatch가 비슷한 역할을 한다.)
- 네트워크 장비상에서 확인하기
정상적인 상황에서 2개 이상의 IP를 가진 MAC 주소가 있다면 반드시 의심해 봐야하며
게이트웨이나 중요서버들의 IP를 뿌리는 MAC 주소가 있다면 해당 포트의 연결을 끊고
해당 MAC 주소를 가진 장비를 확인해야한다.
3. 개인 PC상에서 공격에 대응하기
- 대응하는 방법
이를 위해서 우선 게이트웨이의 정상적인 MAC 주소를 알아야 한다.
네트워크 관리자를 통해서 확인하는 방법이 가장 정확한 방법이며
감염되지 않은 PC에서 'arp -a' 명령을 통해서 확인하는 방법도 괜찮다.
물론 MAC 주소 고정은 어디까지나 임시 방편으로..
확산 속도를 늦추는 것 뿐이고 결국 감염원을 찾아서 바이러스를 제거해야 한다.
- Windows XP
'arp -d [IP 주소]' (전체를 지우고 싶다면 그냥 'arp -d'만..)
실행하고 나면 ARP 테이블은 완전히 지워지게 된다. (확인은 'arp -a'로..)
이후에 게이트웨이의 엔트리를 정적으로 지정해서 수동으로 입력한다.
'arp -s {IP 주소} {MAC 주소}'
실행하고 나면 시간이 지나도 바뀌지 않는 ARP 엔트리를 확인할 수 있다.
아래는 실행한 예이다.
Microsoft Windows [Version 5.2.3790] (C) Copyright 1985-2003 Microsoft Corp. C:\Documents and Settings\Administrator>arp -a Interface: 155.230.90.97 --- 0x2 Internet Address Physical Address Type 155.230.88.5 00-16-17-6a-f1-5a dynamic 155.230.90.140 00-1a-4d-48-91-dc dynamic C:\Documents and Settings\Administrator>arp -s 155.230.88.5 00-04-96-20-b9-b9 C:\Documents and Settings\Administrator>arp -a Interface: 155.230.90.97 --- 0x2 Internet Address Physical Address Type 155.230.88.5 00-04-96-20-b9-b9 static 155.230.90.140 00-1a-4d-48-91-dc dynamic C:\Documents and Settings\Administrator>arp -d 155.230.90.140 C:\Documents and Settings\Administrator>arp -a Interface: 155.230.90.97 --- 0x2 Internet Address Physical Address Type 155.230.88.5 00-04-96-20-b9-b9 static C:\Documents and Settings\Administrator>
- Windows Vista
'arp -s' 명령이 동작하지 않기 때문에 다른 방법을 사용해야 한다.
아래는 넷쉘을 사용해서 MAC 주소를 고정시키는 방법을 사용한 예이다.
예에서 MAC 주소를 고정시키는 명령은 다음과 같다.
'set neighbors "{영역 이름}" "{IP 주소}" "{MAC 주소}"''
그외에 예에서 실행한 명령들을 확인해서 유사하게 사용하면 된다.
Microsoft Windows [Version 6.0.6000] (C) Copyright 1985-2005 Microsoft Corp. C:\Users\Administrator>netsh -c "interface ipv4" netsh interface ipv4>show addresses "로컬 영역 연결" 인터페이스에 대한 구성 DHCP 사용: No IP 주소: 155.230.90.140 서브넷 접두사: 155.230.88.0/22(마스크 255.255.252.0) 기본 게이트웨이: 155.230.88.5 게이트웨이 메트릭: 256 인터페이스 메트릭: 20 "Loopback Pseudo-Interface 1" 인터페이스에 대한 구성 DHCP 사용: No IP 주소: 127.0.0.1 서브넷 접두사: 127.0.0.0/8(마스크 255.0.0.0) 인터페이스 메트릭: 50 netsh interface ipv4>set neighbors "로컬 영역 연결" "155.230.88.5" "00-04-96-20-b9-b9" netsh interface ipv4>exit C:\Users\Administrator>arp -a 인터페이스: 155.230.90.140 --- 0x8 인터넷 주소 물리적 주소 유형 155.230.88.5 00-04-96-20-b9-b9 정적 155.230.90.88 00-a0-b0-1b-5f-db 동적 C:\Users\Administrator>
- Ubuntu Linux
'arp -a'
'sudo arp -d ...'
'sudo arp -s ...'
4. 네트워크 관리자의 대응
- 감염원 파악
ARP 스푸핑이 처음 발견되면 빠른 시간 내에 MAC 주소를 모니터링해서 주 감염원을 찾고
선을 뽑아서 네트워크에서 제거한다.
2장의 마지막 절에 언급된 대로 터널이나 기타 복잡한 기능의 장비가 아닌 일반 장비(PC 등)가
하나의 MAC 주소에 두개 이상의 IP를 가지고 있다면 해당 MAC 주소의 장비를 검사해야한다.
(게이트웨이의 IP를 가지려는 MAC 주소의 장비를 필히 확인한다.)
- 모든 하위 네트워크 상의 PC를 치료
주 감염 PC는 자료를 백업한 후 새로 설치하도록 권고한다.
- 장비 재정비
자동 혹은 수동으로 장비들을 리셋한다.
대규모 네트워크의 경우 복구에 훨씬 많은 시간이 소요된다.
게이트웨이 장비에 아예 수동으로 ARP 테이블을 구성하고 IP나 장비가 바뀔 때마다
직접 등록하면서 네트워크가 안정되기를 기다리는 것도 좋은 방법이다.
A. 여담
- 비상사태는 비상사태고 퇴근시간은 퇴근시간?
이전엔 잘되던 IP 변경도 일일히 신청해서 바꿔야하고.. 몇몇 PC는 인터넷이 안되고..
특히 우리 연구실은 공교롭게도 같은 날 네트워크 장비를 기가비트로 교체하는 작업을 하고
대다수의 PC들이 IP를 바꾼 상태라 그야말로 패닉이었다.
그래서 바쁘게 전산센터와 연락하면서 이런 저런 일들을 겪었는데..
나: '그래서 빨리 변경해주셨으면 합니다.'
콜센터: "지금이 비상사태라 당장 해드리기 어렵습니다."
(직접 찾아가서 자료를 전달한 후)
나: '그럼 언제쯤 가능 할까요?'
콜센터: "곧 퇴근 시간이라 내일해야할 것같은데요.."
나: '응?'
비상사태라면서 정시퇴근을?!?!
해커는 다음 파라미터를 수정해 또다른 포트로 서비스 가능하다
Thank you for your post here.
Did you install the security updateKB951746 (MS08-037) on the Windows Server 2003 server?
If yes, please check how it works if you create portreservation for PPTP.
1. On the server, locate the following registry keys in Regiedit:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters\ReservedPorts
2. Add 1723-1723 to reserve the port needed forPPTP
3. Reboot the server to check how it works.
Some Services May Fail to Start or May Not Work Properly After Installing MS08-037 (951746 and 951748)
If you have any questions or concerns, please do not hesitate to let me know.
자동 통계 업데이트에 인한 recompile 유무 테스트
http://cafe.naver.com/sqlmvp/3487
dbcc freeproccache
go
drop table tblx
go
create table tblx
(idx int
,a int
,b int)
go
create clustered index nc_tblx_01 on tblx (idx)
create nonclustered index nc_tblx_02 on tblx (a)
--dbcc show_statistics (tblx, nc_tblx_01)
--dbcc show_statistics (tblx, nc_tblx_02)
if OBJECT_ID ('usp_cluster') is null
exec ('create proc usp_cluster as select 1 ')
go
if OBJECT_ID ('usp_covered') is null
exec ('create proc usp_covered as select 1 ')
go
if OBJECT_ID ('usp_none_covered') is null
exec ('create proc usp_none_covered as select 1 ')
go
alter proc usp_cluster
@a int
as
select * from tblx where idx = @a
go
alter proc usp_covered
@a int
as
select a from tblx where a = @a
go
alter proc usp_none_covered
@a int
as
select * from tblx where a = @a
go
set nocount on
declare @init int = 0
while (@init < 10000) begin
insert into tblx values (1,@init,1)
set @init += 1
end
go
exec dbo.usp_cluster 1
go 10
exec dbo.usp_covered 1
go 10
exec dbo.usp_none_covered 1
go 10
select refcounts, usecounts, plan_generation_num, text, *
from sys.dm_exec_query_stats a
cross apply sys.dm_exec_sql_text (a.sql_handle) b
left outer join sys.dm_exec_cached_plans c
on a.plan_handle = c.plan_handle
where text like '%tblx%'
and text not like '%sys.dm%'
and text not like '%insert%'
go
set nocount on
declare @init int = 0
while (@init < 10000) begin
insert into tblx values (1,@init,1)
set @init += 1
end
go
-- 여기서 부터 프로파일링 잡을것
exec dbo.usp_cluster 1
go
exec dbo.usp_covered 1
go
exec dbo.usp_none_covered 1
go
select refcounts, usecounts, plan_generation_num, text, *
from sys.dm_exec_query_stats a
cross apply sys.dm_exec_sql_text (a.sql_handle) b
left outer join sys.dm_exec_cached_plans c
on a.plan_handle = c.plan_handle
where text like '%tblx%'
and text not like '%sys.dm%'
and text not like '%insert%'
go
프로시저가 플랜을 재컴파일이 일어남을 잡는 방법 및 재컴파일시 결과
clustered index 만 재컴파일 일어나도록 했고, 나머지는 돌리지 않은 결과 입니다.
Discontinued Database Engine Functionality in SQL Server 2012
ad-hoc query plan guide
아래 테스트는 ISV 에서 만든 어플리케이션과 같이 특수한 환경에서 쿼리를 직접 고치지 못할 때 유용한 쿼리 힌트 방법이다. 특히 AD-HOC 쿼리로 들어오는 쿼리를 OPTIMIZE FOR 등의 방법으로 쿼리 힌트를 구현하고자 할 때 매우 유용한 방법이다. 스터디에서 OPTIMIZE FOR 의 설명은 많이 했으므로, 해당 설명은 생략 하도록 하겠다.
아래 내용은 편집해서 출판 할 것이므로, 그전에 오류나 추가할 내용 가이드 주세요~ ^.^
12년 12월 11일 update
USE MASTER
GO
IF EXISTS (SELECT * FROM SYS.DATABASES WHERE NAME ='DB_PLAN_GUIDE_TEST')
BEGIN
ALTER DATABASE DB_PLAN_GUIDE_TEST SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE DB_PLAN_GUIDE_TEST
END
GO
CREATE DATABASE DB_PLAN_GUIDE_TEST
CONTAINMENT = NONE
ON PRIMARY
( NAME = N'DB_PLAN_GUIDE_TEST', FILENAME = N'L:\MSSQL\DB_PLAN_GUIDE_TEST.MDF' , SIZE = 100MB , MAXSIZE = UNLIMITED, FILEGROWTH = 100MB )
LOG ON
( NAME = N'DB_PLAN_GUIDE_TEST_LOG', FILENAME = N'L:\MSSQL\DB_PLAN_GUIDE_TEST_LOG.LDF' , SIZE = 100MB , MAXSIZE = 2048GB , FILEGROWTH = 100MB )
GO
USE DB_PLAN_GUIDE_TEST
GO
-- 기본 상태로 만든다.
ALTER DATABASE DB_PLAN_GUIDE_TEST SET PARAMETERIZATION SIMPLE
GO
SELECT * FROM SYS.plan_guides
GO
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST1')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST1
GO
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST2')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST2
GO
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST3')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST3
GO
IF OBJECT_ID ('TBLX') IS NOT NULL
DROP TABLE TBLX
GO
WITH TEMP AS
(
SELECT TOP 1000000
CAST(ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS INT) IDX
, 'PNAME_'+RIGHT(REPLICATE('0',7)+CAST(CAST(ABS(CHECKSUM(NEWID())) % 1000 AS INT) AS VARCHAR(10)),7)+CHAR(ASCII('A') + ABS(CHECKSUM(NEWID()))%10) PRODUCTNAME
FROM SYS.OBJECTS A1
CROSS JOIN SYS.OBJECTS A2
CROSS JOIN SYS.OBJECTS A3
CROSS JOIN SYS.OBJECTS A4
CROSS JOIN SYS.OBJECTS A5
)
SELECT IDX, PRODUCTNAME, CAST(SUBSTRING(PRODUCTNAME,7,7) AS INT) PRODUCTID
, CAST(ABS(CHECKSUM(NEWID())) % 100 AS INT) SHOPID
, CAST(ABS(CHECKSUM(NEWID())) % 1000000 AS INT) PRICE
, DATEADD(MINUTE, CAST(ABS(CHECKSUM(NEWID())) % 20000 AS INT) * -1, GETDATE()) INSERTTIME
INTO TBLX
FROM TEMP
GO
--SELECT COUNT(*) FROM TBLX
--SELECT TOP 10 * FROM TBLX
CREATE UNIQUE CLUSTERED INDEX UCL_TBLX ON TBLX (IDX)
CREATE NONCLUSTERED INDEX NC_TBLX_02 ON TBLX (PRODUCTID)
GO
DBCC FREEPROCCACHE
GO
SET STATISTICS PROFILE ON
GO
-- SIMPLE PARAMETERIZATION을 방지하기 위해 JOIN 함
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 0
-- SERIAL PLAN
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 1000000
-- PARALLEL PLAN
SET STATISTICS PROFILE OFF
-- 이제 항상 PARALLEISM 으로 동작 시키기 위해서 PLAN 을 고정시켜 보고자 한다.
-- QUERY A START
DECLARE @STMT NVARCHAR(MAX);
DECLARE @PARAMS NVARCHAR(MAX);
EXEC SP_GET_QUERY_TEMPLATE N'
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 1000000
',
@STMT OUTPUT,
@PARAMS OUTPUT;
-- 확인을 위한 쿼리
SELECT @STMT, @PARAMS
EXEC SP_CREATE_PLAN_GUIDE
@NAME = N'PLAN_GUIDE_TEST1',
@STMT = @STMT,
@TYPE = N'SQL',
@MODULE_OR_BATCH = NULL,
@PARAMS = @PARAMS,
@HINTS = N'OPTION (OPTIMIZE FOR (@0 = 1000000))'
-- QUERY A END
SELECT * FROM SYS.plan_guides WHERE NAME = 'PLAN_GUIDE_TEST1'
-- 이렇게 해서는 PLAN_GUIDE 가 동작하지 않는다.
-- 왜냐구? 파라미터된 계획과 그렇지 않는 계획은 다른것 이기 때문이다.
-- 아래 쿼리가 PARALLEL 로 돌아야 하는데 SERIAL 로 동작한다.
-- 속성에도 PLAN GUIDE 가 쓰지 않은 것으로 뜬다.
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 1
-- SERIAL PLAN 으로 동작함
-- 옵셥 확인
SELECT CASE WHEN IS_PARAMETERIZATION_FORCED = 0 THEN 'DISABLED' ELSE 'ENABLED' END
, COMPATIBILITY_LEVEL
FROM SYS.DATABASES
WHERE NAME ='DB_PLAN_GUIDE_TEST'
GO
-- 강제 PARAMETERIZATION 활성화
ALTER DATABASE DB_PLAN_GUIDE_TEST SET PARAMETERIZATION FORCED
GO
-- 이제는 플랜가이드가 동작한다.
-- 기타 체크
-- 호환성 레벨 확인법
--exec sp_helpdb paging
--exec sp_dbcmptlevel paging
-- 호환성 레벨 변경방법
--ALTER DATABASE DB_PLAN_GUIDE_TEST
--SET COMPATIBILITY_LEVEL = 110
-- 호환성 레벨이 90 이상이고 IS_PARAMETERIZATION_FORCED 이 1 이면 가능하다.
DBCC FREEPROCCACHE
GO
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 0
-- 병렬실행 계획이 동작한다면 정상이다.
-- 이 하나의 쿼리를 파라미터화 해서 쓰기 위해 전체 데이터베이스 옵션을 변경 할 수는 없다.
-- 그래서 다시 SIMPLE 로 돌린다.
ALTER DATABASE DB_PLAN_GUIDE_TEST SET PARAMETERIZATION SIMPLE
GO
DBCC FREEPROCCACHE
GO
-- 해당 쿼리만 PARAMETERIZATION FORCED 옵션을 만들어주는 플랜가이드를 하나 더 만든다.
-- 기발한 생각이죠?
DECLARE @STMT NVARCHAR(MAX);
DECLARE @PARAMS NVARCHAR(MAX);
EXEC SP_GET_QUERY_TEMPLATE N'
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 1000000
',
@STMT OUTPUT,
@PARAMS OUTPUT;
EXEC sp_create_plan_guide
N'PLAN_GUIDE_TEST2',
@stmt,
N'TEMPLATE',
NULL,
@params,
N'OPTION(PARAMETERIZATION FORCED)';
GO
-- 아래와 같이 두개의 플랜가이드를 확인한다.
SELECT * FROM SYS.plan_guides
GO
plan_guide_id name create_date modify_date is_disabled query_text scope_type scope_type_desc scope_object_id scope_batch parameters hints
65537 PLAN_GUIDE_TEST1 2012-12-08 21:27:21.103 2012-12-08 21:27:21.103 0 select SUM ( A . SHOPID ) from TBLX A join TBLX B on A . IDX = B . IDX where B . PRODUCTID < @0 2 SQL NULL select SUM ( A . SHOPID ) from TBLX A join TBLX B on A . IDX = B . IDX where B . PRODUCTID < @0 @0 int OPTION (OPTIMIZE FOR (@0 = 1000000))
65539 PLAN_GUIDE_TEST2 2012-12-08 23:31:13.657 2012-12-08 23:31:13.657 0 select SUM ( A . SHOPID ) from TBLX A join TBLX B on A . IDX = B . IDX where B . PRODUCTID < @0 3 TEMPLATE NULL select SUM ( A . SHOPID ) from TBLX A join TBLX B on A . IDX = B . IDX where B . PRODUCTID < @0 @0 int OPTION(PARAMETERIZATION FORCED)
-- 결국 위 두개의 PLAN_GUIDE 조합으로 PARAMETERIZATION 이 SIMPLE 인데도 위 쿼리는 파라미터화 해서 동작 시킬 수 있다.
-- 아래 쿼리를 동작시키면 병렬로 동작 할 것이다.
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 0
-- 일반적인 테스트 끝
-- 원상복귀
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST1')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST1
GO
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST2')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST2
GO
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST3')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST3
GO
-- 동작의 검증
--PROFILER 에서 PERFORMANCE > PLAN GUIDE SUCCESSFUL , PLAN GUIDE UNSUCCESSFUL 로 확인 가능하다.
--GRAPHIC 실행계획의 마지막 이터레이터에서 속성을 확인하면 플랜가이드가 사용된 것을 확인 할 수 있다.
-- 테스트 시나리오 2
-- 아래는 SP_CREATE_PLAN_GUIDE_FROM_HANDLE 으로 플랜을 고정시키는 방법이다.
-- 플랜에 올라온 실행계획을 고정시키는 방법이다.
-- 그러나, 이 방법으로 MEMORY GRANT 는 고정시킬 수 없고, 역시 PARAMETERIZATION FORCE 는 개별로 ENABLE 시키던지
-- 데이터베이스 전역으로 ENABLE 시켜야 한다.
-- 그러므로 OPTIMIZER FOR 를 이용해 실행시 PARAMETER 를 인식시켜 충분한 메모리를 할당 받을 수
-- 있도록 하는것이 최선의 방법이다.
-- 어떤 방법이든, 방법의 한계를 잘 이해하는 것이 중요하다.
-- 역시 SIMPLE인 상태에서 파라미터화 해서 쓰기위해서 다음 플랜가이드를 먼저 만든다.
DECLARE @STMT NVARCHAR(MAX);
DECLARE @PARAMS NVARCHAR(MAX);
EXEC SP_GET_QUERY_TEMPLATE N'
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 1000000
',
@STMT OUTPUT,
@PARAMS OUTPUT;
EXEC sp_create_plan_guide
N'PLAN_GUIDE_TEST2',
@stmt,
N'TEMPLATE',
NULL,
@params,
N'OPTION(PARAMETERIZATION FORCED)';
GO
DBCC FREEPROCCACHE
GO
-- 병렬쿼리로 동작 할꺼다.
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 10000000
GO
-- 이 플랜을 받아서 고정시켜보자
-- 그런데 이번에는 좀 스마트하게 SP_CREATE_PLAN_GUIDE_FROM_HANDLE 에서 받아볼까?
DECLARE @PLAN_HANDLE VARBINARY(1000)
SELECT @PLAN_HANDLE = PLAN_HANDLE
--SELECT *
FROM SYS.DM_EXEC_QUERY_STATS QS
CROSS APPLY SYS.DM_EXEC_SQL_TEXT(QS.SQL_HANDLE) SQT
WHERE TEXT LIKE '%SUM%SHOPID%PRODUCTID%'
AND TEXT NOT LIKE '%SYS.DM_EXEC%'
SELECT @PLAN_HANDLE
EXEC SP_CREATE_PLAN_GUIDE_FROM_HANDLE 'PLAN_GUIDE_TEST3', @PLAN_HANDLE=@PLAN_HANDLE
GO
DBCC FREEPROCCACHE
GO
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 0
GO
--MemoryGrant="25736"
DBCC FREEPROCCACHE
GO
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 10000000
GO
--MemoryGrant="205960"
-- 위와 같이 플랜은 같게 플랜 가이드를 잘 쓰지만 메모리 GRANT 는 크게 차이가 나고 처음에 0을 넣고
-- 돌린 쿼리가 돌게되면 두번째 쿼리 변수가 크면 이번에는 HASH WARNING 이 발생하면서 TEMPDB SPILL 이 발생한다.
-- 동작 속도도 엄청나게 떨어지게 된다.
-- 테스트를 완료 했으면, 플랜 가이드를 지운다.
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST1')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST1
GO
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST2')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST2
GO
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST3')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST3
GO
-- 2번 테스트 끝
-- 강제 파라미터화를 위해 PLAN_GUIDE_TEST2 는 지우지 않았다.
-- 테이블 힌트를 통해서 플랜가이드를 주었을때 해당 인덱스가 삭제되면
-- 어떻게 될까? (동작하지 않는다.)
-- CREATE NONCLUSTERED INDEX NC_TBLX_02 ON TBLX (PRODUCTID)
GO
DECLARE @STMT NVARCHAR(MAX);
DECLARE @PARAMS NVARCHAR(MAX);
EXEC SP_GET_QUERY_TEMPLATE N'
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 1000000
',
@STMT OUTPUT,
@PARAMS OUTPUT;
EXEC sp_create_plan_guide
N'PLAN_GUIDE_TEST2',
@stmt,
N'TEMPLATE',
NULL,
@params,
N'OPTION(PARAMETERIZATION FORCED)';
GO
DECLARE @STMT NVARCHAR(MAX);
DECLARE @PARAMS NVARCHAR(MAX);
EXEC SP_GET_QUERY_TEMPLATE N'
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 1000000
',
@STMT OUTPUT,
@PARAMS OUTPUT;
-- 확인을 위한 쿼리
SELECT @STMT, @PARAMS
EXEC SP_CREATE_PLAN_GUIDE
@NAME = N'PLAN_GUIDE_TEST1',
@STMT = @STMT,
@TYPE = N'SQL',
@MODULE_OR_BATCH = NULL,
@PARAMS = @PARAMS,
@HINTS = N'OPTION (TABLE HINT (B, INDEX(NC_TBLX_02)))'
-- QUERY A END
-- 인덱스 힌트를 주고 해당 인덱스를 삭제할 경우
-- 플랜 가이드에 명시적으로 TABLE HINT 로 인덱스가 명시되어 있는데 없어지면 실행이 되지 않는다.
DROP INDEX TBLX.NC_TBLX_02
GO
DBCC FREEPROCCACHE
GO
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 1000000
GO
-- 동작하지 않는다.
SELECT *
FROM SYS.PLAN_GUIDES A
CROSS APPLY SYS.FN_VALIDATE_PLAN_GUIDE (A.PLAN_GUIDE_ID)
GO
-- 인덱스 만들면 잘 동작한다.
CREATE NONCLUSTERED INDEX NC_TBLX_02 ON TBLX (PRODUCTID)
GO
-- Index 'NC_TBLX_02' on table 'TBLX' (specified in the FROM clause) does not exist.
-- 라고 기록되며 실행되지 않는다.
-- 동일한 인덱스 이름을 만들어 주면 잘 동작한다.
-- 오류가 생기는 시점은 인덱스가 삭제되면 바로 발생한다.
-- 해당 테스트는 JOIN TBLX B WITH(INDEX(NC_TBLX_02)) 로 힌트를 주더라도 같다.
-- USE PLAN 에서 사용된 인덱스가 없어져도 무시하고 실행되는 경우는
-- 명시적 인덱스 힌트를 사용하지 않고 만들어진 쿼리플랜을 USE PLAN 으로 사용할 때
-- 인덱스를 지우면 FN_VALIDATE_PLAN_GUIDE 에 오류가 남고 실행은 된다.
-- 실행은 되나 플랜의 최 하단 이터레이터에서 속성을 확인하면, 플랜 가이드를 쓰지 않은것을 볼 수 있다.
-- 다른 테스트를 위해 플랜 가이드 제거
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST1
GO
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST2
GO
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST3
GO
DBCC FREEPROCCACHE
GO
--DROP INDEX TBLX.NC_TBLX_02
--GO
DECLARE @STMT NVARCHAR(MAX);
DECLARE @PARAMS NVARCHAR(MAX);
EXEC SP_GET_QUERY_TEMPLATE N'
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 1000000
',
@STMT OUTPUT,
@PARAMS OUTPUT;
EXEC sp_create_plan_guide
N'PLAN_GUIDE_TEST2',
@stmt,
N'TEMPLATE',
NULL,
@params,
N'OPTION(PARAMETERIZATION FORCED)';
GO
CREATE NONCLUSTERED INDEX NC_TBLX_02 ON TBLX (PRODUCTID)
GO
-- 아래 쿼리는 TBLX NC_TBLX_02 를 쓰는 실행계획이다.
-- 플랜 확인 할 것
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 1000000
GO
-- USE PLAN 을 이용해 PLAN GUIDE 생성하기
-- QUERY B START
DECLARE @SQL_XML_PLAN NVARCHAR(MAX)
DECLARE @STMT NVARCHAR(MAX);
DECLARE @PARAMS NVARCHAR(MAX);
SELECT @SQL_XML_PLAN =
CONVERT(NVARCHAR(MAX),SQP.QUERY_PLAN)
FROM SYS.DM_EXEC_QUERY_STATS QS
CROSS APPLY SYS.DM_EXEC_SQL_TEXT(QS.SQL_HANDLE) SQT
CROSS APPLY SYS.DM_EXEC_QUERY_PLAN(QS.PLAN_HANDLE) SQP
WHERE TEXT LIKE '%SUM%SHOPID%PRODUCTID%'
AND TEXT NOT LIKE '%SYS.DM_EXEC%'
EXEC SP_GET_QUERY_TEMPLATE N'
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 1000000
',
@STMT OUTPUT,
@PARAMS OUTPUT;
-- 확인을 위한 쿼리
SELECT @STMT, @PARAMS
SELECT @SQL_XML_PLAN
SET @SQL_XML_PLAN = 'OPTION(USE PLAN '''+@SQL_XML_PLAN+''')'
EXEC SP_CREATE_PLAN_GUIDE @NAME =N'PLAN_GUIDE_TEST3'
, @STMT = @STMT
, @TYPE = N'SQL'
, @MODULE_OR_BATCH = NULL
, @PARAMS = @PARAMS
, @HINTS = @SQL_XML_PLAN
-- QUERY B END
DROP INDEX TBLX.NC_TBLX_02
GO
-- 플랜 가이드를 무시하고, 클러스터 인덱스를 이용해 잘 실행한다.
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 0
-- 인덱스 삭제등의 문제가 있으면 아래 쿼리로 확인 가능하다.
SELECT *
FROM SYS.PLAN_GUIDES A
CROSS APPLY SYS.FN_VALIDATE_PLAN_GUIDE (A.PLAN_GUIDE_ID)
GO
-- 인덱스 만들어주면 다시 잘 동작한다.
CREATE NONCLUSTERED INDEX NC_TBLX_02 ON TBLX (PRODUCTID)
GO
-- 이번에는 플랜가이드를 사용할 것이다.
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 0
--Index 'DB_PLAN_GUIDE_TEST.dbo.TBLX.NC_TBLX_02', specified in the USE PLAN hint, does not exist. Specify an existing index, or create an index with the specified name.
-- 플랜가이드 제거
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST1')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST1
GO
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST2')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST2
GO
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST3')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST3
GO
-- 원본 쿼리의 힌트제거 방법
-- 아래와 같이 ISV 개발자가 임으로 HASH JOIN 을 기술해 개발해 놓았으나
-- 내 데이터베이스에서는 해당 힌트가 동작하지 않았으면 한다. (무시하길 원한다.)
-- 단, 아래 방법은 FROM 절에 JOIN 힌트로 기술되어 있거나, 다른 JOIN 방법을 명시하는 것은 불가능 하다.
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 0
OPTION (HASH JOIN)
-- 1. SIMPLE PARAMETERIZATION 데이터베이스에서 테스트 하기 위해 개별 쿼리에
-- PARAMETERIZATION 을 활성화 한다.
DECLARE @STMT NVARCHAR(MAX);
DECLARE @PARAMS NVARCHAR(MAX);
EXEC SP_GET_QUERY_TEMPLATE N'
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 0
OPTION (HASH JOIN)
',
@STMT OUTPUT,
@PARAMS OUTPUT;
EXEC sp_create_plan_guide
N'PLAN_GUIDE_TEST2',
@stmt,
N'TEMPLATE',
NULL,
@params,
N'OPTION(PARAMETERIZATION FORCED)';
GO
-- 2. HINT 절에 NULL 을 기술해 힌트를 제거한다.
DECLARE @STMT NVARCHAR(MAX);
DECLARE @PARAMS NVARCHAR(MAX);
EXEC SP_GET_QUERY_TEMPLATE N'
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 0
OPTION (HASH JOIN)
',
@STMT OUTPUT,
@PARAMS OUTPUT;
-- 확인을 위한 쿼리
SELECT @STMT, @PARAMS
EXEC SP_CREATE_PLAN_GUIDE
@NAME = N'PLAN_GUIDE_TEST1',
@STMT = @STMT,
@TYPE = N'SQL',
@MODULE_OR_BATCH = NULL,
@PARAMS = @PARAMS,
@HINTS = NULL
-- QUERY A END
GO
DBCC FREEPROCCACHE
GO
SELECT SUM(A.SHOPID)
FROM TBLX A
JOIN TBLX B
ON A.IDX = B.IDX
WHERE B.PRODUCTID < 0
OPTION (HASH JOIN)
-- 플랜가이드 제거
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST1')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST1
GO
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST2')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST2
GO
IF EXISTS (SELECT * FROM SYS.plan_guides WHERE NAME ='PLAN_GUIDE_TEST3')
EXEC SP_CONTROL_PLAN_GUIDE N'DROP', PLAN_GUIDE_TEST3
GO
http://msdn.microsoft.com/en-us/library/ms191275(v=sql.100).aspx
http://msdn.microsoft.com/en-us/library/ms190417(v=sql.100).aspx
adventureworks
15 sec....
paging.....
IF DB_ID('PAGING') IS NOT NULL
BEGIN
USE MASTER
ALTER DATABASE PAGING SET SINGLE_USER WITH ROLLBACK IMMEDIATE
DROP DATABASE PAGING
END
CREATE DATABASE [PAGING] ON PRIMARY
( NAME = N'PAGING', FILENAME = N'L:\MSSQL\PAGING.MDF' , SIZE = 20MB , MAXSIZE = UNLIMITED, FILEGROWTH = 100MB )
LOG ON
( NAME = N'PAGING_LOG', FILENAME = N'L:\MSSQL\PAGING_LOG.LDF' , SIZE = 20MB , MAXSIZE = UNLIMITED, FILEGROWTH = 100MB)
GO
USE PAGING
GO
WITH TEMP AS
(
SELECT TOP 1000000
CAST(ROW_NUMBER() OVER (ORDER BY (SELECT 1)) AS INT) IDX
, 'PNAME_'+RIGHT(REPLICATE('0',7)+CAST(CAST(ABS(CHECKSUM(NEWID())) % 1000 AS INT) AS VARCHAR(10)),7)+CHAR(ASCII('A') + ABS(CHECKSUM(NEWID()))%10) PRODUCTNAME
FROM SYS.OBJECTS A1
CROSS JOIN SYS.OBJECTS A2
CROSS JOIN SYS.OBJECTS A3
CROSS JOIN SYS.OBJECTS A4
CROSS JOIN SYS.OBJECTS A5
)
SELECT IDX, PRODUCTNAME, CAST(SUBSTRING(PRODUCTNAME,7,7) AS INT) PRODUCTID
, CAST(ABS(CHECKSUM(NEWID())) % 100 AS INT) SHOPID
, CAST(ABS(CHECKSUM(NEWID())) % 1000000 AS INT) PRICE
, DATEADD(MINUTE, CAST(ABS(CHECKSUM(NEWID())) % 20000 AS INT) * -1, GETDATE()) INSERTTIME
INTO TBLX
FROM TEMP
GO
CREATE UNIQUE CLUSTERED INDEX UCL_TBLX ON TBLX (IDX)
CREATE UNIQUE NONCLUSTERED INDEX NC_TBLX_01 ON TBLX (INSERTTIME, IDX)
DECLARE @ROWS_PER_PAGE INT
,@JUMP_PAGE INT
,@INSERTTIME DATETIME
,@IDX INT
SELECT @ROWS_PER_PAGE = 2
,@JUMP_PAGE = 100
,@INSERTTIME = '2012-10-15 07:10:09.557'
,@IDX = 73413
--SELECT TOP 100 * FROM TBLX ORDER BY INSERTTIME, IDX
SELECT *
FROM (SELECT *
FROM (SELECT TOP (@ROWS_PER_PAGE + (@ROWS_PER_PAGE * @JUMP_PAGE)) T.IDX, T.INSERTTIME
, ROW_NUMBER() OVER (ORDER BY T.INSERTTIME, T.IDX) RNUM
FROM DBO.TBLX T
WHERE T.INSERTTIME >= @INSERTTIME
AND ((T.INSERTTIME = @INSERTTIME AND T.IDX > @IDX) OR (T.INSERTTIME > @INSERTTIME))
ORDER BY T.INSERTTIME, IDX) V
WHERE RNUM >= @ROWS_PER_PAGE * @JUMP_PAGE + 1
AND RNUM <= @ROWS_PER_PAGE * @JUMP_PAGE + @ROWS_PER_PAGE) A
JOIN TBLX B
ON A.IDX = B.IDX
ORDER BY B.INSERTTIME, B.IDX;
순서 지켜서 나오게 하기
IF OBJECT_ID ('TBLX') IS NOT NULL
DROP TABLE TBLX
GO
CREATE TABLE TBLX
(IDX INT
,C1 INT
,C2 INT)
GO
INSERT INTO TBLX VALUES (1,1,1), (2,2,2), (3,3,3),(4,4,4)
GO
SELECT *
FROM (SELECT * FROM (VALUES (1), (3)) V (INSERTORDER)) A
CROSS APPLY (SELECT * FROM TBLX WHERE IDX = INSERTORDER) B
SELECT *
FROM (SELECT * FROM (VALUES (1), (3)) V (INSERTORDER)) A
INNER LOOP JOIN TBLX B
ON B.IDX = INSERTORDER
IF OBJECT_ID('DBO.USP_A') IS NULL
EXEC ('CREATE PROC USP_A AS SELECT 1 ')
GO
ALTER PROC DBO.USP_A
(@A1 INT
,@A2 INT
,@A3 INT)
AS
SELECT *
FROM (SELECT * FROM (VALUES (@A1), (@A2), (@A3)) V (INSERTORDER)) A
INNER LOOP JOIN TBLX B
ON B.IDX = INSERTORDER
GO
EXEC DBO.USP_A 1,2,3
EXEC DBO.USP_A 3,1,2