Использование разреженных столбцов для сокращения повторяющихся данных в файлах журналов
Я создаю приложение, которое записывает показатели производительности автомобилей в таблицу, состоящую из 100 столбцов X 500000 строк на сеанс. Некоторые из столбцов очень повторяющиеся (температура охлаждающей жидкости в градусах Фаренгейта). Другие колонки постоянно меняются (опережение зажигания, давление в коллекторе, соотношение воздух-топливо).
Колоночные магазины и сжатие страниц исключены, поскольку проект нацелен на аудиторию с открытым исходным кодом. Он должен поддерживать MS SQL Express Edition или другую бесплатную платформу базы данных, которая хорошо подходит для таблиц больших размеров.
Мое первоначальное решение состоит в том, чтобы разрешить нулевое значение в некоторых столбцах, что должно значительно уменьшить объем памяти, не вставляя повторяющиеся значения, и это позволяет мне увеличить разрешение журнала до более высокой частоты кадров.
Однако это создает препятствие при выборе дискретных строк, потому что некоторые столбцы будут нулевыми на 99%. Поэтому необходимо создать представление (или вычисляемый столбец?), Которое выберет последнюю строку, содержащую значение в этом поле. Мой подход заключается в использовании подзапроса для каждого разреженного столбца. Это кажется крайне не элегантным. Есть ли более эффективный подход, который я должен рассмотреть?
SELECT ISNULL(
val1,
(
SELECT TOP 1 val1
FROM [values] subv
WHERE subv.id <= v.id
AND subv.val1 IS NOT NULL
ORDER BY
subv.id DESC
)
) AS val1,
ISNULL(
val2,
(
SELECT TOP 1 val2
FROM [values] subv
WHERE subv.id <= v.id
AND subv.val2 IS NOT NULL
ORDER BY
subv.id DESC
)
) AS val2,
ISNULL(
val3,
(
SELECT TOP 1 val3
FROM [values] subv
WHERE subv.id <= v.id
AND subv.val3 IS NOT NULL
ORDER BY
subv.id DESC
)
) AS val3
FROM [values] v
2 ответа
Может быть, лучше вместо этого создать новую таблицу, которая содержит только 3 столбца, например MetricType, Metricvalue, MeasurementTime
, Вставляйте новые значения измерений только в том случае, если значение изменилось для определенной метрики.
Я придумал частичное решение.
Моя первая идея состояла в том, чтобы найти способ сохранить значения в скалярной функции между вызовами. Это оказалось невозможным, поскольку параметры копируются в / копируются. Параметры табличных значений являются исключением, но они доступны только для чтения, так что это бесполезно.
Вместо этого я решил пойти с SQL-CLR. Я написал класс.NET на C#, чтобы сохранить значения между вызовами, и сопоставил это со скалярной функцией SQL. Код не подписан, поэтому для запуска:
- База данных должна быть помечена как доверенная
- Сборка CLR должна быть помечена как UNSAFE, чтобы разрешить статические поля
- Также стоит отметить, что это не многопоточное приложение базы данных и не протестировано или не должно работать как таковое
- Также следует отметить, что фатальный недостаток этого решения заключается в том, что оно работает только для последовательных упорядоченных выборок, так что я думаю, что это не слишком большое решение.
Вот код deploy.sql (моя сборка называется SqlClr):
sp_configure 'show advanced options', 1
GO
RECONFIGURE
GO
sp_configure 'clr enabled', 1
GO
RECONFIGURE
GO
USE Test
ALTER DATABASE Test SET TRUSTWORTHY ON
GO
IF OBJECT_ID ('cached_value') is not null
BEGIN
DROP FUNCTION cached_value
END
GO
IF EXISTS (SELECT * FROM sys.assemblies WHERE name = 'SqlClr')
BEGIN
DROP ASSEMBLY SqlClr
END
GO
CREATE ASSEMBLY SqlClr
FROM 'C:\SqlClr\bin\Debug\SqlClr.dll'
WITH PERMISSION_SET = UNSAFE
GO
CREATE FUNCTION cached_value(@rowid bigint, @col int, @current_value bigint)
RETURNS bigint
AS EXTERNAL NAME SqlClr.[SqlClr.StoredProcedures].[cached_value]
Вот код C#.NET:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SqlServer.Server;
using System.Diagnostics;
using System.Data.SqlClient;
namespace SqlClr
{
public class StoredProcedures
{
public static long last_rowid;
public static Dictionary<int, long> values = new Dictionary<int, long>();
[SqlFunction(DataAccess = DataAccessKind.Read, IsDeterministic = false)]
public static Nullable<long> cached_value(long rowid, int col, Nullable<long> current_value)
{
if (rowid < last_rowid)
{
values.Clear();
}
last_rowid = rowid;
if (current_value.HasValue)
{
values[col] = current_value.Value;
return current_value.Value;
}
if (values.ContainsKey(col))
return values[col];
else
return null;
}
}
}
Обратите внимание, что функция.NET должна быть украшена IsDeterministic = false
Наконец, вот пример вызова с использованием функции с 25 столбцами:
SELECT
dbo.cached_value(id, 1, val1) AS val1,
dbo.cached_value(id, 2, val2) AS val2,
......
dbo.cached_value(id, 25, val25) AS val25
FROM LotsOfValues
25 столбцов х 500000 строк, заполненных случайным образом 64-разрядными целыми числами и нулями, select * from LotsOfValues
занимает 7 секунд, чтобы закончить. С использованием dbo.cached_value(id, n, valn)
вызов для всех 25 столбцов, тот же запрос занимает 19 секунд. Я могу обойтись минимум с 50 столбцами, 25 из которых будут редкими, но я думаю, что это увеличит мой потолок до 100. Это приемлемое снижение производительности для приложения, и цель экономии места была достигнута.