클라이언트 <-> 서버 <-> DB 관계에서는 비동기 쿼리 호출응답을 사용하여야 하는데, 멀티쓰레드 환경에서 오류가 발생하였고, 이를 해결하여 정리해봤습니다.
📺 오류
- This MySqlConnection is already in use. 오류가 나오며 현재 이 Connection이 사용중이라고 나옵니다.
💬 오류 스크립트
using MySqlConnector;
using Server.Game;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
/// <summary>
/// MySQL 쿼리 매니저
/// </summary>
public class MySqlManager
{
private static MySqlConnection _Connection; //mySQL connection
private static string _SqlConnection = null; //SQL 접속 명령인자
static MySqlManager()
{
// 접속 명령인자 생성
StringBuilder builder = new StringBuilder();
...
_SqlConnection = builder.ToString();
Console.WriteLine($"MySQL 준비 완료 {0}", System.DateTime.Now);
}
private static void MySqlConnect()
{
try
{
_Connection = new MySqlConnection(_SqlConnection);
_Connection.Open();
if (_Connection.State != ConnectionState.Open)
{
throw new InvalidOperationException("Connection failed to open.");
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private static void MySqlDisconnect()
{
_Connection.Close();
}
public static MySqlErrorCode Command(string command)
{
MySqlConnect(); //접속
try
{
MySqlCommand dbcmd = new MySqlCommand(command, _Connection); //명령어를 커맨드에 입력
dbcmd.ExecuteNonQuery(); //명령어를 SQL에 보냄
return MySqlErrorCode.None;
}
catch (MySqlException e) //SQL 오류
{
Console.WriteLine(e);
return e.ErrorCode;
}
finally
{
MySqlDisconnect(); //접속해제
}
}
public static DataTableReader Select(string command)
{
MySqlConnect(); //접속
try
{
MySqlDataAdapter adapter = new MySqlDataAdapter(command, _Connection);
DataTable table = new DataTable(); //테이블 생성
adapter.Fill(table); //데이터 테이블 채우기
return table.CreateDataReader(); //성공적으로 select를 했다면, 데이터 리더를 생성
}
catch (MySqlException e) //SQL 오류 발생
{
Console.WriteLine(e);
return null;
}
finally
{
MySqlDisconnect(); //접속 해제
}
}
}
- 현재 이 스크립트는 싱글턴 방식을 사용합니다.
- 싱글턴 방식에서 하나의 Connector을 사용하며, 멀티쓰레드 환경에 대응하지 못한 상태입니다.
- 동시 다발적으로 Sql 쿼리가 발생하면 비동기로 각 쓰레드가 쿼리를 요청하는데, 동시에 하나의 Connector에 접근하여 발생한 오류로 생각됩니다.
📖 오류 해결 스크립트
- 쓰레드 로컬 스토리지를 이용하여 해결하였습니다.
using MySqlConnector;
using Server.Game;
using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
/// <summary>
/// MySQL 쿼리 매니저
/// </summary>
public class MySqlManager
{
private static ThreadLocal<MySqlConnection> _Connection = new ThreadLocal<MySqlConnection>(); // SQL Connection
private static string _SqlConnection = null; //SQL 접속 명령인자
static MySqlManager()
{
// 접속 명령인자 생성
StringBuilder builder = new StringBuilder();
...
_SqlConnection = builder.ToString();
Console.WriteLine($"MySQL 준비 완료 {0}", System.DateTime.Now);
}
private static void MySqlConnect()
{
try
{
_Connection.Value = new MySqlConnection(_SqlConnection);
_Connection.Value.Open();
if (_Connection.Value.State != ConnectionState.Open)
{
throw new InvalidOperationException("Connection failed to open.");
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
private static void MySqlDisconnect()
{
if (_Connection.Value != null && _Connection.Value.State != ConnectionState.Closed)
{
_Connection.Value.Close();
}
}
public static MySqlErrorCode Command(string command)
{
MySqlConnect(); // 접속
try
{
MySqlCommand dbcmd = new MySqlCommand(command, _Connection.Value); // 명령어를 커맨드에 입력
dbcmd.ExecuteNonQuery(); // 명령어를 SQL에 보냄
return MySqlErrorCode.None;
}
catch (MySqlException e) // SQL 오류
{
Console.WriteLine(e);
return e.ErrorCode;
}
finally
{
MySqlDisconnect(); // 접속 해제
}
}
public static DataTableReader Select(string command)
{
MySqlConnect(); // 접속
try
{
MySqlDataAdapter adapter = new MySqlDataAdapter(command, _Connection.Value);
DataTable table = new DataTable(); // 테이블 생성
adapter.Fill(table); // 데이터 테이블 채우기
return table.CreateDataReader(); // 성공적으로 select를 했다면, 데이터 리더를 생성
}
catch (MySqlException e) // SQL 오류 발생
{
Console.WriteLine(e);
return null;
}
finally
{
MySqlDisconnect(); // 접속 해제
}
}
}
private static ThreadLocal<MySqlConnection> _Connection = new ThreadLocal<MySqlConnection>(); // SQL Connection
- 각 쓰레드별로 고유한 Connection을 생성합니다.
- 이 Connection은 쓰레드별로 연결되거나 해제되기때문에 동시다발적으로 쓰레드에서 각 쿼리문을 요청하여도 서로 스페이스를 침범하지 않습니다.
MySqlCommand dbcmd = new MySqlCommand(command, _Connection.Value); // 명령어를 커맨드에 입력
- _Connection을 사용하기위해서는 로컬쓰레드의 레퍼런스를 가져와야합니다.
- _Connection의 Value를 이용하여 실제 레퍼런스를 사용합니다.
'server > socket server' 카테고리의 다른 글
[C# 서버] 소켓 서버 문서 (0) | 2024.01.28 |
---|---|
[C# 서버] RSA(공개키) 방식으로 통신 (0) | 2023.06.07 |
[구글 프로토콜 버퍼] 패킷 한글 주석 인코딩 해결 (0) | 2023.05.18 |
[C# 서버] Async Session, Event (0) | 2023.02.06 |
[C# 서버] Async Listener (0) | 2023.01.17 |