Hey

I have indeed found a good solution. I know it's good because I have put it through many tests and found no wasted overhead, and also because it's been running for a few years on many networks in a few African countries where the power goes out on a daily basis, problems would have been caught when the power goes back on.

The important part is this: you instantiate a new connection on program startup, the connection is closed and reopened after each query, but it must not be destroyed throughout the program's session. Opening and closing it does not cause wasted overhead, but destroying it and instantiating a new one would.

I will provide a (mostly-)complete code example, as it took years of experimentation to reach this stable point.

Declare this somewhere persistent throughought your program's life, e.g. in your loader module:

vb Code:
  1. Public g_mhD As MariaHandler

In your program's startup:

vb Code:
  1. Set g_mhD = New MariaHandler
  2.     If Not g_mhD.Init Then
  3.         Call MsgBox("Opening a connection to the database failed." & App.ProductName & ".", vbCritical, App.Title)
  4.         Exit Sub
  5.     End If

Now in the MariaHandler class:

vb Code:
  1. ' If compiling in Win7 there is a risk that the binary won't run in Vista/XP
  2. ' See this article for the resolution:
  3. ' [url]https://support.microsoft.com/en-us/help/2517589/an-ado-application-does-not-run-on-down-level-operating-systems-after[/url]
  4.  
  5. Private m_cn As ADODB.Connection
  6. Private m_rs As ADODB.Recordset
  7. Private m_cmd As ADODB.Command
  8.  
  9. Public Function Init() As Boolean
  10.     Dim success As Boolean
  11.     success = initConn
  12.     Init = success
  13. End Function
  14.  
  15. ' Instantiate and test new connection.
  16. Private Function initConn() As Boolean
  17.     Dim connStr As String
  18.     Dim success As Boolean
  19.  
  20.     On Error GoTo Fail
  21.  
  22.     success = False
  23.  
  24.     Set m_cn = New ADODB.Connection
  25.  
  26.     ' Configure connection
  27.     connStr = g_cfgD.dbConnDriver _
  28.         & ";SERVER=" & g_cfgD.dbServer _
  29.         & ";DATABASE=" & g_cfgD.dbName _
  30.         & ";UID=" & g_cfgD.dbUser _
  31.         & ";PWD=" & g_cfgD.dbPwd _
  32.         & ";PORT=" & g_cfgD.dbPort _
  33.         & ";POOLING=true" _
  34.         & ";OPTION=3"
  35.  
  36.     m_cn.ConnectionString = connStr
  37.     m_cn.CommandTimeout = 30
  38.  
  39.     ' Test that the connection works
  40.     success = openConn
  41.  
  42.     Call closeConn
  43.  
  44.     initConn = success
  45.     Exit Function
  46.  
  47. Fail:
  48.     initConn = False
  49.     Call failHandler("initConn", "", err, "Initializing a connection to the database failed!")
  50. End Function
  51.  
  52. Private Function openConn() As Boolean
  53.     On Error GoTo Fail
  54.  
  55.     If m_cn.State <> adStateOpen Then
  56.         ' An error is raised here if any of the details in m_cn.ConnectionString are wrong
  57.         Call m_cn.Open
  58.     End If
  59.  
  60.     If m_cn.State = adStateOpen Then
  61.         openConn = True
  62.     Else
  63.         openConn = False
  64.     End If
  65.  
  66.     Exit Function
  67.  
  68. Fail:
  69.     openConn = False
  70.     Call failHandler("openConn", "", err, "Opening a connection to the database failed!")
  71. End Function
  72.  
  73. Public Sub destroyConn()
  74.     On Error Resume Next
  75.  
  76.     Set m_cmd.ActiveConnection = Nothing
  77.     Call m_rs.Close
  78.     Set m_rs.ActiveConnection = Nothing
  79.     Call m_cn.Close
  80.  
  81.     Set m_cmd = Nothing
  82.     Set m_rs = Nothing
  83. End Sub
  84.  
  85. Public Sub closeConn()
  86.     ' [url]https://docs.microsoft.com/en-us/sql/ado/reference/ado-api/close-method-ado[/url]
  87.     ' [url]https://msdn.microsoft.com/en-us/library/ms810829.aspx[/url]
  88.     ' [url]https://mysql-net.github.io/MySqlConnector/connection-options/[/url]
  89.     ' Note: you cannot access m_rs once m_cn.Close is called
  90.     If m_cn.State <> adStateClosed Then
  91.         Call m_cn.Close
  92.     End If
  93. End Sub
  94.  
  95. ' Called after every query is executed and m_rs no longer needed
  96. Private Sub rsCleanup()
  97.     Call closeConn
  98.  
  99.     If Not m_rs Is Nothing Then
  100.         If m_rs.State <> adStateClosed Then ' Closed when executing an INSERT or UPDATE, open on SELECT
  101.             Call m_rs.Close
  102.         End If
  103.         Set m_rs = Nothing
  104.     End If
  105.  
  106.     Set m_cmd = Nothing
  107. End Sub
  108.  
  109. ' Executes a given query and sets the modular m_rs object
  110. Private Function executeQuery(ByVal sqlQuery As String, ByVal callerName As String)
  111.     On Error GoTo Fail
  112.  
  113.     Call executeCommand(sqlQuery)
  114.  
  115.     Exit Function
  116.  
  117. Fail:
  118.     ' The error is re-raised for the calling function to handle.
  119.     err.Raise err.number, err.Source, err.description, err.HelpFile, err.HelpContext
  120. End Function
  121.  
  122. ' Tries to execute ADODB.Command.
  123. Private Function executeCommand(ByVal sqlQuery As String) As Boolean
  124.     Dim recordsAffected As Long
  125.     Dim success As Boolean
  126.  
  127.     success = False
  128.     On Error GoTo Fail
  129.  
  130.     If Not m_cn Is Nothing Then
  131.  
  132.         ' Parameterized queries require an ad-hoc m_cmd, which must be destroyed once used
  133.         ' so that the next query does not encounter old parameters. m_cmd is destroyed at
  134.         ' the end of this function. Non-parameterized queries do not instantiate a new m_cmd,
  135.         ' so that is done here:
  136.         If m_cmd Is Nothing Then ' Non-parameterized query
  137.             Set m_cmd = New ADODB.Command
  138.             m_cmd.CommandType = adCmdText
  139.         End If
  140.  
  141.         If Not m_cmd Is Nothing Then
  142.             If openConn Then
  143.                 m_cmd.ActiveConnection = m_cn
  144.                 m_cmd.CommandText = sqlQuery
  145.                 Set m_rs = m_cmd.Execute(recordsAffected)
  146.                 success = True
  147.                 ' Do not close connection or destroy m_cmd yet as that would prevent access
  148.                 ' to m_rs, needed not only for SELECT statements but also for retrieving
  149.                 ' error number from stored procedures.
  150.             End If
  151.         End If
  152.     End If
  153.  
  154.     executeCommand = success
  155.     Exit Function
  156.  
  157. Fail:
  158.     err.Raise err.number, err.Source, err.description, err.HelpFile, err.HelpContext
  159. End Function
  160.  
  161. Private Sub Class_Terminate()
  162.     Call destroyConn
  163. End Sub
  164.  
  165. ' Optional, makes error messages more human-friendly
  166. Private Sub failHandler( _
  167.     ByVal procName As String, _
  168.     ByVal queryStr As String, _
  169.     ByVal err As ErrObject, _
  170.     Optional ByVal leadDesc As String)
  171.  
  172.     Dim desc As String
  173.     Dim descPrefix As String
  174.     Dim errDscr As String
  175.     Dim errNbr As Long
  176.     Dim errSrc As String
  177.     Dim ll As Long
  178.     Dim p As Parameter
  179.     Dim msg As String
  180.     Dim regex As RegExp
  181.     Dim res As Integer
  182.  
  183.     ' Handle errors in this error handler.
  184.     ' "On Error..." resets the ErrObject, so backup the error info first:
  185.     errDscr = err.description
  186.     errNbr = err.number
  187.     errSrc = err.Source
  188.     On Error Resume Next
  189.  
  190.     Set regex = New RegExp
  191.  
  192.     If procName = "openConn" Then
  193.         ' m_cn.Open failed, precise cause is in errDscr
  194.         ' e.g.
  195.         ' [MySQL][ODBC 5.3(w) Driver]Access denied for user 'foo'@'localhost' (using password: YES)
  196.  
  197.         regex.Pattern = "\]([^\]]+)$" ' Match last ']'
  198.         descPrefix = regex.Replace(errDscr, "]")
  199.  
  200.         regex.Pattern = ".*\]([^\]]+)$" ' Match everything after last ']'
  201.         desc = regex.Replace(errDscr, "$1")
  202.  
  203.         msg = "Opening a connecton to the database failed:" & vbNewLine _
  204.             & desc & vbNewLine _
  205.             & vbNewLine _
  206.             & "Check the database connection settings in" & vbNewLine _
  207.             & Environ("ALLUSERSPROFILE") & "" & App.ProductName & ".ini" & vbNewLine _
  208.             & vbNewLine _
  209.             & "Error number: " & errNbr & vbNewLine _
  210.             & "Source: " & descPrefix & vbNewLine _
  211.             & "Source: " & errSrc & vbNewLine _
  212.             & "Procedure: " & procName
  213.  
  214.     Else
  215.  
  216.         ' Split errDscr to make it more readable and to make the dialog less wide
  217.         regex.Pattern = "\]([^\]]+)$" ' Match last ']'
  218.         desc = regex.Replace(errDscr, "]" & vbNewLine & "$1")
  219.  
  220.         If LenB(leadDesc) = 0 Then
  221.             msg = "There was an error while communicating with the database." & vbNewLine & vbNewLine
  222.         Else
  223.             msg = leadDesc & vbNewLine & vbNewLine
  224.         End If
  225.  
  226.         msg = msg & "Error number: " & errNbr & vbNewLine _
  227.             & "Source: " & errSrc & vbNewLine _
  228.             & "Procedure: " & procName & vbNewLine _
  229.             & vbNewLine _
  230.             & "Description: " & vbNewLine _
  231.             & desc & vbNewLine
  232.  
  233.         If LenB(queryStr) > 0 Then
  234.             ' Replace ? with parameters
  235.             If Not m_cmd Is Nothing Then
  236.                 If m_cmd.Parameters.Count > 0 Then
  237.                     For ll = 1 To m_cmd.Parameters.Count
  238.                         Set p = m_cmd.Parameters(ll - 1)
  239.                         queryStr = Replace(queryStr, "?", "'" & p.Value & "'", 1, ll)
  240.                     Next
  241.                 End If
  242.             End If
  243.             msg = msg & vbNewLine & queryStr
  244.         End If
  245.  
  246.     End If
  247.  
  248.     Debug.Print msg
  249.     Call MsgBox(msg, vbExclamation, App.Title)
  250.  
  251.     Set m_cmd = Nothing
  252.  
  253.     res = MsgBox("Would you like to close " & App.ProductName & "?" & vbNewLine & _
  254.         "Press ""Yes"" to close it ""No"" to continue using it.", vbQuestion + vbYesNo + vbDefaultButton2, App.Title)
  255.  
  256.     If res = vbYes Then
  257.         Call destroyConn
  258.         Call unloadForms
  259.         End
  260.     End If
  261. End Sub

Helper utils:

vb Code:
  1. Public Const MYSQL_DATETIME_LENGTH As Integer = 19 ' "yyyy-mm-dd hh:mm:ss"
  2. Public Const MYSQL_DATETIME_FORMAT As String = "yyyy-mm-dd hh:mm:ss"
  3. Public Const MYSQL_DATE_LENGTH As Integer = 10 ' "yyyy-mm-dd"
  4. Public Const MYSQL_DATE_FORMAT As String = "yyyy-mm-dd"
  5.  
  6. Public Function getMysqlDate(ByVal v As Variant, doIncludeTime As Boolean) As String
  7.     Dim str As String
  8.  
  9.     If v = 0 Then
  10.         ' Format$(0, MYSQL_DATETIME_FORMAT) returns 1899-12-30 00:00:00
  11.         If doIncludeTime Then
  12.             str = "0000-00-00 00:00:00"
  13.         Else
  14.             str = "0000-00-00"
  15.         End If
  16.     Else
  17.         If doIncludeTime Then
  18.             str = Format$(v, MYSQL_DATETIME_FORMAT)
  19.         Else
  20.             str = Format$(v, MYSQL_DATE_FORMAT)
  21.         End If
  22.     End If
  23.  
  24.     getMysqlDate = str
  25. End Function
  26.  
  27. Public Function getDateTimeNull(ByVal v As Variant) As Date
  28.     If IsDate(v) Then
  29.         getDateTimeNull = v
  30.     Else
  31.         getDateTimeNull = 0
  32.     End If
  33. End Function

And finally, example functions for storing and selecting data, using parameterized queries:

vb Code:
  1. ' The id is set by MariaDB:
  2. '   `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT
  3. '   PRIMARY KEY (`id`)
  4. ' It is then retrieved after the insert, so that your program can refer to fooD.id
  5. Public Function storeFooData(ByVal fooD As FooData) As Boolean
  6.     Dim queryStr As String
  7.  
  8.     On Error GoTo Fail
  9.  
  10.     queryStr = "INSERT INTO foo (" _
  11.         & "item_name, " _
  12.         & "expiry, " _
  13.         & "stock, " _
  14.         & "is_deleted)" & vbNewLine _
  15.         & "VALUES (?, ?, ?, ?);"
  16.  
  17.     Set m_cmd = New ADODB.Command
  18.     m_cmd.Parameters.Append m_cmd.CreateParameter(, adVarWChar, adParamInput, 100, fooD.name)
  19.     m_cmd.Parameters.Append m_cmd.CreateParameter(, adVarChar, adParamInput, MYSQL_DATE_LENGTH, getMysqlDate(fooD.expiry, False)) ' Gotcha
  20.     m_cmd.Parameters.Append m_cmd.CreateParameter(, adUnsignedInt, adParamInput, 10, fooD.stock)
  21.     m_cmd.Parameters.Append m_cmd.CreateParameter(, adBoolean, adParamInput, 1, fooD.isDeleted)
  22.  
  23.     Call executeQuery(queryStr, "storeFooData")
  24.     Call executeQuery("SELECT LAST_INSERT_ID();", "storeFooData")
  25.     If Not m_rs Is Nothing Then
  26.         If Not m_rs.EOF Then
  27.             fooD.id = m_rs("LAST_INSERT_ID()")
  28.         End If
  29.     End If
  30.  
  31.     storeFooData = True
  32.     Call rsCleanup
  33.     Exit Function
  34.  
  35. Fail:
  36.     storeFooData = False
  37.     Call failHandler("storeFooData", queryStr, err)
  38. End Function
  39.  
  40. Public Function retrieveFooDataById(ByVal fooId As Long) As FooData
  41.     Dim fooD As FooData
  42.     Dim queryStr As String
  43.  
  44.     On Error GoTo Fail
  45.  
  46.     queryStr = "SELECT id, item_name, expiry, stock, is_deleted" & vbNewLine _
  47.         & "FROM foo" & vbNewLine _
  48.         & "WHERE id = ?;"
  49.  
  50.     Set m_cmd = New ADODB.Command
  51.     m_cmd.Parameters.Append m_cmd.CreateParameter(, adUnsignedBigInt, adParamInput, 10, fooId)
  52.  
  53.     Call executeQuery(queryStr, "retrieveFooDataById")
  54.  
  55.     If Not m_rs.EOF Then
  56.         Set fooD = New FooData
  57.         fooD.id = m_rs("id")
  58.         fooD.name = m_rs("item_name")
  59.         fooD.expiry = getDateTimeNull(m_rs("expiry")) ' Gotcha
  60.         fooD.stock = m_rs("stock")
  61.         fooD.isDeleted = CBool(m_rs("is_deleted"))
  62.     End If
  63.  
  64.     Set retrieveFooDataById = fooD
  65.     Call rsCleanup
  66.     Exit Function
  67.  
  68. Fail:
  69.     Call failHandler("retrieveFooDataById", queryStr, err)
  70. End Function