新增:MySQL 真连接池重构基础架构
核心改进: - 创建 MySQLConnectionPool 真正的连接池实现 - 连接池配置结构 PoolConfig(可配置参数) - 动态连接获取与释放机制 - 空闲连接自动清理 - 健康检查机制(定期 Ping) - 慢连接日志记录 - 连接池统计信息(Stats) - 维护协程(清理+健康检查) 新增文件: - pool_config.go - 连接池配置和实现 - PoolConfig: 可配置的连接池参数 - MySQLConnectionPool: 真正的连接池 - Acquire/Release: 连接获取与释放 - 清理与维护协程 修改文件: - pool.go - 集成新连接池到 ConnectionPool 技术特性: - 默认配置:20最大连接 / 10最大空闲 / 2最小空闲 - 健康检查:30秒间隔 - 慢连接阈值:500ms - 连接最大生命周期:30分钟 - 空闲超时:10分钟 TODO: - 连接预热(启动时建立最小连接) - LRU 连接复用策略 - 单元测试 - 性能基准测试
This commit is contained in:
@@ -16,6 +16,10 @@ type ConnectionPool struct {
|
|||||||
mysqlClients map[uint]*MySQLClient
|
mysqlClients map[uint]*MySQLClient
|
||||||
redisClients map[uint]*RedisClient
|
redisClients map[uint]*RedisClient
|
||||||
mongoClients map[uint]*MongoClient
|
mongoClients map[uint]*MongoClient
|
||||||
|
|
||||||
|
// 新增:MySQL 真连接池
|
||||||
|
mysqlPool *MySQLConnectionPool
|
||||||
|
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,20 +31,46 @@ var (
|
|||||||
// GetPool 获取全局连接池实例
|
// GetPool 获取全局连接池实例
|
||||||
func GetPool() *ConnectionPool {
|
func GetPool() *ConnectionPool {
|
||||||
poolOnce.Do(func() {
|
poolOnce.Do(func() {
|
||||||
|
// 创建 MySQL 连接池
|
||||||
|
poolConfig := DefaultPoolConfig()
|
||||||
|
|
||||||
|
mysqlPool := NewMySQLConnectionPool(poolConfig)
|
||||||
|
// 启动维护协程
|
||||||
|
mysqlPool.StartMaintenance()
|
||||||
|
|
||||||
globalPool = &ConnectionPool{
|
globalPool = &ConnectionPool{
|
||||||
mysqlClients: make(map[uint]*MySQLClient),
|
mysqlClients: make(map[uint]*MySQLClient),
|
||||||
redisClients: make(map[uint]*RedisClient),
|
redisClients: make(map[uint]*RedisClient),
|
||||||
mongoClients: make(map[uint]*MongoClient),
|
mongoClients: make(map[uint]*MongoClient),
|
||||||
|
mysqlPool: mysqlPool,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return globalPool
|
return globalPool
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMySQLClient 获取或创建 MySQL 客户端
|
// GetMySQLClient 获取或创建 MySQL 客户端(使用连接池)
|
||||||
func (p *ConnectionPool) GetMySQLClient(conn *models.DbConnection) (*MySQLClient, error) {
|
func (p *ConnectionPool) GetMySQLClient(conn *models.DbConnection) (*MySQLClient, error) {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
defer p.mu.Unlock()
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
// 尝试从连接池获取连接
|
||||||
|
if p.mysqlPool != nil {
|
||||||
|
entry, err := p.mysqlPool.Acquire(conn)
|
||||||
|
if err == nil {
|
||||||
|
// 成功从池中获取连接
|
||||||
|
return entry.Client, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 连接池错误,返回
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 降级到原有逻辑(如果连接池未初始化)
|
||||||
|
return p.getMySQLClientLegacy(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// getMySQLClientLegacy 原有的 MySQL 客户端获取逻辑(向后兼容)
|
||||||
|
func (p *ConnectionPool) getMySQLClientLegacy(conn *models.DbConnection) (*MySQLClient, error) {
|
||||||
// 检查是否已存在
|
// 检查是否已存在
|
||||||
if client, ok := p.mysqlClients[conn.ID]; ok {
|
if client, ok := p.mysqlClients[conn.ID]; ok {
|
||||||
// 测试连接是否有效
|
// 测试连接是否有效
|
||||||
@@ -76,6 +106,15 @@ func (p *ConnectionPool) GetMySQLClient(conn *models.DbConnection) (*MySQLClient
|
|||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMySQLPoolStats 获取 MySQL 连接池统计信息
|
||||||
|
func (p *ConnectionPool) GetMySQLPoolStats() *PoolStats {
|
||||||
|
if p.mysqlPool != nil {
|
||||||
|
stats := p.mysqlPool.Stats()
|
||||||
|
return &stats
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetRedisClient 获取或创建 Redis 客户端
|
// GetRedisClient 获取或创建 Redis 客户端
|
||||||
func (p *ConnectionPool) GetRedisClient(conn *models.DbConnection) (*RedisClient, error) {
|
func (p *ConnectionPool) GetRedisClient(conn *models.DbConnection) (*RedisClient, error) {
|
||||||
p.mu.Lock()
|
p.mu.Lock()
|
||||||
|
|||||||
426
internal/dbclient/pool_config.go
Normal file
426
internal/dbclient/pool_config.go
Normal file
@@ -0,0 +1,426 @@
|
|||||||
|
package dbclient
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"u-desk/internal/crypto"
|
||||||
|
"u-desk/internal/storage/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PoolConfig 连接池配置
|
||||||
|
type PoolConfig struct {
|
||||||
|
// 最大打开连接数(硬上限)
|
||||||
|
MaxOpenConns int
|
||||||
|
// 最大空闲连接数(超过此数量的空闲连接会被关闭)
|
||||||
|
MaxIdleConns int
|
||||||
|
// 连接最大生命周期(超过此时间的连接会被关闭)
|
||||||
|
ConnMaxLifetime time.Duration
|
||||||
|
// 连接最大空闲时间(超过此时间未使用的连接会被关闭)
|
||||||
|
ConnMaxIdleTime time.Duration
|
||||||
|
// 最小空闲连接数(保持此数量的空闲连接以快速响应)
|
||||||
|
MinIdleConns int
|
||||||
|
// 连接超时时间(建立连接的最长时间)
|
||||||
|
ConnTimeout time.Duration
|
||||||
|
// 健康检查间隔(定期 Ping 连接检查有效性)
|
||||||
|
HealthCheckInterval time.Duration
|
||||||
|
// 是否启用连接预热(启动时建立最小连接)
|
||||||
|
EnableWarmup bool
|
||||||
|
// 是否启用慢连接日志(记录建立时间超过阈值的连接)
|
||||||
|
EnableSlowConnLog bool
|
||||||
|
// 慢连接阈值(超过此时间记录为慢连接)
|
||||||
|
SlowConnThreshold time.Duration
|
||||||
|
// 连接池最大容量(防止资源耗尽)
|
||||||
|
MaxPoolCapacity int
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultPoolConfig 返回默认连接池配置
|
||||||
|
func DefaultPoolConfig() *PoolConfig {
|
||||||
|
return &PoolConfig{
|
||||||
|
MaxOpenConns: 20, // 最大20个连接
|
||||||
|
MaxIdleConns: 10, // 最大10个空闲
|
||||||
|
ConnMaxLifetime: 30 * time.Minute, // 连接最长30分钟
|
||||||
|
ConnMaxIdleTime: 10 * time.Minute, // 空闲10分钟关闭
|
||||||
|
MinIdleConns: 2, // 保持2个最小空闲
|
||||||
|
ConnTimeout: 5 * time.Second, // 连接超时5秒
|
||||||
|
HealthCheckInterval: 30 * time.Second, // 30秒健康检查一次
|
||||||
|
EnableWarmup: true, // 启用预热
|
||||||
|
EnableSlowConnLog: true, // 启用慢连接日志
|
||||||
|
SlowConnThreshold: 500 * time.Millisecond, // 超过500ms算慢连接
|
||||||
|
MaxPoolCapacity: 50, // 连接池最大容量
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQLPoolEntry MySQL 连接池条目
|
||||||
|
type MySQLPoolEntry struct {
|
||||||
|
Client *MySQLClient
|
||||||
|
LastUsed time.Time
|
||||||
|
CreatedAt time.Time
|
||||||
|
InUse bool
|
||||||
|
mu sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// AcquireResult 连接获取结果
|
||||||
|
type AcquireResult struct {
|
||||||
|
Entry *MySQLPoolEntry
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReleaseResult 连接释放结果
|
||||||
|
type ReleaseResult struct {
|
||||||
|
Success bool
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats 连接池统计信息
|
||||||
|
type PoolStats struct {
|
||||||
|
TotalConns int // 总连接数
|
||||||
|
ActiveConns int // 使用中的连接数
|
||||||
|
IdleConns int // 空闲连接数
|
||||||
|
WaitCount int64 // 等待连接的次数
|
||||||
|
WaitDuration time.Duration // 总等待时间
|
||||||
|
SlowConnCount int64 // 慢连接数量
|
||||||
|
}
|
||||||
|
|
||||||
|
// MySQLConnectionPool MySQL 连接池(真正的连接池)
|
||||||
|
type MySQLConnectionPool struct {
|
||||||
|
config *PoolConfig
|
||||||
|
configHash string // 配置哈希,用于检测配置变更
|
||||||
|
mu sync.RWMutex
|
||||||
|
entries []*MySQLPoolEntry // 连接池条目
|
||||||
|
connMap map[uint]*MySQLClient // 连接ID -> 客户端映射(兼容现有代码)
|
||||||
|
stats PoolStats
|
||||||
|
stopCh chan struct{}
|
||||||
|
wg sync.WaitGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMySQLConnectionPool 创建新的 MySQL 连接池
|
||||||
|
func NewMySQLConnectionPool(config *PoolConfig) *MySQLConnectionPool {
|
||||||
|
if config == nil {
|
||||||
|
config = DefaultPoolConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
pool := &MySQLConnectionPool{
|
||||||
|
config: config,
|
||||||
|
entries: make([]*MySQLPoolEntry, 0, config.MaxPoolCapacity),
|
||||||
|
connMap: make(map[uint]*MySQLClient),
|
||||||
|
stopCh: make(chan struct{}),
|
||||||
|
}
|
||||||
|
|
||||||
|
return pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Acquire 获取一个连接(阻塞等待直到有可用连接)
|
||||||
|
func (p *MySQLConnectionPool) Acquire(conn *models.DbConnection) (*MySQLPoolEntry, error) {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
// 尝试从池中获取空闲连接
|
||||||
|
for _, entry := range p.entries {
|
||||||
|
entry.mu.Lock()
|
||||||
|
if !entry.InUse {
|
||||||
|
entry.InUse = true
|
||||||
|
entry.LastUsed = time.Now()
|
||||||
|
entry.mu.Unlock()
|
||||||
|
|
||||||
|
// 更新统计
|
||||||
|
p.updateWaitStats(startTime)
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
entry.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 没有可用连接,创建新连接
|
||||||
|
if len(p.entries) >= p.config.MaxOpenConns {
|
||||||
|
// 已达到最大连接数,等待
|
||||||
|
return nil, p.waitForAvailableConnection(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新连接
|
||||||
|
newEntry, err := p.createNewEntry(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.entries = append(p.entries, newEntry)
|
||||||
|
p.updateStats()
|
||||||
|
p.updateWaitStats(startTime)
|
||||||
|
|
||||||
|
return newEntry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Release 释放连接回池中
|
||||||
|
func (p *MySQLConnectionPool) Release(entry *MySQLPoolEntry) error {
|
||||||
|
if entry == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.mu.Lock()
|
||||||
|
defer entry.mu.Unlock()
|
||||||
|
|
||||||
|
entry.InUse = false
|
||||||
|
entry.LastUsed = time.Now()
|
||||||
|
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
p.updateStats()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close 关闭连接池
|
||||||
|
func (p *MySQLConnectionPool) Close() error {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
// 发送停止信号
|
||||||
|
close(p.stopCh)
|
||||||
|
|
||||||
|
// 等待所有 goroutine 完成
|
||||||
|
p.wg.Wait()
|
||||||
|
|
||||||
|
// 关闭所有连接
|
||||||
|
var lastErr error
|
||||||
|
for _, entry := range p.entries {
|
||||||
|
entry.mu.Lock()
|
||||||
|
if err := entry.Client.Close(); err != nil {
|
||||||
|
lastErr = err
|
||||||
|
}
|
||||||
|
entry.InUse = false
|
||||||
|
entry.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.entries = make([]*MySQLPoolEntry, 0, p.config.MaxPoolCapacity)
|
||||||
|
p.connMap = make(map[uint]*MySQLClient)
|
||||||
|
|
||||||
|
return lastErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stats 获取连接池统计信息
|
||||||
|
func (p *MySQLConnectionPool) Stats() PoolStats {
|
||||||
|
p.mu.RLock()
|
||||||
|
defer p.mu.RUnlock()
|
||||||
|
return p.stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// cleanupIdleConnections 清理空闲连接
|
||||||
|
func (p *MySQLConnectionPool) cleanupIdleConnections() {
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
keepEntries := make([]*MySQLPoolEntry, 0, len(p.entries))
|
||||||
|
|
||||||
|
for _, entry := range p.entries {
|
||||||
|
entry.mu.Lock()
|
||||||
|
isIdle := !entry.InUse
|
||||||
|
idleDuration := now.Sub(entry.LastUsed)
|
||||||
|
entry.mu.Unlock()
|
||||||
|
|
||||||
|
// 保留条件:正在使用 或 空闲时间未超过阈值 或 数量少于最小空闲数
|
||||||
|
keep := !isIdle ||
|
||||||
|
idleDuration < p.config.ConnMaxIdleTime ||
|
||||||
|
len(keepEntries) < p.config.MinIdleConns
|
||||||
|
|
||||||
|
if keep {
|
||||||
|
keepEntries = append(keepEntries, entry)
|
||||||
|
} else {
|
||||||
|
// 关闭连接
|
||||||
|
entry.Client.Close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p.entries = keepEntries
|
||||||
|
p.updateStats()
|
||||||
|
}
|
||||||
|
|
||||||
|
// healthCheck 健康检查
|
||||||
|
func (p *MySQLConnectionPool) healthCheck() {
|
||||||
|
p.mu.RLock()
|
||||||
|
entriesCopy := make([]*MySQLPoolEntry, len(p.entries))
|
||||||
|
copy(entriesCopy, p.entries)
|
||||||
|
p.mu.RUnlock()
|
||||||
|
|
||||||
|
var healthyEntries []*MySQLPoolEntry
|
||||||
|
|
||||||
|
for _, entry := range entriesCopy {
|
||||||
|
entry.mu.Lock()
|
||||||
|
if !entry.InUse {
|
||||||
|
// Ping 测试
|
||||||
|
if err := entry.Client.sqlDB.Ping(); err != nil {
|
||||||
|
// 连接失效,标记为需要关闭
|
||||||
|
entry.mu.Unlock()
|
||||||
|
entry.Client.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry.mu.Unlock()
|
||||||
|
healthyEntries = append(healthyEntries, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新连接池
|
||||||
|
p.mu.Lock()
|
||||||
|
defer p.mu.Unlock()
|
||||||
|
p.entries = healthyEntries
|
||||||
|
p.updateStats()
|
||||||
|
}
|
||||||
|
|
||||||
|
// StartMaintenance 启动维护协程(清理和健康检查)
|
||||||
|
func (p *MySQLConnectionPool) StartMaintenance() {
|
||||||
|
p.wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer p.wg.Done()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(p.config.HealthCheckInterval)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
// 清理空闲连接
|
||||||
|
p.cleanupIdleConnections()
|
||||||
|
// 健康检查
|
||||||
|
p.healthCheck()
|
||||||
|
case <-p.stopCh:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// createNewEntry 创建新的连接池条目
|
||||||
|
func (p *MySQLConnectionPool) createNewEntry(conn *models.DbConnection) (*MySQLPoolEntry, error) {
|
||||||
|
startTime := time.Now()
|
||||||
|
|
||||||
|
client, err := createMySQLClient(conn)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
elapsed := time.Since(startTime)
|
||||||
|
|
||||||
|
// 慢连接日志
|
||||||
|
if p.config.EnableSlowConnLog && elapsed > p.config.SlowConnThreshold {
|
||||||
|
// 记录慢连接
|
||||||
|
p.mu.Lock()
|
||||||
|
p.stats.SlowConnCount++
|
||||||
|
p.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
entry := &MySQLPoolEntry{
|
||||||
|
Client: client,
|
||||||
|
LastUsed: time.Now(),
|
||||||
|
CreatedAt: startTime,
|
||||||
|
InUse: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
return entry, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// waitForAvailableConnection 等待可用连接
|
||||||
|
func (p *MySQLConnectionPool) waitForAvailableConnection(conn *models.DbConnection) error {
|
||||||
|
// 实现简单的等待逻辑(使用 channel)
|
||||||
|
// 创建一个超时上下文
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ticker := time.NewTicker(100 * time.Millisecond)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ErrPoolExhausted
|
||||||
|
case <-ticker.C:
|
||||||
|
// 检查是否有可用连接
|
||||||
|
p.mu.RLock()
|
||||||
|
hasAvailable := false
|
||||||
|
for _, entry := range p.entries {
|
||||||
|
entry.mu.Lock()
|
||||||
|
if !entry.InUse {
|
||||||
|
hasAvailable = true
|
||||||
|
entry.mu.Unlock()
|
||||||
|
break
|
||||||
|
}
|
||||||
|
entry.mu.Unlock()
|
||||||
|
}
|
||||||
|
p.mu.RUnlock()
|
||||||
|
|
||||||
|
if hasAvailable {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateWaitStats 更新等待统计
|
||||||
|
func (p *MySQLConnectionPool) updateWaitStats(startTime time.Time) {
|
||||||
|
waitDuration := time.Since(startTime)
|
||||||
|
p.stats.WaitCount++
|
||||||
|
p.stats.WaitDuration += waitDuration
|
||||||
|
}
|
||||||
|
|
||||||
|
// updateStats 更新连接池统计
|
||||||
|
func (p *MySQLConnectionPool) updateStats() {
|
||||||
|
total := len(p.entries)
|
||||||
|
active := 0
|
||||||
|
idle := 0
|
||||||
|
|
||||||
|
for _, entry := range p.entries {
|
||||||
|
entry.mu.Lock()
|
||||||
|
if entry.InUse {
|
||||||
|
active++
|
||||||
|
} else {
|
||||||
|
idle++
|
||||||
|
}
|
||||||
|
entry.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
p.stats.TotalConns = total
|
||||||
|
p.stats.ActiveConns = active
|
||||||
|
p.stats.IdleConns = idle
|
||||||
|
}
|
||||||
|
|
||||||
|
// createMySQLClient 创建 MySQL 客户端的辅助函数
|
||||||
|
func createMySQLClient(conn *models.DbConnection) (*MySQLClient, error) {
|
||||||
|
// 解密密码
|
||||||
|
password, err := crypto.DecryptPassword(conn.Password)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("密码解密失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
config := &MySQLConfig{
|
||||||
|
Host: conn.Host,
|
||||||
|
Port: conn.Port,
|
||||||
|
Username: conn.Username,
|
||||||
|
Password: password,
|
||||||
|
Database: conn.Database,
|
||||||
|
}
|
||||||
|
|
||||||
|
return NewMySQLClient(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 错误定义
|
||||||
|
var (
|
||||||
|
ErrPoolExhausted = &PoolError{Message: "连接池已耗尽"}
|
||||||
|
ErrPoolClosed = &PoolError{Message: "连接池已关闭"}
|
||||||
|
)
|
||||||
|
|
||||||
|
// PoolError 连接池错误
|
||||||
|
type PoolError struct {
|
||||||
|
Message string
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PoolError) Error() string {
|
||||||
|
if e.Err != nil {
|
||||||
|
return e.Message + ": " + e.Err.Error()
|
||||||
|
}
|
||||||
|
return e.Message
|
||||||
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
74b8a7937d28d6e8fb6d93e63e81abf7
|
0b56c4ddab241d0ca843efcc544c131c
|
||||||
Reference in New Issue
Block a user