Skip to content

DBA & Monitoring

SkylerX bakes the DBA's everyday "live views" into built-in panels: process list / long transactions / lock waits / replication lag / slow query Top N / server metrics / cluster topology / privileges.

All panels run SQL directly against the target connection — no extra agent, no DB config changes. Every SQL and dialect-routing decision is verifiable against the source under packages/ui/src/components/*Dialog.vue.

Entry points

DBA features have no top-level menu — they're all behind the command palette: ⌘K / Ctrl+K then search. Connection-scoped panels (Server activity, Slow query, Replication lag, OB topology) generate one entry per registered connection — picking one routes the panel to that database.

PanelPalette keywordEntry id
Server activityServer activityact:activity:<connId>
Replication lagReplication lagact:repl:<connId>
Slow query analysisSlow queryact:slowq:<connId>
Operation logOperation logact:oplog
Server monitorServer monitoract:monitor
OceanBase cluster topologyOceanBaseact:obtopo:<connId>
Users & privilegesUsers & privilegesact:privileges

Default palette shortcut: DEFAULT_KEY_BINDINGS.palette = 'CmdOrCtrl+K'; remappable under "Settings → Custom shortcuts".


Server activity

ServerActivityDialog.vue — title Server activity · {conn}. Three tabs, plus a refresh button + auto-refresh dropdown at the top (2s / 5s / 10s / off).

Three panes

Process list (tabProcesses)

Dialect familySQL
MySQLinformation_schema.PROCESSLIST WHERE COMMAND <> 'Sleep' ORDER BY TIME DESC
PostgreSQLpg_stat_activity WHERE state IS NOT NULL AND pid <> pg_backend_pid()
SQL Serversys.dm_exec_sessions JOIN sys.dm_exec_requests + OUTER APPLY sys.dm_exec_sql_text(r.sql_handle)

Column names are normalized in SQL (id / user / host / db / time / state / info) so the three dialects render with a consistent header.

Long transactions (tabLongTx)

Dialect familySQL
MySQLinformation_schema.INNODB_TRX ORDER BY trx_started ASC (returns rows_locked / rows_modified)
PostgreSQLpg_stat_activity WHERE xact_start IS NOT NULL
SQL Serversys.dm_tran_active_transactions JOIN sys.dm_tran_session_transactions

Lock waits (tabLocks)

Dialect familySQL
MySQLperformance_schema.data_lock_waits
PostgreSQLpg_locks JOIN pg_stat_activity "blocked / blocking" self-join
SQL Serversys.dm_tran_locks WHERE request_status = 'WAIT'

KILL action

The Process list and Long transactions tabs have an ✗ KILL button per row. Click → confirm "Terminate session / transaction" → execute per dialect:

Dialect familyKILL syntax
MySQLKILL <id>
PostgreSQLSELECT pg_terminate_backend(<pid>)
SQL ServerKILL <spid>

Lock waits don't offer KILL (you usually want to kill the blocker, which lives in Process list).

Dialect routing

Entry goes through familyOfConn(): first checks dialectKind for NoSQL → reject ('NoSQL dialect not applicable here'); else uses ddl.familyOf(dialect):

  • MySQL family direct hit → MariaDB / TiDB / OceanBase / Doris / StarRocks
  • PG family reuses the PG branch → CockroachDB / Greenplum / OpenGauss / KingbaseES / H2 (ddl.ts groups H2 under PG)
  • SQL Server → mssql branch
  • Others show "this dialect is not supported"

Replication lag

ReplicationLagDialog.vue — title Primary/replica lag · {conn}.

The top shows a dialect badge + role + auto-refresh (5s default; off / 2s / 5s / 10s). Four roles, determined in SQL, color-coded:

RoleDetectionColor
Primary (source)MySQL: any row from SHOW REPLICAS / SHOW SLAVE HOSTS / SHOW BINARY LOG STATUS; PG: rows in pg_stat_replication; MSSQL: local replica role_desc = 'PRIMARY'Green
Replica (replica)MySQL: rows in SHOW REPLICA STATUS / SHOW SLAVE STATUS; PG: pg_is_in_recovery() = true; MSSQL: local role_desc = 'SECONDARY'Blue
Standalone (standalone)All probes emptyGrey
Unknown (unknown)Unsupported dialectGrey

Dialect routing details

MySQL family

Four-step fallback; the first step with rows wins:

  1. SHOW REPLICA STATUS (MySQL 8.0.22+ new name)
  2. SHOW SLAVE STATUS (old name; 5.7 / 8.0 < 22 / MariaDB)
  3. Both empty → try SHOW REPLICAS to list downstream replicas
  4. Fall back to SHOW BINARY LOG STATUS / SHOW MASTER STATUS

Returned columns are reordered so the important ones come first: Channel_Name / Source_Host / Replica_IO_Running / Seconds_Behind_Source / Last_Error etc.

PostgreSQL family

sql
-- 1) standby?
SELECT pg_is_in_recovery() AS is_replica
-- 2a) replica view
SELECT
  EXTRACT(EPOCH FROM (now() - pg_last_xact_replay_timestamp()))::int AS lag_seconds,
  pg_last_wal_receive_lsn()::text,
  pg_last_wal_replay_lsn()::text
-- 2b) primary view
SELECT pid, application_name, state, sync_state,
  EXTRACT(EPOCH FROM write_lag)  AS write_lag_seconds,
  EXTRACT(EPOCH FROM flush_lag)  AS flush_lag_seconds,
  EXTRACT(EPOCH FROM replay_lag) AS replay_lag_seconds,
  sent_lsn, write_lsn, flush_lsn, replay_lsn
FROM pg_stat_replication

SQL Server (AOAG)

sys.dm_hadr_database_replica_states JOIN sys.availability_replicas + sys.dm_hadr_availability_replica_states. Includes synchronization_state_desc / synchronization_health_desc / log_send_queue_size / redo_queue_size / DATEDIFF(SECOND, last_commit_time, GETDATE()) AS lag_seconds.

Without AOAG configured, the role is standalone.

Color thresholds

Constants in code:

ts
const LAG_WARN   = 5    // yellow
const LAG_DANGER = 30   // red

Coloring only applies to lag-second columns: lag_seconds / Seconds_Behind_Source / Seconds_Behind_Master / replay_lag_seconds / write_lag_seconds / flush_lag_seconds.

Error tolerance

looksLikeNoReplication() translates errors containing not configured / not a slave / not a replica / no such / access denied / permission denied / privilege / does not exist into a grey "replication not enabled" notice — avoids a sea of red on permission issues.

Last_Error / Last_IO_Error / Last_SQL_Error non-empty → a red banner at the top.


Slow query analysis

SlowQueryDialog.vue + slowQuery.ts — title Slow query analysis.

This tool is read-only — it doesn't SET any variables for you. Whether slow logging is enabled, how long the retention is, and the sampling threshold are DBA decisions; SkylerX just reads what's already there.

Data sources

Family (slowFamilyOf)DialectsSource
mysqlMySQL / MariaDB / TiDB / OceanBase / Doris / StarRocksperformance_schema.events_statements_summary_by_digest
pgPostgreSQL / CockroachDB / Greenplum / OpenGauss / KingbaseES / Redshiftpg_stat_statements extension
otherOthersShows slowq.unsupported

slowFamilyOf() does not reuse ddl.familyOf() — the latter puts H2 under pg and excludes Redshift, which doesn't match the boundaries this module needs.

Query templates

MySQL — events_statements_summary_by_digest

sql
SELECT
  DIGEST_TEXT AS sql_text,
  COUNT_STAR  AS exec_count,
  ROUND(AVG_TIMER_WAIT/1e9, 2) AS avg_ms,
  ROUND(SUM_TIMER_WAIT/1e9, 2) AS total_ms,
  ROUND(MAX_TIMER_WAIT/1e9, 2) AS max_ms,
  SUM_ROWS_EXAMINED AS rows_examined,
  SUM_ROWS_SENT     AS rows_sent,
  SUM_NO_INDEX_USED AS no_index_count,
  FIRST_SEEN, LAST_SEEN
FROM performance_schema.events_statements_summary_by_digest
WHERE (? IS NULL OR SCHEMA_NAME = ?)
ORDER BY <SUM_TIMER_WAIT | AVG_TIMER_WAIT | COUNT_STAR> DESC
LIMIT 50

*_TIMER_WAIT is in picoseconds (10⁻¹² s); we /1e9 to get ms. The schema param is auto-filled with connection.database.

PostgreSQL — pg_stat_statements

sql
SELECT
  query AS sql_text,
  calls AS exec_count,
  ROUND(mean_exec_time::numeric, 2)  AS avg_ms,
  ROUND(total_exec_time::numeric, 2) AS total_ms,
  ROUND(max_exec_time::numeric, 2)   AS max_ms,
  rows AS rows_sent,
  shared_blks_hit, shared_blks_read
FROM pg_stat_statements
WHERE dbid = (SELECT oid FROM pg_database WHERE datname = current_database())
ORDER BY <total_exec_time | mean_exec_time | calls> DESC NULLS LAST
LIMIT 50

Sort, Top N, "not enabled" hint

  • "Sort by" dropdown at the top: total time / average time / call count — each change re-runs the query with a new ORDER BY, not a frontend sort
  • Default LIMIT 50; code caps at Math.max(1, Math.min(500, limit))
  • Enablement probe: MySQL SHOW VARIABLES LIKE 'slow_query_log', PG SELECT extname FROM pg_extension WHERE extname = 'pg_stat_statements'
  • Probe fails → the list is replaced with a "not enabled" notice with copy-pasteable enable SQL (MySQL: SET GLOBAL slow_query_log = ON; SET GLOBAL long_query_time = 1; …; PG: CREATE EXTENSION IF NOT EXISTS pg_stat_statements; …)

Row expansion actions

Click a row to expand → full SQL + 4 buttons:

ButtonAction
Copynavigator.clipboard.writeText(sql)
Open as queryemit openSql → parent opens a new query tab
Run EXPLAINRuns EXPLAIN <sql> (without trailing ;); result rendered inline. Does not ANALYZE to avoid accidentally writing
AI optimizeemit optimizeWithAi → pushes the SQL into the AI Toolbox's "optimize SQL" task

Operation log

OperationLogDialog.vue — title Operation log. SkylerX's local audit log, not the DB's audit log.

On open, pulls all registered connections and grabs the last 200 entries each from connections.history, then merges by executedAt desc. Each row: success/failure mark + execution time + connection name + duration (ms) + single-line SQL.

Filters

FilterOptions
StatusAll / Success / Failure
ConnectionAll / a specific connection
KeywordCase-insensitive substring on SQL text

Export

"Export CSV" exports the current filtered list as skylerx-operation-log.csv with columns: time, connection, status, duration_ms, sql.

Click a row → emit openSql(connId, sql) to push the SQL into a query tab (closes the dialog).


Cluster topology

Generic ClusterTopologyDialog (TiDB / OceanBase)

ClusterTopologyDialog.vue — two tabs: Nodes / TiKV Stores | Region/Tablet (name switches per dialect).

DialectNodes tabRegions tab
TiDBinformation_schema.cluster_info (tidb / tikv / pd / tiflash)information_schema.tikv_store_status (store_id, address, store_state_name, capacity, available, leader_count, region_count)
OceanBaseoceanbase.DBA_OB_SERVERSFirst oceanbase.GV$OB_TABLET_TO_LS LIMIT 200; on failure fall back to oceanbase.DBA_OB_UNITS
Others'Cluster topology not supported for this dialect'Same

Byte columns (capacity / available / size$) are formatted in the UI as KB / MB / GB / TB.

OceanBase-specific topology

OceanBaseTopologyDialog.vue — title OceanBase cluster topology; entry visible only when the connection dialect is OceanBase.

Top: 4 count cards (Zones / Observers / Tenants / Units). Left: Zone → Observer tree. Right: Tenant → Unit list (expandable). All four views fetch in parallel; on failure a banner appears but the previously successful data stays visible.

ViewSQL
ZonesSELECT zone, status, idc, region FROM oceanbase.DBA_OB_ZONES ORDER BY zone
ObserversSELECT svr_ip, svr_port, zone, status, with_rootserver, build_version, start_service_time FROM oceanbase.DBA_OB_SERVERS ORDER BY zone, svr_ip
TenantsSELECT tenant_id, tenant_name, tenant_type, primary_zone, compatibility_mode, status, locked, locality FROM oceanbase.DBA_OB_TENANTS ORDER BY tenant_id
UnitsSELECT unit_id, resource_pool_id, unit_group_id, tenant_id, zone, svr_ip, svr_port, status FROM oceanbase.DBA_OB_UNITS ORDER BY tenant_id, zone, svr_ip

Status colors: ACTIVE / NORMAL green, INACTIVE / OFFLINE / DELETING red, others yellow. tenant_type uses emoji: 👑 SYS / ⚙ META / 🏢 USER. Click an observer to copy svr_ip:svr_port.

Auto-refresh: off / 5s / 10s / 30s (default off).


Server monitor

ServerMonitorDialog.vue — title Server monitor.

A dropdown switches between supported registered connections. Once started, polls every 2 seconds via setInterval and keeps a 60-point sparkline in memory.

Dialect support

ts
function fam(d) {
  if ([MySQL, MariaDB, OceanBase].includes(d)) return 'mysql'
  if ([PostgreSQL, KingbaseES].includes(d)) return 'pg'
  return 'other'
}

MySQL metrics (SHOW GLOBAL STATUS + SHOW VARIABLES LIKE 'max_connections')

CardSource
UptimeUptime (formatted as Xd Yh Zm)
QPS(Queries/Questions delta) ÷ time delta
ConnectionsThreads_connected / max_connections
RunningThreads_running
Slow queriesSlow_queries
Aborted connectsAborted_connects

PostgreSQL metrics (single aggregate SQL)

sql
SELECT
  (SELECT count(*) FROM pg_stat_activity) AS conns,
  (SELECT count(*) FROM pg_stat_activity WHERE state = 'active') AS active,
  (SELECT count(*) FROM pg_stat_activity WHERE wait_event_type = 'Lock') AS waiting,
  (SELECT sum(xact_commit + xact_rollback) FROM pg_stat_database) AS xacts,
  (SELECT sum(blks_hit) FROM pg_stat_database) AS hit,
  (SELECT sum(blks_read) FROM pg_stat_database) AS rd,
  extract(epoch FROM (now() - pg_postmaster_start_time()))::bigint AS uptime

Cards: Uptime / TPS (xacts delta) / Connections / Running / Waiting on lock / Buffer hit ratio hit / (hit + rd) * 100%.

The bottom sparkline title shows QPS or TPS depending on dialect.


Users & privileges

PrivilegesDialog.vue + privileges.ts — title Users & privileges.

Left column: user/role list. Right column: "Existing grants" / "Build GRANT".

Dialect support

Dialect familyList usersView grants
MySQL (incl. MariaDB / OceanBase)SELECT User, Host FROM mysql.userSHOW GRANTS FOR 'usr'@'host'
PostgreSQL (incl. KingbaseES)SELECT rolname FROM pg_roles WHERE rolcanlogininformation_schema.role_table_grants
Oracle (incl. DM)SELECT username FROM all_users WHERE oracle_maintained = 'N' (12c+)dba_sys_privs ∪ dba_role_privs ∪ dba_tab_privs
SQL Serversys.database_principals WHERE type IN ('S','U','G')sys.database_permissions JOIN sys.database_principals
OthersShows priv.unsupportedNot supported

Oracle grant inspection uses dba_* views — if the connection user lacks DBA role you get ORA-00942; the UI captures and shows the error in the "Existing grants" pane.

GRANT builder

Check privileges + set target + optional WITH GRANT OPTION → live preview, e.g.:

sql
GRANT SELECT, INSERT ON sales.orders TO 'app'@'%' WITH GRANT OPTION;

Preset checklist COMMON_PRIVS = ['SELECT', 'INSERT', 'UPDATE', 'DELETE', 'ALL PRIVILEGES'].

Grantee format per dialect:

DialectGrantee format
MySQL'user'@'host' (host defaults to % when empty)
SQL Server[user] (] escaped to ]])
Oracle"USER" (all upper; " escaped to "")
Others"user"

Doesn't execute for you

SkylerX won't run GRANT / REVOKE for you. Two buttons:

  • Copy → to clipboard
  • Open as query → push to a query tab where you execute it yourself (through SkylerX's SQL channel, which catches prod-tagged connections via production safeguards)

buildRevoke() is exported from privileges.ts but the current UI doesn't have a REVOKE form — just edit the GRANT preview text directly if you need it.


Compatibility matrix

FeatureMySQL famPG famSQL ServerOracle / DMOceanBaseTiDBNoSQL
Server activity: process listinformation_schema.PROCESSLISTpg_stat_activitydm_exec_sessionsvia MySQL branchvia MySQL branchN/A
Server activity: long txINNODB_TRXpg_stat_activitydm_tran_active_transactionsvia MySQL branchvia MySQL branch
Server activity: lock waitsdata_lock_waitspg_locksdm_tran_locksvia MySQL branchvia MySQL branch
KILLKILL <id>pg_terminate_backendKILL <spid>
Replication lagSHOW REPLICA STATUS etc.pg_stat_replication / pg_last_xact_replay_timestampAOAG dm_hadr_database_replica_statesvia MySQL branchvia MySQL branch
Slow queryevents_statements_summary_by_digestpg_stat_statements
Server monitorSHOW GLOBAL STATUSpg_stat_* aggregatevia MySQL branch (KingbaseES via pg)
Cluster topologyDBA_OB_*cluster_info / tikv_store_status
OB topology (dedicated)
Users & privilegesmysql.userpg_rolesdatabase_principalsall_users + dba_*via MySQL branchvia MySQL branch
Operation log (local)

"via X branch" means the dialect is classified into family X by ddl.familyOf() (or slowFamilyOf / fam()), reusing the same SQL — there's no guarantee that every column from every version of the catalog views matches exactly. Doris / StarRocks are MySQL wire-compatible and FE usually exposes events_statements_summary_by_digest; some versions don't, and the panel falls back to the "not enabled" notice.

NoSQL (Redis / MongoDB / Elasticsearch) is short-circuited by dialectKind(NoSql) on the Server activity panel — no SQL is sent; the UI suggests "use ⚙ Server → Client / Slow log". Redis live monitoring is in the dedicated RedisMonitorDialog, out of scope here.

Apache License 2.0 · Wuhan Skyler Network Technology Co., Ltd.