OS : Microsoft Windows 2000 Server / Advanced Server
SQL : Microsoft SQL Server 2000 Standard / Enterprise Edition
그동안 Oralce을 많이 이용했었습니다.
저의 개념으로 이해가 안되는 부분이 있어서 질문드립니다.
현재 제가 아는 System이 MS SQL Server 2000 Standard를 사용하고 있는데
원인은 정확히 파악할 수 없으나 수시로(LOCK)이 걸린다고 합니다.
Strored Procedure는 몇개 이용하고 있습니다. 이 SP는 스케쥴러(작업)에 의해 Call되어지고 있구요...
해결책으로 애플리케이션이든 SP든 모든 Query에 (NOLOCK)을 추가해 주었더군요.
DB를 알고있는 관계자들이
"MS SQL Server는 기본적으로 Select문을 날리면 Lock이 걸린다"
"Default다"
라고들 합니다.
책을 찾아보고 인터넷을 뒤져봐도 이런내용은 없는것 같은디...
맞는 이야기인지,
맞다면 왜 단순 Select문을 던지는데 기본적으로 Lock을 잡아야 하는지 답변 좀 부탁 합니다.
----------------------------------------
대학원에서 간단히 테이블 한 두개 만들어 놓고 VB로 연결해서 DB Programming을 해 본게 전부라 오라클에 대한 지식이 거의 없는터라 오라클에서 왜 그렇게 동작하는지 정말 그런지 뭐 그런건 잘 모른채 SQL Server의 격리 수준에 대한 내용이 생각나서 답변해 주기위해 좀 더 검색해 보며 내가 알고 있는 내용을 정리하던 중, 오라클과 SQL Server의 병행제어 방법이 다르다는 것을 알게 되었다.
혹 관심이 있는 분들이나 나 자신을 위해 간단히 정리해 보려 합니다.
처음 위 질문을 보고, 'SQL Server의 기본 isolation level이 read committed니가 그런거겠지... 그럼 Oracle은 그게 아닌가?'라고 생각하고 Oracle쪽을 찾다가 다음과 같은 글을 보았습니다.
'SQL Server와 Oracle의 격리와 병행제어에는 차이가 있습니다....'
그래서 당연히 default isolation level이 달라서 나타나는 차이라고 생각하고 답글을 올려버렸습니다. --;; (사실 상당히 주의하고 답글을 올리는 편임에도 불구하고 실수를 했죠).
그리고 이 내용에 흥미가 생겨 블로그에 정리하다가 그게 아니라는걸 알게되었습니다.
다시 검색하다가 아래의 내용을 보았습니다.
'The default isolation level for Oracle is read committed'
쩝 --;; 그럼 isolation level이 같은데 왜 그럴까?? 차이는 병행제어 알고리즘의 차이입니다.
데이터의 일관성과 데이터의 동시성
여러 가지 정의가 있겠지만, Oracle 9i의 교재에서는 다음과 같이 정의하고 있습니다.
Data concurrency means that many users can access data at the same time.
Data consistency means that each user sees a consistent view of the data, including visible changes made by the user's own transactions and transactions of other users.
동시성이란 다수의 사용자가 동시에 데이터에 접근할 수 있어야 한다는 의미이고, 일관성이란, 각각의 사용자가 자신의 트랜잭션이나 다른 사람의 트랜잭션에 의해 변경된 내용을 포함하여 일관된 값을 본다라는 의미라고 합니다.
다수의 사용자가 데이터베이스에 동시에 접근하게 될 경우, 각각의 사용자가 다른 데이터에 접근을 한다면 문제 없겠지만, 같은 데이터에 접근을 한다면 잘못된 데이터의 변경을 막기 위해선 반드시 어떠한 제어를 해 주어야 합니다.
그것이 병행 제어(concurrency control)입니다.
대학원 수업시간에 배운 내용 중 기억 나는 부분만 소개하자면...
병행 제어 알고리즘은 다음과 같은 분류의 알고리즘이 있습니다.
* 낙관적 병행제어 알고리즘 : 다른 사용자가 동시에 같은 데이터에 접근할 경우가 적다고 보고 구현한 알고리즘
* 비관적 병행제어 알고리즘 : 낙관적과는 반대로 다른 사용자가 동시에 같은 데이터에 접근할 경우가 많다고 보고 구현한 알고리즘
위의 내용은 머리속에 남은 조그마한 지식의 일부분이고..
대학원때 배웠던 교제 중, Distributed Systems Concepts and Design 이라는 책에 나온 내용을 소개하자면,
병행제어 알고리즘에는 다음과 같은 것 들이 있습니다.
1. Locking (비관적 병행제어 알고리즘)
2. optimistic concurrency control (낙관적 병행제어 알고리즘)
3. timestamp ordering (비관적 병행제어 알고리즘)
각 알고리즘에 대한 소개는 skip하고 ^^''
SQL Server에서 사용하는 병행제어 알고리즘은 Locking입니다.
그러나 Oracle에서 사용하는 병행제어 알고리즘은 일반적인 Locking과는 조금 다릅니다. Oracle에서는 Multiversion Concurrency Control이라고 말하는 방법을 사용하는데, 중요한 것은 SQL Server와 다른 알고리즘을 사용 한다는 것입니다.
아래의 내용은 첨부된 문서의 내용 중 일부입니다.
오라클의 잠금 정책 요약입니다.
* 오라클은 변경의 경우에만 테이블의 행단위로 잠금을 한다. 블록이나테이블 수준으로잠금을 확대하지는 않는다.
* 오라클은 단지 데이터를 읽기 위해서 잠금을 하지 않는다. 즉, 간단한 읽기에 의한 데이터 행들에 잠금을 두지는 않는다.
* 데이터 기록기(writer)는 데이터 판독기(reader)를 방해하지 않는다. 즉, 읽기는 쓰기에 의해서 방해받지 않는다. 이는 기본적으로 읽기가 쓰기에 의해서 차단되는 거의 모든 다른 데이터베이스들과 구별된다.
* 데이터 기록기는 다른 데이터 기록기가 먼저 접근하고자 하는 데이터에 대해서 잠금을 했을 때에만 차단된다.
* 데이터 판독기는 데이터 기록기에 의해서 전혀 차단되지 않는다.
그러므로, SQL Server에서는 read T가 write T와 충돌이 발생하여, 둘 중 하나의 T는 blocking이 걸리게 되지만, Oracle에서는 read T와 write T가 서로 충돌하지 않습니다.
Historically, the concurrency control model in SQL Server at the server level has been pessimistic and based on locking. While locking is still the best concurrency control choice for most applications, it can introduce significant blocking problems for a small set of applications.
The biggest problem arises when locking causes the writer-block-reader or reader-block-writer problem. If a transaction changes a row, it holds exclusive locks on the changed data. The default behavior in SQL Server is that no other transactions can read the row until the writer commits. Alternatively, SQL Server supports ‘read uncommitted isolation’, which can be requested by the application either by setting the isolation level for the connection or by specifying the NOLOCK table hint. This nonlocking scan should always be carefully considered prior to use, because it is not guaranteed to return transactional consistent results.
간단히 정리해 보면...
"SQL Server는 병행제어 모델로 비관적 알고리즘인 locking을 사용해 왔다. locking은 아직까지 대부분의 application에서 병행제어 방법으로는 가장 좋은 방법이긴 하지만 작은 규모의 application에서 중요한 blocking 문제를 야기시킬 수 있다.
가장 큰 문제점은 locking이 writer-block-reader 혹은 reader-block-writer의 원인이 되는 것이다. 기본적으로 SQL Server에서는 writer가 commit하기 전까지 어떤 트랜잭션도 row를 읽을 수 없다. 대신 application에서 isolotion level을 지정하거나 NOLOCK 테이블 힌트를 이용해 요청할 수 있는 'read uncommitted isolation'을 지원한다. 일관성 있는 결과를 보장하지 않으므로 이런 방법으로 locking하지 않은 채 사용하는 것은 주의해야 한다."
대략 정리했는데, 큰 틀은 맞겠지만 소소하게 틀린 부분은 많을테니 잘못된 부분 지적해 주시면 감사하고 이해하고 보시거나 ㅋㅋ
나머지는 시간날 때 날 잡고...
그리고 Oracle 최신 버전에는 혹시 다른 변화가 있는지도 같이 한번...
Oracle 10i의 메뉴얼 중 Concurrency Control 관련 부분 메뉴얼은 여기
파일은
특정 조건을 AND로 추가해서 Query할 수도 있고,
특정 조건을 Filter로 추가해서 Query할 수도 있다.
총 검색되는 갯수는 같지만, 간단하지만 보이지 않는 차이를 정리해 보면...
* AND로 검색을 하면 score에 해당 내용이 반영이 되어 정확도로 정렬할 경우 해당 내용이 반영된다.
* AND로 검색을 하면 별로도 추가로 검색을 하는 것이므로, 부하가 있는 쿼리의 경우는 AND조건 검색보다 Filter로 걸러내는 것이 속도가 빠르다. (검색된 것들 중에서 filtering을 하는 것이니 당연히 빠르겠죠? 전체 인덱스의 크기가 크면 클 수록 and query를 이용한 것 보다 훨씬 빠를테니까요.)
조금 더 자세히 몇 가지를 설명하자면...
Lucene in Action과 현재 licene 2.0 버젼이 filter 부분이 차이가 좀 있는데
DateFilter라는건 없어졌다.
그리고 내 경우는 기존에 사용하던 Filter도 있어서...
추가로 한가지 Filter를 추가한 case라서..
주로 MS-SQL에서 작업을 하다, mysql에서 처음으로 stored procedure(이하 sp)를 만들면서, 5분이면 될 것을 수십분은 걸린 것 같다 --;;
에러가 정확한 위치를 알려주질 않는다.
아래의 정리한 내용은 MS-SQL에 익숙하고 mysql을 사용해 보지 않은 분이 보면 무자게 도움이 될 것 같다.
- IF문을 쓸 때 END IF문이 있어야 하며, END IF문 뒤에는 ;를 붙여야 한다.
ex> IF (test IS NULL) THEN
SELSET 'ok';
END IF;
- auto increment값을 얻어올 때는 LAST_INSERT_ID()을 이용한다.
ex> SET serverId = LAST_INSERT_ID();
- CREATE PROCEDURE문 전에 delimeter문을 이용해 delimeter를 ';'가 아닌 다른 걸로 변경하고 뒤에 다시 변경할 것
ex> delimiter // # delimeter를 //로 변경
CREATE PROCEDURE xxx
.....
.....
END;
// # 실제 한 command가 종료됨을 의미
CREATE PROCEDURE getServerId(ip VARCHAR(15), OUT serverId tinyint)
BEGIN
SELECT id INTO serverId FROM tServer WHERE serverIp = ip;
END;
//
delimiter ;
- mysql에서는 ;가 command의 종료를 의미하는데 stored procedure의 경우 여러 query의 조합이라 중간에 ;가 들어갈 수 있는데, 그러면 문법 오류로 간주되므로 delimiter를 임시로 다른 걸로 바꾼 후 sp를 만들고 다시 예전 delimiter로 변경하는 것임.
lucene을 이용해 검색을 구현할 때, 검색어에 lucene에서 사용하는 특수기호를 넣으면, 잘못 반영 될 소지가 있어, lucene에서 사용하는 특수 기호들에 대해서는 escape(기호 앞에 \를 추가)을 해 줘야 한다.
정규식을 이용해 간단하게 구현했는데, Lucene in Action 책을 보니 sandbox에 있는 js를 참고하라고 해서 홈페이지에 가 보니 없었다.
결국 알고보니 api에 추가가 되었다.
QueryParse.escape();
나 처럼 뻘짓하는 분 없기를...
but....
http://lucene.apache.org/java/docs/queryparsersyntax.html
여기 가 보면 lucene에서 사용하는 특수기호들에 대한 설명이 나와 있다.
'+ - && || ! ( ) { } [ ] ^ " ~ * ? : \'
그런데 lucene의 escape 코드를 보면 '&&'와 '||'은 빠져 있다. (2007년 6월 19일 발표된 2.2.0버젼에서 bugfix 되었습니다.)
직관적으로 이해 하겠지만 search value에 &&는 and로 ||는 or로 변환되어 처리되므로,
실제 &&나 ||를 검색해야 하는 경우 코드를 따다 수정해서 사용해야 할 듯.
실제 lucene 코드
public static String escape(String s) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// NOTE: keep this in sync with _ESCAPED_CHAR below!
if (c == '\\' || c == '+' || c == '-' || c == '!' || c == '(' || c == ')' || c == ':'
|| c == '^' || c == '[' || c == ']' || c == '\"' || c == '{' || c == '}' || c == '~'
|| c == '*' || c == '?') {
sb.append('\\');
}
sb.append(c);
}
return sb.toString();
}
현재 아래의 언어들에 대한 reference를 제공한다. C, C++, CSS, HTML, HTML DOM, Java, JavaScript, MySQL, Perl, PHP, and Ruby
더 좋은 점은 실제 doc문서를 바로 보여준다. ajax를 이용해 해당 사이트의 내용을 바로 보여주는 듯...
프로그래밍 할 때 이용할 수 있는 자동 완성 기능이 구현되어 정확히 모르고 찾을 때도 매우 유용할 듯.
* 오늘 날짜 구하기
- new Date();
- Calendar now = Calendar.getInstance();
now.getTime();
* 어제 날짜 구하기
Calendar now = Calendar.getInstance();
now.add(Calendar.DAY_OF_MONTH, -1);
now.getTime();
* 날짜 포맷을 원하는 형태로 바꾸기
Calendar now = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.format(now.getTime()); // 결과는 String
* 지 맘데로의 날짜를 담은 문자열을 다른 형태로 convert
String dateStr = "200707211541122"; SimpleDateFormat oldFormat = new SimpleDateFormat("yyyyMMddHHmmss"); SimpleDateFormat newFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
1. 객체를 이용하여 저장하는 방법
Transaction tx = session.beginTransaction();
Cafe cafe = new Cafe();
cafe = new Cafe();
cafe.setId(Id);
cafe.setName(name);
session.save(cafe);
tx.commit();
2. key 값을 이용해 값이 존재하는지 확인한 후 저장하는 방법
Session session = HibernateUtil.getCurrentSession();
if (existCafe == null) {
Transaction tx = session.beginTransaction();
Cafe cafe = new Cafe();
cafe = new Cafe();
cafe.setId(id);
cafe.setName(name);
session.save(cafe);
tx.commit();
System.out.println("inserted");
}
else{
System.out.println("already exist");
}
HibernateUtil.closeSession();
3. key가 아닌 값을 이용해 존재하는지 확인한 후 저장하는 방법
* createQuery 안의 문장은 SQL Query가 아니라 Hibernate에서 사용하는 HQL(Hibernate Query Language) 이다.
* 자세한 문법은 http://www.hibernate.org/hib_docs/v3/reference/ko/html/queryhql.html
Keyword k = (Keyword) session.createQuery(
"select k from Keyword as k where keyword = ?").setString(0,
keyword).uniqueResult();
if (k == null) {
Transaction tx = session.beginTransaction();
Keyword kw = new Keyword();
kw.setKeyword(keyword);
4. 자동 증가값 받기
* 걍 강제 casting
int id = (Integer) session.save(kw);
5. DB의 identity 컬럼 관련 XML 설정
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="net.daum.cafe.search.weblog.entity">
<class name="Keyword" table="tKeyword">
<id name="id" column="id">
<generator class="identity" />
</id>
<property name="keyword" type="string" column="keyword" />
</class>
</hibernate-mapping>
6. 복합키 관련 XML 설정
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="net.daum.cafe.search.weblog.entity">
<class name="DTBC" table="tableD">
<composite-id>
<key-property name="Id" type="int" column="Id" />
<key-property name="keywordId" type="int" column="keywordId" />
</composite-id>
<property name="count" type="int" column="count" />
</class>
</hibernate-mapping>
* 참고 : 복합키를 구현하려면 Serializable을 반드시 implements해 주어야 한다. 그렇지 않으면 'composite-id class must implement Serializable' 예외가 발생한다.
일반적으로 'x = x + i' 라고 쓰는게 길어 쉽고 간편하게 쓰기 위해 'x += i'를 많이 사용한다.
그러나 두 문장은 엄밀히 말해 동일하지 않다.
두 문장 모두 assignment expression이다.
Java language specification을 보면, compound assignment(+=, -=, *= ...)는
E1 op E2는 E1 = (T) ((E1) op (E2)) 와 동일하다.
여기서 T는 E1의 Type을 의미 한다. 즉, 연산의 결과 값을 E1의 type으로 casting을 하는 것이다.
실제로
short x = 0;
int i = 123456;
x += i; // 알아서 casting이 되므로 컴파일 에러가 나지 않는다.
x = x + i; // 컴파일 에러 발생
일반적으로 다른 type끼리를 위와 같이 사용하는 경우는 없겠지만, 혹시 그럴 경우 주의해야 한다.
둘 다 간략하게 리뷰를 해 본 결과 실제 사용하는 사람 입장에서 좀 편한 것을 택하게 되었으니,
그 놈이 Javolution이었다.
Javolution은 단순히 XML관련 라이브러리만이 아니고, 기타 다른 여러 개의 라이브러리 묶음 중 XML관련 기능도 제공하는 것이었다.
이 넘을 이용해 잘 사용하고 있는데... 문제가 발생했다. 서버가 뻗는 --;;
여러 모로 원인을 찾다가... 아무래도 Javolution에 문제가 있는게 아닐까 하는 생각에 만든 사람한테 미친척 오늘 새벽에 메일을 보내봤다. thread dump뜬 내용과 간단한 상황설명과 관련 소스 조금...
그런데... 아침에 와보니 답변이 와 있었다.~~
답장 메일
Hi Danny,
Thanks to your backtrace I found the problem.
It is due to the PersistentReference access during CharacterData creation.
I would recommend not using PersitentReference for the buffer length
(mistake from my part!). For example:
/**
* Holds the internal buffer.
*/
private char[] _buffer = new char[0];
...
/**
* Returns the character data for the specified character sequence
* (convenience method).
*
* @param csq the character sequence being wrapped.
* @return the corresponding character data instance.
*/
public static CharacterData valueOf(CharSequence csq) {
final int length = csq.length();
CharacterData cd = (CharacterData) FACTORY.object();
if (length > cd._buffer.length) { // Resizes.
cd._buffer = new char[length];
// LENGTH.setMinimum(new Integer(length)); Useless
}
cd._chars = cd._buffer;
cd._offset = 0;
cd._length = length;
for (int i=0; i < length;) {
cd._chars[i] = csq.charAt(i++);
}
return cd;
}
It should be noted that in version 4.0, CharacterData is replaced by StAX
writeCData new capability (4.0 breaks backward compatibility, but not
functionality). Here is an example of writing/reading CDATA with the
upcoming version:
XMLFormat<Foo> XML = new XMLFormat<Foo>(Foo.class) {
public void format(Foo foo, XMLElement xml) {
// Direct use of XMLStreamWriter (StAX).
xml.getWriter().writeCData(foo.text);
}
public Foo parse(XMLElement xml) {
Foo foo = xml.object();
// Direct use of XMLStreamReader (StAX).
foo.text = xml.getReader().getText().toString();
return foo;
}
};
The release date for 4.0 is still unknown (I am very busy lately) but
should not extend past mid-August.
Thanks for your patience.
Jean-Marie.
한편으로 무지 반가웠지만 한편으로는 좀 원망스럽기도 했다. (그 간의 맘 고생을 생각하면...)
엉망으로 보낼 질문 메일의 원문은 공개하기 꺼려져 공개하지 않는다...
뭐... 위 메일대로 수정해서 테스트 중인데, 정말 해결될 지는 잘 모르겠지만...
잘 되길 바라며..
혹시 Javolution을 사용하고 있는 분 특히 CDATA 출력을 위해 CharacterData를 사용하시는 분 참고하시기 바랍니다.