MySQL JDBC StreamResult의 통신 원리에 대한 간략한 논의

不言
풀어 주다: 2018-10-19 16:33:51
앞으로
2744명이 탐색했습니다.

이 기사는 MySQL JDBC StreamResult 통신 원리에 대해 간략하게 설명합니다. 이는 특정 참조 가치가 있으므로 도움이 될 수 있습니다.

MySQL JDBC를 사용하여 대용량(예: 1GB 이상)의 데이터를 읽어보신 분들은 읽을 때 메모리가 Java 힙을 오버플로할 가능성이 높다는 점을 아셔야 하며, 저희 솔루션은 해결책은 state.setFetchSize(Integer.MIN_VALUE)이며 커서가 읽기 전용이고 앞으로 스크롤되는지 확인합니다(커서의 기본값). 또한 유형을 com.mysql.jdbc.StatementImpl로 캐스팅한 다음 호출할 수도 있습니다. 내부 메소드: 활성화StreamingResults()는 데이터 메모리를 읽을 때 중단되지 않으며 두 가지로 얻는 효과는 동일합니다. 물론 useCursorFetch도 사용할 수 있지만 이 방법의 테스트 결과 성능은 StreamResult보다 훨씬 느립니다. 이유는 무엇입니까? 이 기사에서는 일반적인 원칙을 설명합니다.

MySQL JDBC의 내부 처리 코드를 3개의 클래스로 나누어서 완성한다고 이전 글이나 책에서 소개한 적이 있지만, 데이터베이스에 대해 자세히 살펴본 적은 없고 JDBC가 어떻게 서로 통신을 하는지 ? 한동안 저는 이것이 서버 측 동작이거나 클라이언트와 서버 간의 협력 동작이라고 생각했지만 오늘은 이것이 무엇인지에 대해 이야기하겠습니다.

[간단한 통신 먼저 검토]:

JDBC와 데이터베이스 간의 통신은 Socket을 통해 완료되므로 다음과 같은 작업을 수행할 수 있습니다. SocketServer의 공급자이므로 SocketServer가 데이터를 반환할 때(SQL 결과 집합 반환과 유사) 프로세스는 다음과 같습니다. 서버 프로그램 데이터(데이터베이스) -> 커널 소켓 버퍼 -> 클라이언트 프로그램(JDBC가 위치한 JVM 메모리)

지금까지 IT 업계에서 누구나 한 번쯤 본 JDBC는 MySQL JDBC, SQL Server JDBC, PG JDBC, Oracle JDBC 입니다. NoSQL 클라이언트(Redis 클라이언트, MongoDB 클라이언트, Memcached)의 경우에도 데이터 반환은 기본적으로 동일한 논리를 따릅니다.

MySQL JDBC StreamResult의 통신 원리에 대한 간략한 논의

[기본적으로 MySQL JDBC를 사용하여 데이터를 직접 읽을 때 왜 멈추나요? 】

(1) MySQL Server에서 시작된 SQL 결과 세트는 모두 OutputStream을 통해 데이터를 출력합니다. 즉, 로컬 Kennel에 해당하는 소켓 버퍼에 데이터를 씁니다. (메모리 사본은 이 기사의 초점이 아닙니다).

(2) Kennel의 버퍼에 데이터가 있으면 TCP 링크(JDBC에서 적극적으로 시작된 소켓 링크)를 통해 데이터를 다시 JDBC로 보냅니다. 당신이 있는 기계에 들어가면 먼저 개집(Kennel) 영역에 들어가고 버퍼(Buffer) 영역에도 들어가게 됩니다.

(3) JDBC가 SQL 작업을 시작한 후 Java 코드는 inputStream.read() 작업을 차단하고 버퍼에 데이터가 있으면 깨어납니다. 버퍼는 JDBC 측의 메모리 복사본인 Java 메모리로 읽혀집니다.

(4) 다음으로 MySQL JDBC는 계속해서 버퍼 데이터를 Java 메모리로 읽어오고, MySQL Server는 계속해서 데이터를 보냅니다. 데이터가 완전히 조합되기 전에는 클라이언트가 시작한 SQL 작업이 응답하지 않습니다. 즉, MySQL 서버가 아직 응답하지 않은 것처럼 느껴집니다. 실제로 데이터가 로컬로 전송되었으며 JDBC가 응답하지 않았습니다. 아직 실행 메소드가 호출된 위치로 결과 세트를 반환하지 않았지만 버퍼에서 데이터를 계속 읽습니다.

(5) 핵심은 이 바보가 집에 저장되어 있든 없든 데이터를 읽은 후 테이블 전체의 내용을 Java 메모리로 읽어들이고 다음 단계인 FULL GC부터 시작한다는 것입니다. 메모리 오버플로입니다.

[문제를 해결하려면 JDBC 매개변수에 useCursorFetch=true를 설정하세요.]

FetchSize 설정과 결합된 이 솔루션은 실제로 문제를 해결할 수 있습니다. 이 솔루션은 실제로 내가 원하는 데이터 양과 매번 원하는 데이터 양을 MySQL 서버에 알려줍니다. 통신 프로세스는 다음과 같습니다.

MySQL JDBC StreamResult의 통신 원리에 대한 간략한 논의#🎜🎜 #

이렇게 생활하면서 마트에 가서 필요한거 사서 필요한 만큼만 사요. 하지만 이런 종류의 상호작용은 지금의 온라인 쇼핑과는 달리 집에 앉아 있는 동안 집으로 물건을 배달받을 수 있으며, 걸어야(네트워크 연결)하므로 데이터에 1억 개의 데이터가 있는 경우 네트워크 시간 오버헤드가 필요합니다. , FetchSize를 1000으로 설정하면 왕복 통신이 100,000회가 되고, 동일한 컴퓨터실에서 네트워크 지연이 0.02ms이면 통신 100,000회에 2초가 추가되는데 이는 큰 문제가 아닙니다. 따라서 전산실 간 지연 시간이 2ms라면 200초 길어집니다(즉, 3분 20초). 중국 도시 간 지연 시간이 10~40ms라면 시간은 1000~4000초가 됩니다. .국가별로 200~300ms이면 어떨까요? 시간은 10시간 이상 길어집니다.

여기 계산에는 시스템 호출 수 증가, 스레드 대기 및 깨우기 컨텍스트 수 증가, 네트워크 패킷 재전송이 전체 성능에 미치는 영향이 포함되지 않았습니다. 이 해결책은 합리적으로 보이지만 성능은 실제로 그리 좋지 않습니다.

또한, MySQL은 클라이언트가 언제 데이터 소비를 완료했는지 알지 못하고 해당 테이블에 DML 쓰기 작업이 있을 수 있으므로 MySQL은 제거해야 하는 데이터를 저장할 임시 테이블 공간을 생성해야 합니다. 따라서 useCursorFetch를 활성화하여 큰 테이블을 읽으면 MySQL에서 몇 가지 현상이 나타납니다.

(1) IO 읽기 수가 많아 IOPS가 급증합니다. 일반 하드 디스크의 경우 비즈니스 쓰기가 발생할 수 있습니다. 이번에는 지터가 들어옵니다

(2) 디스크 공간이 급증하고 있습니다. 이 임시 공간은 원본 테이블보다 클 수 있습니다. 이 테이블이 전체 데이터베이스에서 큰 비중을 차지하면 데이터베이스 디스크가 가득 찰 수 있습니다. 검색이 완료된 후 또는 클라이언트가 Result.close()를 시작하면 MySQL은 이를 재활용합니다.

(3) CPU 성능에 따라 CPU와 메모리가 일정 비율로 증가합니다.

(4) 클라이언트 JDBC는 SQL 응답 데이터를 오랫동안 기다립니다. 이 시간 동안 서버는 데이터를 준비합니다. 이 대기는 매개 변수를 설정하지 않는 원래 JDBC 방법과 다릅니다. 내부 원칙상 대기 중인 것으로 나타났습니다. 전자는 네트워크 버퍼에서 데이터를 읽어왔고 비즈니스에 응답하지 않았습니다. 이제 MySQL 데이터베이스는 임시 데이터 공간을 준비 중이며 JDBC에 응답하지 않습니다.

【스트림 읽기 데이터】

우리는 첫 번째 방법이 Java를 중단시킬 수 있다는 것을 알고 있습니다. 두 번째 방법은 비효율적이며 MySQL 데이터베이스에 더 큰 영향을 미칩니다. 클라이언트 응답도 느려서 문제만 해결할 수 있습니다. 문제입니다. 이제 스트림 읽기 방법을 살펴보겠습니다.

앞서 언급한 바와 같이,statement.setFetchSize(Integer.MIN_VALUE) 또는 com.mysql.jdbc.StatementImpl.enableStreamingResults()를 사용하면 Stream이 실행을 시작하기 전에 FetchSize를 수동으로 설정할 수 없는 결과 세트를 읽을 수 있습니다. 커서가 FORWARD_ONLY인지 확인하세요.

이 방법은 놀라운 것 같습니다. 메모리가 더 이상 끊기지 않고, 응답도 더 빠르며, MySQL에 미치는 영향도 적습니다. 적어도 IOPS는 그렇게 크지 않을 것이며, 디스크 사용량도 사라질 것입니다. 과거에는 JDBC에서 별도의 코드만 보고 이것이 MySQL과 JDBC 사이의 또 다른 통신 프로토콜이라고 생각했습니다. 이것이 "클라이언트 동작"이라는 사실은 거의 알지 못했습니다. 네, 맞습니다. 클라이언트입니다. 행동.

enableStreamingResults()를 시작하면 서버와 거의 상호 작용하지 않습니다. 즉, 서버는 방법 1에 따라 데이터를 반환합니다. 그런 다음 서버는 데이터를 버퍼에 푸시하기 위해 최선을 다합니다. 클라이언트가 압력을 처리할 수 있는 사람은 어떻습니까?

JDBC에서는 스트림 결과 집합 처리를 활성화하면 모든 데이터를 한 번에 Java 메모리로 읽지 않습니다. 즉, 그림 1의 데이터는 한 번에 하나의 패키지를 읽습니다. 한 번(이 패키지는 Java의 byte[] 배열로 이해될 수 있음) 한 번에 이만큼 읽은 다음 데이터의 무결성을 보장하기 위해 아래쪽으로 계속 읽을지 여부를 확인합니다. 비즈니스 코드는 바이트를 기준으로 한 줄로 구문 분석되어 비즈니스 측에서 사용됩니다.

서버가 버퍼에 데이터를 푸시하기 시작하면 데이터가 클라이언트의 커널 버퍼도 가득 차게 됩니다. 양쪽의 버퍼가 가득 차면 서버의 버퍼가 TCP를 통해 데이터를 수신자에게 전달하려고 시도합니다. 이때 소비자의 버퍼도 가득 차 있기 때문에 보낸 사람의 스레드가 차단되어 상대방이 소비할 때까지 기다리게 됩니다. 상대방이 그 중 일부를 소비하면 데이터의 일부가 여기에 푸시될 수 있습니다. 연결은 JDBC 스트림 데이터가 소비되기 전에 버퍼 데이터가 가득 차면 데이터를 보내는 MySQL의 스레드가 차단되어 균형을 보장하는 것으로 보입니다. (이를 위해 Java의 소켓을 사용하여 시도해 볼 수 있습니다. 아래의 경우).

JDBC 클라이언트의 경우 데이터를 획득할 때마다 로컬 커널 버퍼에 커뮤니티의 택배 상자와 거리가 멀기 때문에 자연스럽게 RT는 매번 갈 때보다 훨씬 작습니다. , 이 프로세스는 준비된 데이터이므로 IO 차단 프로세스가 없습니다(MySQL 서버가 전달하는 데이터가 소비자가 데이터를 처리하는 것만큼 빠르지 않는 한 일반적으로 소비자만 비즈니스를 수행하지 않습니다). 데이터를 얻은 후 바로 포기합니다. 현재로서는 컴퓨터실, 지역, 국가에 관계없이 서버가 응답하기 시작하는 한 데이터가 지속적으로 발생합니다. 통과되었으며, 이 작업은 첫 번째 방법을 경험한 경우에도 필요합니다.

첫 번째 방법에 비해 JDBC를 사용하면 메모리 오버플로 없이 큰 테이블을 읽어도 응답 시간이 오래 걸리지만 이 방법은 데이터베이스에 미치는 영향이 상대적으로 적습니다. 방법 1. 대형, 데이터 전송 과정에서 해당 데이터 행이 잠깁니다(수정 방지). InnoDB를 사용하면 세그먼트 잠금이 수행되고 MyISAM을 사용하면 전체 테이블 잠금이 추가되어 비즈니스 차단이 발생할 수 있습니다.

[이론적으로는 의욕이 있는 한 더 나아갈 수 있다]

이론적으로는 이 방법이 더 좋지만 완벽주의 측면에서는 계속 논의할 수 있습니다. 게으른 사람들에게는 동기가 없습니다. 아래층 커뮤니티에 있는 택배박스를 픽업하러 갈 생각은 누군가가 집으로 가져와서 입에 넣어주고, 심지어 입까지 벌려줄 수 있을까 하는 생각이에요.

기술적으로 이것은 JDBC가 커널에서 Java로 메모리를 복사하는 데 시간이 걸리기 때문에 실제로 수행할 수 있습니다. 내집. 이용하고 싶을때 그냥 집에서 바로오면 시간이 절약되는거 아닌가요? 모든 실수는 실제로 저장됩니다. 하지만 문제는 누가 이를 보낼 것인가입니다.

이를 위해서는 프로그램에 스레드를 추가하고, 커널 데이터를 애플리케이션 메모리에 복사하고, 애플리케이션이 직접 사용할 수 있도록 데이터 행으로 구문 분석해야 하는데, 이것이 반드시 완벽할까요? 사실 중간에 조화의 문제가 있습니다. 예를 들어, 집에서 요리를 하고 싶은데 양념이 부족하다면 아래층에서 직접 구입할 수도 있지만 배달을 요청해야 합니다. 이때까지 다른 요리는 다 익었고 양념은 한 봉지만 남았으니, 집으로 향하기 전에 양념이 배달될 때까지 기다릴 수밖에 없습니다. 요리의 다음 단계. 따라서 이상적인 상황에서는 메모리 복사 시간을 많이 절약할 수 있지만 조정 잠금 오버헤드가 일부 증가합니다.

그렇다면 커널 버퍼에서 직접 데이터를 읽는 것이 가능한가요?

이론적으로는 가능합니다. 이 문제를 설명하기 전에 먼저 이 메모리 복사본 외에 무엇이 있는지 이해해 보겠습니다.

JDBC는 커널 버퍼의 데이터를 바이너리 형식으로 읽은 후 이를 특정 구조화된 데이터로 추가로 구문 분석합니다. 이때 ResultSet의 특정 행의 구조화된 데이터를 비즈니스 당사자에게 반환해야 하기 때문에 즉, RowData에서 생성된 데이터를 한 번 복사해야 하고 JDBC가 특정 객체 유형 데이터(예: byte [ ] 배열), 일부 시나리오 구현에서는 ResultSet 자체의 내용을 수정하기 위해 결과 집합을 통해 반환된 결과(byte[1] = 0xFF)에서 byte[]의 내용을 수정하는 것을 원하지 않습니다. 또 다른 메모리 사본을 만들 수도 있습니다. 비즈니스 코드를 사용하는 과정에서 문자열 맞춤법, 네트워크 출력 등도 있을 수 있으며, 이는 비즈니스 수준에서 피할 수 없는 것입니다. , 그것은 단순히 중요하지 않기 때문에 수행하지 않았습니다. , 프로그램 병목 현상이 발생하지 않는 한 전반적인 관점에서 거의 사소한 일이라고 생각합니다.

전체적으로 메모리 복사는 불가피합니다. 이번에는 시스템 수준 호출에 지나지 않으며 기술적으로 말하면 커널 상태에서 직접 데이터를 읽을 수 있습니다. 더 많은 원격 데이터를 전송할 수 있도록 버퍼의 데이터를 바이트 단위로 가져와야 합니다. 버퍼를 저장할 세 번째 위치가 없습니다. 그렇지 않으면 커널에서 애플리케이션으로 메모리 복사본으로 반환됩니다.

상대적으로 말하면 서버는 직접 IO를 통해 데이터를 최적화하고 직접 전송할 수 있습니다(그러나 이러한 방식의 데이터 프로토콜은 분명히 이론적인 데이터 저장 형식과 일치합니다). 프로토콜을 실제로 사용자 정의하고 직접 데이터를 보낼 수 있습니다. 커널 상태를 변환하려면 OS 수준 파일 시스템 프로토콜을 수정해야 합니다.


위 내용은 MySQL JDBC StreamResult의 통신 원리에 대한 간략한 논의의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

관련 라벨:
원천:segmentfault.com
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
최신 이슈
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿