Driver version
mssql-jdbc-12.2.0.jre11.jar
SQL Server version
Microsoft SQL Server 2019 (RTM-GDR) (KB5014356) - 15.0.2095.3 (X64) Apr 29 2022 18:00:13 Copyright (C) 2019 Microsoft Corporation Enterprise Edition (64-bit) on Windows Server 2022 Datacenter 10.0 (Build 20348: ) (Hypervisor)
Client Operating System
Edition Windows Server 2022 Datacenter
Version 21H2
Installed on 11/5/2021
OS build 20348.1129
JAVA/JVM version
Oracle Java SE SDK 17.0.7
Problem description
When using a CallableStatement on the client to call a stored procedure
that has a decimal(25,12) parameter, this driver triggers a memory leak
in the SERVER which causes the process managing the connection to the client
to use an increasing amount of memory with each call. Eventually
the database runs out of memory and dies a slow painful death.
This problem was not evident in previous versions of the jdbc driver.
Duplicating the problem
On the server, add the following simple stored procedure:
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[proc_DoNothing]
@lastname nvarchar(256),
@MiDName nvarchar(16),
@firstname nvarchar(256),
@Level int,
@special decimal(25, 12)
AS
BEGIN
SET NOCOUNT ON;
END
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
on the client, run this program:
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
package company.util.test2;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
public class FakeServer2
{
public static FakeServer2 inst;
private static final String proc_DoNothing =
"{ call proc_DoNothing(?, ?, ?, ?, ?) }";
public DriverManagerConnectionFactory connectionFactory;
public static void main(String args[])
{
inst = new FakeServer2();
inst.init();
inst.testDoNothingStoredProc();
}
public void init()
{
try
{
try
{
Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
}
catch (Exception e)
{
e.printStackTrace();
}
String server = "10.10.10.10";
String database = (((("mydb"))));
String user = ("username");
String password = "pwd";
String url2 = "jdbc:sqlserver://" + server + ":1433;databaseName=" + database + ";user="
+ user + ";password=" + password + ";applicationName=" + "FakeServer2"
+ ";trustServerCertificate=true";
connectionFactory = new DriverManagerConnectionFactory(url2, user, password);
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
}
}
public Connection getConnection()
{
Connection c = null;
try
{
c = connectionFactory.createConnection();
}
catch (SQLException e)
{
e.printStackTrace();
}
return c;
}
public void closeConnection(Connection conn)
{
try
{
conn.close();
}
catch (Exception ee)
{
ee.printStackTrace();
}
}
public void testDoNothingStoredProc()
{
Connection dbconn = null;
CallableStatement stmt = null;
dbconn = getConnection();
for (int j = 0; j < 10000000; j++)
{
try
{
System.out.println("iteration number " + j);
stmt = dbconn.prepareCall(proc_DoNothing);
int count = 20;
for (int i = 0; i < count; ++i)
{
try
{
doNothingDB(stmt, 0, 0);
doNothingDB(stmt, 1, 0);
doNothingDB(stmt, 0, 1);
doNothingDB(stmt, 1, 1);
}
finally
{
}
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
try
{
stmt.close();
//dbconn.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
stmt = null;
} // end finally
}
try
{
dbconn.close();
}
catch (SQLException e)
{
e.printStackTrace();
}
}
private void doNothingDB(CallableStatement stmt, int side, int book) throws SQLException
{
try
{
for (int lev = 0; lev < 10; ++lev)
{
int ind = 1;
stmt.setNString(ind++, "OK");
stmt.setNString(ind++, "PHIL");
stmt.setNString(ind++, "MORE");
stmt.setLong(ind++, 1232);
BigDecimal bd = new BigDecimal((long) (Math.random() * 100));
stmt.setBigDecimal(ind++, bd);
stmt.execute();
}
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
}
}
}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Using Sql Management Studio, select the database server and display the activity monitor.
Under processes, find the entry for FakeServer2.
Look at the Memory column, and you will see the value increase without bound.
Workaround
Closing the database connection will alleviate this problem on the server.
But when using a pooled datasource this is not feasible.
You can workaround this issue by passing the big decimal as a String:
BigDecimal bd = new BigDecimal((long) (Math.random() * 100));
if (bd.scale() > 12)
bd.setScale(12);
stmt.setNString(ind++, bd.toPlainString());
Driver version
mssql-jdbc-12.2.0.jre11.jar
SQL Server version
Microsoft SQL Server 2019 (RTM-GDR) (KB5014356) - 15.0.2095.3 (X64) Apr 29 2022 18:00:13 Copyright (C) 2019 Microsoft Corporation Enterprise Edition (64-bit) on Windows Server 2022 Datacenter 10.0 (Build 20348: ) (Hypervisor)
Client Operating System
Edition Windows Server 2022 Datacenter
Version 21H2
Installed on 11/5/2021
OS build 20348.1129
JAVA/JVM version
Oracle Java SE SDK 17.0.7
Problem description
When using a CallableStatement on the client to call a stored procedure
that has a decimal(25,12) parameter, this driver triggers a memory leak
in the SERVER which causes the process managing the connection to the client
to use an increasing amount of memory with each call. Eventually
the database runs out of memory and dies a slow painful death.
This problem was not evident in previous versions of the jdbc driver.
Duplicating the problem
On the server, add the following simple stored procedure:
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[proc_DoNothing]
@lastname nvarchar(256),
@MiDName nvarchar(16),
@firstname nvarchar(256),
@Level int,
@special decimal(25, 12)
AS
BEGIN
SET NOCOUNT ON;
END
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
on the client, run this program:
vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
package company.util.test2;
import java.math.BigDecimal;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
public class FakeServer2
{
}
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Using Sql Management Studio, select the database server and display the activity monitor.
Under processes, find the entry for FakeServer2.
Look at the Memory column, and you will see the value increase without bound.
Workaround
Closing the database connection will alleviate this problem on the server.
But when using a pooled datasource this is not feasible.
You can workaround this issue by passing the big decimal as a String:
BigDecimal bd = new BigDecimal((long) (Math.random() * 100));
if (bd.scale() > 12)
bd.setScale(12);
stmt.setNString(ind++, bd.toPlainString());