Loading...
Loading...
Analyze whether ClickHouse indexes (PRIMARY KEY, ORDER BY, skipping indexes, projections) are being used effectively for actual query patterns. Use when investigating index effectiveness, ORDER BY key design, query-to-index alignment, or when queries scan more data than expected.
npx skill4agent add altinity/skills altinity-expert-clickhouse-index-analysisEXPLAIN indexes = 1
{query_without_format}PrimaryKeytrueGranules: X/YSkipSELECT
{columns} APPLY uniq
FROM {database}.{table}
WHERE {time_column} > now() - INTERVAL {days} DAYWITH
any(query) AS q,
arrayJoin(extractAll(query, '\\b(?:PRE)?WHERE\\s+(.*?)\\s+(?:GROUP BY|ORDER BY|UNION|SETTINGS|FORMAT|$)')) AS w,
arrayFilter(x -> (position(w, extract(x, '\\.(`[^`]+`|[^\\.]+)$')) > 0), columns) AS c,
arrayJoin(c) AS c2
SELECT
c2,
count() AS usage_count
FROM system.query_log
WHERE event_time >= now() - toIntervalDay({days})
AND arrayExists(x -> x LIKE '%{table}%', tables)
AND query ILIKE 'SELECT%'
AND type = 'QueryFinish'
GROUP BY c2
ORDER BY usage_count DESC
FORMAT PrettyCompactMonoBlockWITH
arrayJoin(extractAll(normalizeQuery(query), '\\b(?:PRE)?WHERE\\s+(.*?)\\s+(?:GROUP BY|ORDER BY|UNION|SETTINGS|FORMAT|$)')) AS w
SELECT
w AS where_pattern,
count() AS frequency
FROM system.query_log
WHERE event_time >= now() - toIntervalDay({days})
AND arrayExists(x -> x LIKE '%{table}%', tables)
AND query ILIKE 'SELECT%'
AND type = 'QueryFinish'
GROUP BY w
ORDER BY frequency DESC
LIMIT 20SELECT
query_id,
normalized_query_hash,
selected_parts,
selected_marks,
read_rows,
round(read_rows / nullIf(selected_marks, 0)) AS rows_per_mark,
query_duration_ms,
formatReadableSize(read_bytes) AS read_bytes
FROM system.query_log
WHERE event_time >= now() - toIntervalDay({days})
AND arrayExists(x -> x LIKE '%{table}%', tables)
AND query ILIKE 'SELECT%'
AND type = 'QueryFinish'
ORDER BY selected_marks DESC
LIMIT 20selected_marks-- Table structure with ORDER BY, PRIMARY KEY, indexes
SHOW CREATE TABLE {database}.{table}-- Skipping indexes
SELECT name, type, expr, granularity
FROM system.data_skipping_indices
WHERE database = '{database}' AND table = '{table}'| Query Pattern | Index Support | Action |
|---|---|---|
| Filters on ORDER BY prefix | ✅ Good | None |
| Filters on non-ORDER BY cols | ⚠️ Skip index? | Add bloom_filter or projection |
| Time range + entity | ⚠️ Check order | Time in ORDER BY or partition? |
| High-cardinality first in ORDER BY | ❌ Bad | Reorder (low→high cardinality) |
toDate(ts)| Anti-Pattern | Problem | Fix |
|---|---|---|
| High-cardinality UUID first | No granule skipping | Move after low-cardinality columns |
| DateTime64 microseconds first | Too granular | Use |
| Column in WHERE not in ORDER BY | Full scan | Add to ORDER BY or create projection |
| Bloom filter on ORDER BY column | Redundant | Remove skip index |
| Time not in ORDER BY or partition | Range queries scan all | Add |
entity_typeentitycast_hash(entity_type, entity, cast_hash, ...)