Ticket Hash: | be12568e30af55ac0f31ee1109ebce0e0a2bc3a3 | ||
Title: | Performance degrade in Latest SQLite versions (1.0.113) | ||
Status: | Closed | Type: | Performance |
Severity: | Important | Priority: | Medium |
Subsystem: | Connection | Resolution: | Fixed |
Last Modified: | 2020-10-29 02:37:03 | ||
Version Found In: | 1.0.113 |
User Comments: | |||||||||||||||||||||||||
anonymous added on 2020-09-23 11:54:52:
We are able to notice a significant degrade in latest SQLite packages (1.0.113) due to some of the newly added features like .xml/Environment variable configurations. As part of migrating to latest SQLite (1.0.113) from our existing version (1.0.74) we encounter a lot of issues related to performance. We see latest version taking about 2-3 times more time than our existing version. On analysis we see the below issues that needs some attention :
Please see the below table for a comparison on how the performance has degraded. [The numbers are for 100 iterations of each operations.]
These reads are happening in a connection open, which results in degrading the performance of almost all the different Sqlite operations (Like Insert, Select etc) |
Scenario | version 1.0.74 | version 1.0.113 |
---|---|---|
Open | 8ms | 25ms |
Close | 2ms | 6ms |
Insert | 10720ms | 27937ms |
Flat Query | 20ms | 40ms |
Update | 15ms | 35ms |
Delete | 3768ms | 3598ms |
We have the profiles taken during measurement which can help in better understanding where the problem is. Get in touch for more details. We are ready to help. Find the code we used for reference below.
Pre-Requisite : An Sqlite DB with 3 tables, with 2000 records each and each table having 20 columns. Compile the same code with different versions of SQLite.
Thanks in Advance!
using System; using System.Collections.Generic; using System.Data.SQLite; using System.IO; using System.Linq; using System.Text; namespace SqlitePerformance { public static class SqlitePerformanceMeasurement { private static long InsertTimeInMs; private static long CreateTimeInMs; private static long UpdateTimeInMs; private static long DeleteTimeInMs; private static readonly string broadQueryCommandText = "SELECT storables from Table2"; private static readonly string queryWithWhereClause = "select storables from Table2 where col1='1.3.46.670589.5.2.10.2.4.46.678.1.6072.1211920446095.5' AND deviceId='SQLiteDatabase';"; public static string ConnectionString { get; set; } = "Data Source=" + Path.Combine(Path.GetTempPath(), "SpikeSqlite.db") + ";Version=3;foreign keys = True;Journal Mode = Wal;Pooling=True;Max Pool Size = 100"; public static string InsertConnectionString { get; set; } = "Data Source=" + Path.Combine(Path.GetTempPath(), "SpikeInsert.db") + ";Version=3;foreign keys = True;Journal Mode = Wal;Pooling=True;Max Pool Size = 100"; public static void BroadQuery(int numberOfQueries) { ActualQuery(numberOfQueries, broadQueryCommandText); } public static void QueryWithWhereClause(int numberOfQueries) { ActualQuery(numberOfQueries, queryWithWhereClause); } public static void ActualQuery(int numberOfConnections, string cmdText) { for (int i = 0; i < numberOfConnections; i++) { using (var con = new SQLiteConnection(ConnectionString)) { con.Open(); using (var cmd = new SQLiteCommand(cmdText, con)) { cmd.ExecuteReader(); } con.Close(); } } } ////// Method that prints the version of SQLite assembly loaded. /// public static void GetSqliteVersionLoaded() { using (var conn = new SQLiteConnection(ConnectionString)) { conn.Open(); var cmd = new SQLiteCommand("select sqlite_version();", conn); var version = cmd.ExecuteScalar().ToString(); Console.WriteLine(version); conn.Close(); } } private static DateTime dateTime = DateTime.Now; private static string updateCommandText = $"UPDATE Table2 SET col1='{dateTime}', storables=@storables WHERE col3='1.3.46.670589.5.2.10.2.4.46.678.1.6072.1211920446095.5' AND deviceId='SQLiteDatabase'"; public static void UpdateColumns(int numberOfIterations) { for (int i = 0; i < numberOfIterations; i++) { using (var con = new SQLiteConnection(ConnectionString)) { con.Open(); using (var cmd = new SQLiteCommand(updateCommandText, con)) { cmd.Parameters.AddWithValue("@storables", DummyStorables.Create(i)); cmd.ExecuteNonQuery(); } } } } public static void DeleteRow(int numberOfIterations) { var conString = "Data Source=" + Path.Combine(Path.GetTempPath(), "SpikeSqlite-Clone.db") + ";Version=3;foreign keys = True; Pooling=True;Max Pool Size = 100;Journal Mode = Wal"; var rows = File.ReadAllLines(Path.Combine(Path.GetTempPath(), "DeleteRows.txt")); for (int i = 0; i < numberOfIterations; i++) { var commandText = $"DELETE FROM Table2 where uid='{rows[i].Trim('\"')}' AND deviceId='SQLiteDatabase';"; ActualDelete(conString, commandText); } } private static void ActualDelete(string conString, string commandText) { using (var con = new SQLiteConnection(conString)) { con.Open(); using (var cmd = new SQLiteCommand(commandText, con)) { cmd.ExecuteNonQuery(); } con.Close(); } } public static void Show() { var builder = new StringBuilder(); builder.Append("--- SQLite Performance Spike ---"); builder.AppendLine("Choose the operation to perform"); builder.AppendLine("1. Connection Open."); builder.AppendLine("2. Broad Query."); builder.AppendLine("3. Query with where clause."); builder.AppendLine("4. Update Row."); builder.AppendLine("5. Delete Row."); builder.AppendLine("6. Insert Row."); Console.WriteLine(builder.ToString()); var operation = int.Parse(Console.ReadLine() ?? throw new InvalidOperationException()); builder.Clear(); builder.Append("How many iterations? : "); Console.WriteLine(builder.ToString()); var iterations = int.Parse(Console.ReadLine() ?? throw new InvalidOperationException()); switch (operation) { case 1: MultipleOpenCLoseConnections(iterations); break; case 2: BroadQuery(iterations); break; case 3: QueryWithWhereClause(iterations); break; case 4: UpdateColumns(iterations); break; case 5: DeleteRow(iterations); break; case 6: InsertOperation(iterations); break; default: throw new InvalidOperationException("Invalid input"); } } private static void InsertOperation(int iterations) { foreach (var level in new[] { "TABLE1", "TABLE2", "TABLE3" }) { InsertOperation(GetDbData(level), level); } } private static void InsertOperation(List> data, string level) { var columns = new List (); foreach (var d in data.First()) { columns.Add(d.Key); } var insertCols = new StringBuilder(); foreach (var column in columns) { insertCols.Append(column); insertCols.Append(","); } var valuesCols = new StringBuilder(); foreach (var column in columns) { valuesCols.Append("@"); valuesCols.Append(column); valuesCols.Append(","); } var insertCommandText = $"Insert into {level} ({insertCols.ToString().TrimEnd(',')}) values ({valuesCols.ToString().TrimEnd(',')});"; ActualInsert(insertCommandText, data); } /// /// Create connection, Open it, Insert the row, Close and Dispose the connection. /// /// /// private static void ActualInsert(string cmdText, List> data) { // Insert each row foreach (var eachRow in data) { using (var con = new SQLiteConnection(InsertConnectionString)) { con.Open(); using (var cmd = con.CreateCommand()) { cmd.CommandText = cmdText; // Populate column values foreach (var eachItem in eachRow) { cmd.Parameters.AddWithValue("@" + eachItem.Key, eachItem.Value); } cmd.CommandText += "; Select last_insert_rowid()"; // Insert cmd.ExecuteScalar(); cmd.Parameters.Clear(); } } } } /// /// Utility to get the data from the table to use and delete the data from the table. /// /// ///private static List > GetDbData(string level) { var resultList = new List >(); using (var con = new SQLiteConnection(ConnectionString)) { con.Open(); using (var cmd = new SQLiteCommand($"SELECT * from {level}", con)) { var reader = cmd.ExecuteReader(); while (reader.Read()) { var tagValue = new Dictionary (); for (int i = 0; i < reader.FieldCount; i++) { tagValue.Add(reader.GetName(i), reader.GetValue(i)); } resultList.Add(tagValue); } } con.Close(); } return resultList; } /// /// Creates, Opens and Closes multiple connections. /// /// private static void MultipleOpenCLoseConnections(int iterations) { SQLiteConnection[] connections = new SQLiteConnection[iterations]; for (int i = 0; i < iterations; i++) { connections[i] = new SQLiteConnection(ConnectionString); } for (int i = 0; i < iterations; i++) { using (var con = connections[i]) { con.Open(); con.Close(); } } } } ////// Dummy class to generate Byte array that is stored as blob /// internal static class DummyStorables { public static IReadOnlyCollectionCreate(int seed) { var rand = new Random(Seed: seed); var byteArray = new byte[2020]; rand.NextBytes(byteArray); return byteArray; } } }
mistachkin added on 2020-09-24 22:14:50:
Thanks for the detailed report. I was able to find several places where unnecessary work was being performed during SQLiteConnection construction. These have been addressed via trunk check-in [438037ced66e89a2]. I'm not sure how to optimize the Open method without losing significant features and/or breaking backwards compatibility. As far as the connection pool, it was designed primarily for thread safety and friendliness to the .NET garbage collector. It is extremely sensitive code in terms of correctness and thread safety. I'm not sure how to make it faster; however, I'm open to any specific suggestions you may have. I'm curious, why create and open so many connections? Part of the problem here is that the SQLiteConnection constructor and Open / Close methods are not designed to be used so frequently. Parsing a connection string is not cheap in the best of circumstances.
mistachkin added on 2020-10-29 02:37:03:
I believe most of the big performance issues described in this ticket are now fixed.