2017-03-29 27 views
0

我有一个项目使用Wt::Dbo作为对象与MySQL的关系数据库管理。大约一个星期以来,我已经说过数据库准备好的声明有泄漏。此前该项目正在使用SQLite。使用Wt :: Dbo导致MySQL准备语句泄漏

我尝试了不同的flush()没有成功,也无法弄清楚究竟是什么导致了这种泄漏,但是肯定当准备好的语句在某个时候增长时,MySQL会停止应答。

这是我如何监控泄露编制声明:

$ mysql -uroot -p -e "SHOW SESSION STATUS LIKE '%prepare%';" | grep stmt_count 
Enter password: 
Prepared_stmt_count 260 

泄露的声明重新启动程序时将被清除。

所有数据库操作都集中一个名为DataBase类中,这里是已知泄漏的一些功能:

数据库初始化::()

void DataBase::initialize(void) 
{ 
    m_oMutex.lock(); 

    if(!m_bInitialized) 
    { 
     m_bInitialized = true; 

     try 
     { 
      m_oSessionConfigurations.setConnection(*new Wt::Dbo::backend::Sqlite3("databases/configuration.db")); 
      m_oSessionConfigurations.mapClass<InformationSite>("informationsite"); 

      m_oSessionConfigurations.createTables(); 
     } 
     catch(std::exception &e) 
     { 
      ajouterLog("error",std::string("DataBase::initialize() : ") + e.what()); 
     } 

     try 
     { 
      #if defined(DATABASE_TYPE_SQLITE) 
       Wt::Dbo::backend::Sqlite3 *pBackend = new Wt::Dbo::backend::Sqlite3("databases/dataBase.db"); 
      #elif defined(DATABASE_TYPE_MYSQL) 
       Wt::Dbo::backend::MySQL *pBackend; 

       try 
       { 
        pBackend = new Wt::Dbo::backend::MySQL(DATABASE_NAME,DATABASE_USERNAME,DATABASE_PASSWORD,"localhost"); 
       } 
       catch(Wt::Dbo::Exception &e) 
       { 
        ajouterLog("error",std::string("DataBase::initialize() : ") + e.what()); 

        // If MySQL is not available, this cause issue to program until restart. 
        exit(1); 
       } 
      #endif 

      pBackend->setProperty("show-queries","true"); 

      m_oSession.setConnection(*pBackend); 

      m_oSession.setFlushMode(Wt::Dbo::FlushMode::Auto); 

      m_oSession.mapClass<RFNode>("rfnode"); 
      m_oSession.mapClass<NodeMeasure>("nodemeasure"); 

      // Override the default InnoDB from Wt, MyISAM is easier to repair in case of hardware failure with database corruption 
      #if defined(DATABASE_TYPE_MYSQL) 
       try 
       { 
        Wt::Dbo::Transaction oTransaction(m_oSession); 

        m_oSession.execute("SET default_storage_engine=MYISAM;"); 

        oTransaction.commit(); 
       } 
       catch(Wt::Dbo::Exception &e) 
       { 
        ajouterLog("error",std::string("DataBase::initialize() : ") + e.what()); 
       } 
      #endif 

      m_oSession.createTables(); 
     } 
     catch(Wt::Dbo::Exception &e) 
     { 
      ajouterLog("error",std::string("DataBase::initialize() : ") + e.what()); 
     } 
    } 

    m_oMutex.unlock(); 
} 

数据库:: addNodeMeasure( )

void DataBase::addNodeMeasure(NodeMeasure *p_pItem) 
{ 
    m_oMutex.lock(); 

    try 
    { 
     Wt::Dbo::Transaction oTransaction(m_oSession); 

     Wt::Dbo::ptr<NodeMeasure> oItem = m_oSession.add(p_pItem); 

     oItem.flush(); 

     oTransaction.commit(); 
    } 
    catch(std::exception &e) 
    { 
     ajouterLog("error",std::string("Exception DataBase::addNodeMeasure() : ") + e.what()); 
    } 

    m_oMutex.unlock(); 

    printPreparedStatementCount("DataBase::addNodeMeasure()"); 
} 

数据库:: updateNode()

void DataBase::updateNode(RFNode *p_pItem) 
{ 
    printPreparedStatementCount("DataBase::updateNode() Before"); 

    m_oMutex.lock(); 

    try 
    { 
     Wt::Dbo::Transaction oTransaction(m_oSession); 

     Wt::Dbo::ptr<RFNode> oItem = m_oSession.find<RFNode>().where("mac = ?").bind(p_pItem->mac); 

     oItem.modify()->zone     = p_pItem->zone; 
     oItem.modify()->subZone     = p_pItem->subZone; 
     oItem.modify()->unit     = p_pItem->unit; 
     oItem.modify()->pwm      = p_pItem->pwm; 
     oItem.modify()->led      = p_pItem->led; 
     oItem.modify()->network     = p_pItem->network; 
     oItem.modify()->lastContact    = p_pItem->lastContact; 
     oItem.modify()->ioConfiguration   = p_pItem->ioConfiguration; 
     oItem.modify()->networkAddress   = p_pItem->networkAddress; 
     oItem.modify()->type     = p_pItem->type; 
     oItem.modify()->functionality   = p_pItem->functionality; 
     oItem.modify()->transmitPowerLevel  = p_pItem->transmitPowerLevel; 
     oItem.modify()->lastNetworkRoute  = p_pItem->lastNetworkRoute; 
     oItem.modify()->lastNetworkJumpsCount = p_pItem->lastNetworkJumpsCount; 
     oItem.modify()->lastRequestDuration  = p_pItem->lastRequestDuration; 
     oItem.modify()->hardwareVersion   = p_pItem->hardwareVersion; 
     oItem.modify()->softwareVersion   = p_pItem->softwareVersion; 

     oItem.flush(); 

     oTransaction.commit(); 
    } 
    catch(std::exception &e) 
    { 
     ajouterLog("error",std::string("Exception DataBase::updateNode() : ") + e.what()); 
    } 

    m_oMutex.unlock(); 

    printPreparedStatementCount("DataBase::updateNode() After"); 
} 

数据库:: getNodeMeasures()

std::vector<NodeMeasure> DataBase::getNodeMeasures(std::string p_sMAC, int p_nType, Wt::WDateTime p_oStartDate, Wt::WDateTime p_oEndDate, std::string p_sOrder, int p_nLimit) 
{ 
    std::vector<NodeMeasure> lNodeMeasures; 

    m_oMutex.lock(); 

    try 
    { 
     Wt::Dbo::Transaction oTransaction(m_oSession); 

     std::string sWhereClause = "", sOrderClause = ""; 

     if(!p_sMAC.empty()) 
     { 
      if(!sWhereClause.empty()) 
      { 
       sWhereClause += " AND "; 
      } 

      sWhereClause += "mac = '" + p_sMAC + "'"; 
     } 

     if(p_nType != -1) 
     { 
      if(!sWhereClause.empty()) 
      { 
       sWhereClause += " AND "; 
      } 

      sWhereClause += "type = " + std::to_string(p_nType); 
     } 

     if(p_oStartDate.isValid()) 
     { 
      if(!sWhereClause.empty()) 
      { 
       sWhereClause += " AND "; 
      } 

      // When not using type, we usually want nodes measures (not external temperature), so we want to find them using batchDate instead of date 
      sWhereClause += (p_nType != -1 ? "date" : "batchDate"); 
      sWhereClause += " >= '"; 
      sWhereClause += p_oStartDate.toString("yyyy-MM-ddTHH:mm:ss").toUTF8(); 
      sWhereClause += "'"; 
     } 

     if(p_oEndDate.isValid()) 
     { 
      if(!sWhereClause.empty()) 
      { 
       sWhereClause += " AND "; 
      } 

      // When not using type, we usually want nodes measures (not external temperature), so we want to find them using batchDate instead of date 
      sWhereClause += (p_nType != -1 ? "date" : "batchDate"); 
      sWhereClause += " <= '"; 
      // Add one second because SQLite have microseconds, and we must include results no matter microseconds field 
      sWhereClause += p_oEndDate.addSecs(1).toString("yyyy-MM-ddTHH:mm:ss").toUTF8(); 
      sWhereClause += "'"; 
     } 

     if(!p_sOrder.empty()) 
     { 
      sOrderClause = " ORDER BY " + p_sOrder; 
     } 

     std::string sQuery = ""; 

     if(!sWhereClause.empty()) 
     { 
      sQuery += " WHERE "; 
      sQuery += sWhereClause; 
     } 

     if(!sOrderClause.empty()) 
     { 
      sQuery += sOrderClause; 
     } 

     //std::cout << "**************************************************************************" << std::endl; 
     //std::cout << sQuery << std::endl; 
     //Wt::WDateTime oStart = Wt::WDateTime::currentDateTime(); 

     if(Configuration::getParameter(Configuration::PARAMETER_DEBUG).getBooleanValue()) 
     { 
      ajouterLog("debug","DataBase::getNodeMeasures() " + sQuery); 
     } 

     // TEST : find vs query 
     Wt::Dbo::collection<Wt::Dbo::ptr<NodeMeasure>> lMeasures = m_oSession.find<NodeMeasure>(sQuery).limit(p_nLimit).resultList(); 

     // TODO : Get it cleaner... can't use Wt::Dbo::ptr outside transaction. 
     for(Wt::Dbo::collection<Wt::Dbo::ptr<NodeMeasure>>::const_iterator pMeasure = lMeasures.begin();pMeasure != lMeasures.end();pMeasure++) 
     { 
      lNodeMeasures.push_back(
        NodeMeasure(
          (*pMeasure)->mac, 
          (*pMeasure)->type, 
          (*pMeasure)->date, 
          (*pMeasure)->batchDate, 
          (*pMeasure)->value 
        ) 
      ); 

      (*pMeasure).flush(); 
     } 

     //lNodeMeasures = m_oSession.find<NodeMeasure>(sQuery).limit(p_nLimit).resultList(); 

     //std::cout << "Result : " << lNodeMeasures.size() << " in " << oStart.secsTo(Wt::WDateTime::currentDateTime()) << "s" << std::endl; 
     //std::cout << "**************************************************************************" << std::endl; 

     oTransaction.commit(); 
    } 
    catch(std::exception &e) 
    { 
     ajouterLog("error",std::string("Exception DataBase::getNodeMeasures() : ") + e.what()); 
    } 

    m_oMutex.unlock(); 

    printPreparedStatementCount("DataBase::getNodeMeasures()"); 

    return lNodeMeasures; 
} 

回答

1

是否报表的大小生长失控? Wt :: Dbo缓存MySQL SqlConnection对象中的所有语句,并在销毁时清除它们。由于应用程序经常使用例如连接池大小为10,准备好的语句数量通常是您使用的语句数量的倍数。

由于您似乎将您的语句组成为ascii文本而不是使用参数绑定,因此您的缓存中可能会有很多语句。

DBO支持此类型的代码风格:

auto measuresQuery = m_oSession.find<NodeMeasure>(sQuery).limit(p_nLimit); 
    if(!p_sMAC.empty()) 
    { 
     measureQuery.where("mac = ?").bind(p_sMAC); 
    } 

    if(p_nType != -1) 
    { 
     measureQuery.where("type = ?").bind(std::to_string(p_nType)); 
    } 
    if(p_oStartDate.isValid()) 
    { 
     // When not using type, we usually want nodes measures (not external temperature), so we want to find them using batchDate instead of date 
     measureQuery.where(p_nType != -1 ? "date >= ?" : "batchDate >= ?").bind(p_oStartData); 
    } 
    if(!p_sOrder.empty()) 
    { 
     measureQuery.orderBy(p_sOrder); 
    } 

    Wt::Dbo::collection<Wt::Dbo::ptr<NodeMeasure>> lMeasures = measureQuery.resultList(); 

这将产生预处理语句量较小,而在我看来更短,更清晰,更安全的代码。

+0

其实我只使用一个连接,它从应用程序的开始直到崩溃或重新启动,24/7。语句可以增长,直到MySQL不想正确响应(大约5000-10000)。有没有办法来禁用此缓存?我从来没有看到任何其他框架这样做! –

+0

关于在哪里和绑定,我很久以前使用,但没有按预期工作,我会再试一次。 –

+0

缓存无法禁用。有一种保护方法可以清除MySqlConnection中的缓存(clearStatementCahce()),但我认为这不是解决此问题的正确答案。我只能推荐真正使用预准备语句并绑定参数。您的代码将更具性能(sql server只优化每个重用的查询一次),更重要的是,针对SQL注入进行安全保护,这是Web应用程序中最重要的安全漏洞之一。 – user52875