百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术教程 > 正文

数据库连接池(数据库连接池的作用)

mhr18 2024-11-10 09:48 18 浏览 0 评论

为了避免频繁的创建、释放连接引起的性能开销,于是引入了连接池来得到资源的服用,能更快的系统响应,以及统一的连接管理,避免了数据库连接泄露。

  数据库连接池设计大同小异,主要考虑几个问题:如果通过队列管理连接、如何获取连接、如何归还连接、如何处理扩容问题等。解决以上问题,基本就可以实现一个简单的连接池。

  首先明确一点,就是连接池只是负责管理连接对象,而连接对象才是真正干活的部分。

  (1)连接对象设计

    这里的连接对象(文中为 CDBConnect)中包括连接建立、释放、数据库表的操作(增删改查)、事务(开启、提交、回滚)等方法的实现。

//初始化建立连接
int CDBConnect::Init(const char* db_server_ip, uint16_t db_server_port,const char* username, const char* password, const char* db_name)
{
    m_mysql = mysql_init(NULL); // mysql_标准的mysql c client对应的api
    if (!m_mysql)
    {
        log_error("mysql_init failed\n");
        return -1;
    }
 
    if (!db_server_ip)
    {
        log_error("db_server_ip is null\n");
        return -1;
    }
     
    if (!username)
    {
        log_error("username is null\n");
        return -1;
    }
 
    if (!password)
    {
        log_error("password is null\n");
        return -1;
    }
 
    if (!db_name)
    {
        log_error("db_name is null\n");
        return -1;
    }
 
    bool reconnect = true;
    mysql_options(m_mysql, MYSQL_OPT_RECONNECT, &reconnect);    // 配合mysql_ping实现自动重连
    mysql_options(m_mysql, MYSQL_SET_CHARSET_NAME, "utf8mb4");  // utf8mb4和utf8区别
 
    // ip、用户名、密码、数据库名、端口
    if (!mysql_real_connect(m_mysql, db_server_ip, username, password, db_name, db_server_port, NULL, 0))
    {
        log_error("mysql_real_connect failed: %s\n", mysql_error(m_mysql));
        return -1;
    }
 
    return 0;
}

C++后台开发架构师免费学习地址:C/C++Linux服务器开发/后台架构师【零声教育】-学习视频教程-腾讯课堂

另外还整理一些C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享有需要的可以自行添加点击 正在跳转 群文件共享

执行查询,首先对返回结果对象,单独经过封装,实现表中字段类型值的封装,便于获取值。

//结果集
CResultSet::CResultSet(MYSQL_RES *res)
{
    m_res = res;
 
    // map table field key to index in the result array
    int num_fields = mysql_num_fields(m_res);
    MYSQL_FIELD *fields = mysql_fetch_fields(m_res);
    for (int i = 0; i < num_fields; i++)
    {
        // 多行
        m_key_map.insert(make_pair(fields[i].name, i));
    }
}
 
CResultSet::~CResultSet()
{
    if (m_res)
    {
        mysql_free_result(m_res);
        m_res = NULL;
    }
}
 
bool CResultSet::Next()
{
    m_row = mysql_fetch_row(m_res);
    if (m_row)
    {
        return true;
    }
    else
    {
        return false;
    }
}
 
int CResultSet::_GetIndex(const char *key)
{
    map<string, int>::iterator it = m_key_map.find(key);
    if (it == m_key_map.end())
    {
        return -1;
    }
    else
    {
        return it->second;
    }
}
 
int CResultSet::GetInt(const char *key)
{
    int idx = _GetIndex(key);
    if (idx == -1)
    {
        return 0;
    }
    else
    {
        return atoi(m_row[idx]); // 有索引
    }
}
 
char *CResultSet::GetString(const char *key)
{
    int idx = _GetIndex(key);
    if (idx == -1)
    {
        return NULL;
    }
    else
    {
        return m_row[idx];      // 列
    }
}

  查询实现如下:

//执行查询,在使用完结果集CResultSet后记得释放
CResultSet *CDBConnect::ExecuteQuery(const char *sql_query)
{
    mysql_ping(m_mysql);
 
    if (mysql_real_query(m_mysql, sql_query, strlen(sql_query)))
    {
        log_error("mysql_real_query failed: %s, sql: %s\n", mysql_error(m_mysql), sql_query);
        return NULL;
    }
    // 返回结果
    MYSQL_RES *res = mysql_store_result(m_mysql);   // 返回结果
    if (!res)
    {
        log_error("mysql_store_result failed: %s\n", mysql_error(m_mysql));
        return NULL;
    }
 
    CResultSet *result_set = new CResultSet(res);   // 存储到CResultSet
    return result_set;
}

 事务操作如下:

//开启事务
bool CDBConnect::StartTransaction()
{
    mysql_ping(m_mysql);
 
    if (mysql_real_query(m_mysql, "start transaction\n", 17))
    {
        log_error("mysql_real_query failed: %s, sql: start transaction\n", mysql_error(m_mysql));
        return false;
    }
 
    return true;
}
 
//回滚
bool CDBConnect::Rollback()
{
    mysql_ping(m_mysql);
 
    if (mysql_real_query(m_mysql, "rollback\n", 8))
    {
        log_error("mysql_real_query failed: %s, sql: rollback\n", mysql_error(m_mysql));
        return false;
    }
 
    return true;
}
 
//提交
bool CDBConnect::Commit()
{
    mysql_ping(m_mysql);
 
    if (mysql_real_query(m_mysql, "commit\n", 6))
    {
        log_error("mysql_real_query failed: %s, sql: commit\n", mysql_error(m_mysql));
        return false;
    }
 
    return true;
}

 (2)队列管理

CDBPool::CDBPool(const char *pool_name, const char *db_server_ip, uint16_t db_server_port, const char *username, const char *password, const char *db_name, int max_conn_cnt)
{
    m_pool_name = pool_name;
    m_db_server_ip = db_server_ip;
    m_db_server_port = db_server_port;
    m_username = username;
    m_password = password;
    m_db_name = db_name;
    m_db_max_conn_cnt = max_conn_cnt;   // 最大连接数
    m_db_cur_conn_cnt = MIN_DB_CONN_CNT; // 最小连接数量
}
 
// 释放连接池
CDBPool::~CDBPool()
{
    std::lock_guard<std::mutex> lock(m_mutex);
    m_abort_request = true;
    m_cond_var.notify_all();        // 通知所有在等待的
 
    //强制删除已用的连接
    for (list<CDBConnect *>::iterator it = m_used_list.begin(); it != m_used_list.end(); it++)
    {
        CDBConnect *pConn = *it;
        delete pConn;
    }
    m_used_list.clear();
 
    //删除空闲的连接
    for (list<CDBConnect *>::iterator it = m_free_list.begin(); it != m_free_list.end(); it++)
    {
        CDBConnect *pConn = *it;
        delete pConn;
    }
    m_free_list.clear();
     
}
 
int CDBPool::Init()
{
    // 创建固定最小的连接数量
    for (int i = 0; i < m_db_cur_conn_cnt; i++)
    {
        CDBConnect *pDBConn = new CDBConnect();
        int ret = pDBConn->Init(m_db_server_ip.c_str(),m_db_server_port,m_username.c_str(),m_password.c_str(),m_db_name.c_str());
        if (ret < 0)
        {
            delete pDBConn;
            return ret;
        }
        //添加到空闲的链表
        m_free_list.push_back(pDBConn);
    }
    return 0;
}
 
/*
 * timeout_ms默认为-1死等
 * timeout_ms >=0 则为等待的时间
 */
CDBConnect *CDBPool::GetDBConn(const int timeout_ms)
{
    std::unique_lock<std::mutex> lock(m_mutex);
    if(m_abort_request)
    {
        log_warn("have aboort\n");
        return NULL;
    }
    // 当没有连接可以用时
    if (m_free_list.empty())       
    {
        // 第一步先检测 当前连接数量是否达到最大的连接数量
        if (m_db_cur_conn_cnt >= m_db_max_conn_cnt)
        {
            // 看看是否需要超时等待
            if(timeout_ms < 0)       // 死等,直到有连接可以用 或者 连接池要退出
            {
                log_info("wait ms:%d\n", timeout_ms);
                m_cond_var.wait(lock, [this]
                {
                    // 当前连接数量小于最大连接数量 或者请求释放连接池时退出
                    return (!m_free_list.empty()) | m_abort_request;
                });
            } else {
                // return如果返回 false,继续wait(或者超时),  如果返回true退出wait
                // 1.m_free_list不为空
                // 2.超时退出
                // 3. m_abort_request被置为true,要释放整个连接池
                m_cond_var.wait_for(lock, std::chrono::milliseconds(timeout_ms), [this] {
                    return (!m_free_list.empty()) | m_abort_request;
                });
                // 带超时功能时还要判断是否为空
                if(m_free_list.empty())// 如果连接池还是没有空闲则退出
                {
                    return NULL;
                }
            }
 
            if(m_abort_request)
            {
                log_warn("have aboort\n");
                return NULL;
            }
        }
        else // 还没有到最大连接则创建连接,扩容
        {
            CDBConnect *pDBConn = new CDBConnect(); //新建连接
            int ret = pDBConn->Init(m_db_server_ip.c_str(),m_db_server_port,m_username.c_str(),m_password.c_str(),m_db_name.c_str());
            if (ret < 0)
            {
                log_error("Init DBConnecton failed\n\n");
                delete pDBConn;
                return NULL;
            }
 
            m_free_list.push_back(pDBConn);
            m_db_cur_conn_cnt++;
            log_info("new db connection: %s, conn_cnt: %d\n", m_pool_name.c_str(), m_db_cur_conn_cnt);
        }
    }
 
    CDBConnect *pConn = m_free_list.front();// 获取连接
    m_free_list.pop_front();    // 从空闲队列删除
    // pConn->setCurrentTime();  // 设置连接使用的开始时间
    m_used_list.push_back(pConn);
    return pConn;
}
 
void CDBPool::RelDBConn(CDBConnect *pConn)
{
    std::lock_guard<std::mutex> lock(m_mutex);
 
    list<CDBConnect *>::iterator it = m_free_list.begin();
    for (; it != m_free_list.end(); it++)   // 避免重复归还
    {
        if (*it == pConn)  
        {
            break;
        }
    }
 
    if (it == m_free_list.end())
    {
        m_used_list.remove(pConn);//从已用的链表中删除
        m_free_list.push_back(pConn);//添加到空闲链表
        m_cond_var.notify_one();        // 通知取队列
    } else
    {
        log_error("RelDBConn failed\n");
    }
}
// 遍历检测是否超时未归还
// pConn->isTimeout(); // 当前时间 - 被请求的时间
// 强制回收  从m_used_list 放回 m_free_list

  以上代码中的初始化、释放都比较好理解;对于从连接池获取一个连接,考虑到扩容处理,当空闲链表为空时,需要扩容,先判断当前连接数量是否已经大于连接数量上限,如果不大于,则新建一个连接,并添加到空闲链表中,如果大于,则进行超时处理;如果空闲链表不为空,则从空闲链表头部,取出一个连接,此处使用了另外一个链表m_used_list,保存已使用的连接,便于做连接使用超时处理。连接使用完后,进行归还操作,首先在空闲链表中遍历归还的连接是否存在,避免重复归还,如果在空闲链表中找不到,则把连接重新添加回空闲链表,并从已使用链表中删除该连接。

  对于扩展进行连接超时处理,单独开启一个线程检测链表m_used_list中连接使用时间,如果超时,则强制归还,从m_used_list 放回 m_free_list中。

  以上是MySql的连接池,如果是Redis,连接池的管理不变,主要变化就是连接对象的实现(包括常用指令对应的实现方法:get、set、mget、incr、decr、hget、hset、hmset、hmget、lpush、rpush、lrange.....等),使用开源hiredis进行封装,hiredis提供了C语言版本访问redis的常用操作,并且支持一次发送多条指令,所有结果一次返回(异步请求方式),不像MySQL单个连接执行一条指令,必须等待响应返回后,才能执行下一条指令(同步请求方式)。

原文地址:数据库连接池 - MrJuJu - 博客园

相关推荐

Redis合集-使用benchmark性能测试

采用开源Redis的redis-benchmark工具进行压测,它是Redis官方的性能测试工具,可以有效地测试Redis服务的性能。本次测试使用Redis官方最新的代码进行编译,详情请参见Redis...

Java简历总被已读不回?面试挂到怀疑人生?这几点你可能真没做好

最近看了几十份简历,发现大部分人不是技术差,而是不会“卖自己”——一、简历死穴:你写的不是经验,是岗位说明书!反面教材:ד使用SpringBoot开发项目”ד负责用户模块功能实现”救命写法:...

redission YYDS(redission官网)

每天分享一个架构知识Redission是一个基于Redis的分布式Java锁框架,它提供了各种锁实现,包括可重入锁、公平锁、读写锁等。使用Redission可以方便地实现分布式锁。red...

从数据库行锁到分布式事务:电商库存防超卖的九重劫难与破局之道

2023年6月18日我们维护的电商平台在零点刚过3秒就遭遇了严重事故。监控大屏显示某爆款手机SKU_IPHONE13_PRO_MAX在库存仅剩500台时,订单系统却产生了1200笔有效订单。事故复盘发...

SpringBoot系列——实战11:接口幂等性的形而上思...

欢迎关注、点赞、收藏。幂等性不仅是一种技术需求,更是数字文明对确定性追求的体现。在充满不确定性的网络世界中,它为我们建立起可依赖的存在秩序,这或许正是技术哲学最深刻的价值所在。幂等性的本质困境在支付系...

如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享

如何优化系统架构设计缓解流量压力提升并发性能?Java实战分享在高流量场景下。首先,我需要回忆一下常见的优化策略,比如负载均衡、缓存、数据库优化、微服务拆分这些。不过,可能还需要考虑用户的具体情况,比...

Java面试题: 项目开发中的有哪些成长?该如何回答

在Java面试中,当被问到“项目中的成长点”时,面试官不仅想了解你的技术能力,更希望看到你的问题解决能力、学习迭代意识以及对项目的深度思考。以下是回答的策略和示例,帮助你清晰、有说服力地展示成长点:一...

互联网大厂后端必看!Spring Boot 如何实现高并发抢券逻辑?

你有没有遇到过这样的情况?在电商大促时,系统上线了抢券活动,结果活动刚一开始,服务器就不堪重负,出现超卖、系统崩溃等问题。又或者用户疯狂点击抢券按钮,最后却被告知无券可抢,体验极差。作为互联网大厂的后...

每日一题 |10W QPS高并发限流方案设计(含真实代码)

面试场景还原面试官:“如果系统要承载10WQPS的高并发流量,你会如何设计限流方案?”你:“(稳住,我要从限流算法到分布式架构全盘分析)…”一、为什么需要限流?核心矛盾:系统资源(CPU/内存/数据...

Java面试题:服务雪崩如何解决?90%人栽了

服务雪崩是指微服务架构中,由于某个服务出现故障,导致故障在服务之间不断传递和扩散,最终造成整个系统崩溃的现象。以下是一些解决服务雪崩问题的常见方法:限流限制请求速率:通过限流算法(如令牌桶算法、漏桶算...

面试题官:高并发经验有吗,并发量多少,如何回复?

一、有实际高并发经验(建议结构)直接量化"在XX项目中,系统日活用户约XX万,核心接口峰值QPS达到XX,TPS处理能力为XX/秒。通过压力测试验证过XX并发线程下的稳定性。"技术方案...

瞬时流量高并发“保命指南”:这样做系统稳如泰山,老板跪求加薪

“系统崩了,用户骂了,年终奖飞了!”——这是多少程序员在瞬时大流量下的真实噩梦?双11秒杀、春运抢票、直播带货……每秒百万请求的冲击,你的代码扛得住吗?2025年了,为什么你的系统一遇高并发就“躺平”...

其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。

其实很多Java工程师不是能力不够,是没找到展示自己的正确姿势。比如上周有个小伙伴找我,五年经验但简历全是'参与系统设计''优化接口性能'这种空话。我就问他:你做的秒杀...

PHP技能评测(php等级考试)

公司出了一些自我评测的PHP题目,现将题目和答案记录于此,以方便记忆。1.魔术函数有哪些,分别在什么时候调用?__construct(),类的构造函数__destruct(),类的析构函数__cal...

你的简历在HR眼里是青铜还是王者?

你的简历在HR眼里是青铜还是王者?兄弟,简历投了100份没反应?面试总在第三轮被刷?别急着怀疑人生,你可能只是踩了这些"隐形求职雷"。帮3630+程序员改简历+面试指导和处理空窗期时间...

取消回复欢迎 发表评论: