---
title: TABLE_IS_READ_ONLY ClickHouse error
meta:
  description: Learn how to fix the TABLE_IS_READ_ONLY error in ClickHouse and Tinybird. Understand what causes it and see examples of how to resolve it
---

# TABLE_IS_READ_ONLY ClickHouse error

{% callout %}
This error occurs when you try to modify a table that is in read-only mode. This can happen due to system maintenance, permissions, or table engine constraints.
{% /callout %}

The `TABLE_IS_READ_ONLY` error in ClickHouse (and Tinybird) happens when you attempt to perform write operations (INSERT, UPDATE, DELETE, ALTER) on a table that is currently in read-only mode. This can occur due to various reasons including system maintenance, insufficient permissions, table engine limitations, or intentional configuration.

## What causes this error

You'll typically see it when:

* Attempting to INSERT into a read-only table
* Trying to UPDATE or DELETE from a read-only table
* Running ALTER statements on read-only tables
* System maintenance mode is active
* Insufficient user permissions
* Table engine doesn't support writes
* Table is in a read-only state due to configuration

{% callout type="tip" %}
Read-only tables can still be queried with SELECT statements. Only write operations are blocked.
{% /callout %}

## Example errors

```sql {% title="Fails: INSERT into read-only table" %}
INSERT INTO read_only_table (id, name) VALUES (1, 'test')
-- Error: Table is in readonly mode
```

```sql {% title="Fails: UPDATE read-only table" %}
UPDATE read_only_table SET name = 'updated' WHERE id = 1
-- Error: Table is in readonly mode
```

```sql {% title="Fails: DELETE from read-only table" %}
DELETE FROM read_only_table WHERE id = 1
-- Error: Table is in readonly mode
```

```sql {% title="Fails: ALTER read-only table" %}
ALTER TABLE read_only_table ADD COLUMN new_column String
-- Error: Table is in readonly mode
```

## How to fix it

### Check table status

Verify if the table is actually read-only:

```sql {% title="Check table engine and status" %}
SELECT
    database,
    name,
    engine,
    engine_full,
    total_rows,
    total_bytes
FROM system.tables
WHERE database = currentDatabase()
  AND name = 'your_table_name'
```

### Check table permissions

Verify your user permissions:

```sql {% title="Check user permissions" %}
SELECT
    user,
    database,
    table,
    grantee,
    privilege_type
FROM system.grants
WHERE table = 'your_table_name'
```

### Check system settings

Look for read-only configurations:

```sql {% title="Check read-only settings" %}
SELECT
    name,
    value,
    description
FROM system.settings
WHERE name LIKE '%readonly%'
```

### Verify table engine

Check if the table engine supports writes:

```sql {% title="Check table engine capabilities" %}
SELECT
    name,
    engine,
    engine_full
FROM system.tables
WHERE database = currentDatabase()
```

## Common patterns and solutions

### Temporary read-only state

Wait for maintenance to complete:

```sql {% title="Check if maintenance is active" %}
SELECT
    database,
    table,
    partition,
    name,
    active,
    modification_time
FROM system.parts
WHERE database = currentDatabase()
  AND table = 'your_table_name'
```

### Permission issues

Request proper permissions or use appropriate user:

```sql {% title="Check current user" %}
SELECT currentUser() as current_user
```

### Engine limitations

Use appropriate table engines for write operations:

```sql {% title="Create writable table" %}
CREATE TABLE writable_table (
    id UInt64,
    name String,
    created_at DateTime
) ENGINE = MergeTree()
ORDER BY id
```

### System maintenance

Check system status and wait if needed:

```sql {% title="Check system processes" %}
SELECT
    query_id,
    user,
    query,
    state,
    start_time
FROM system.processes
```

## Tinybird-specific notes

In Tinybird, TABLE_IS_READ_ONLY errors often occur when:

* Data Sources are in maintenance mode
* Workspace permissions are restricted
* Data Sources are configured as read-only
* System maintenance is in progress
* API rate limits are exceeded

To debug in Tinybird:

1. Check Data Source status in the UI
2. Verify workspace permissions
3. Check if maintenance is scheduled
4. Contact support if the issue persists

{% callout type="tip" %}
In Tinybird, Data Sources can be temporarily read-only during maintenance or when rate limits are exceeded.
{% /callout %}

## Best practices

### Table design

* Choose appropriate table engines for your use case
* Plan for maintenance windows
* Use proper permissions and access controls
* Document table write requirements

### Maintenance planning

* Schedule maintenance during low-traffic periods
* Communicate read-only periods to users
* Implement proper backup strategies
* Test maintenance procedures

### Error handling

* Implement retry logic for temporary read-only states
* Use appropriate error messages for users
* Monitor for read-only table states
* Have fallback strategies for write operations

## Troubleshooting steps

### Step 1: Verify table state

```sql {% title="Check table status" %}
SELECT
    database,
    name,
    engine,
    total_rows,
    total_bytes
FROM system.tables
WHERE database = currentDatabase()
  AND name = 'your_table_name'
```

### Step 2: Check permissions

```sql {% title="Verify permissions" %}
SELECT
    user,
    database,
    table,
    privilege_type
FROM system.grants
WHERE table = 'your_table_name'
```

### Step 3: Check system status

```sql {% title="System status check" %}
SELECT
    name,
    value
FROM system.settings
WHERE name IN ('readonly', 'readonly_reason')
```

### Step 4: Verify engine support

```sql {% title="Engine capabilities" %}
SELECT
    name,
    engine,
    engine_full
FROM system.tables
WHERE database = currentDatabase()
```

## Alternative solutions

### Use different table

Create a writable table for temporary operations:

```sql {% title="Temporary writable table" %}
CREATE TABLE temp_writable (
    id UInt64,
    data String
) ENGINE = Memory

-- Insert into temporary table
INSERT INTO temp_writable (id, data) VALUES (1, 'test')

-- Later merge with main table when it's writable
INSERT INTO main_table SELECT * FROM temp_writable
```

### Batch operations

Wait for write access and perform batch operations:

```sql {% title="Batch operations when available" %}
-- Check if table is writable
SELECT count() FROM system.tables
WHERE database = currentDatabase()
  AND name = 'your_table_name'

-- If writable, perform batch insert
INSERT INTO your_table_name (id, name, timestamp) VALUES
(1, 'batch1', now()),
(2, 'batch2', now()),
(3, 'batch3', now())
```

### Read-only alternatives

Use read-only operations while waiting:

```sql {% title="Read operations while waiting" %}
-- Analyze data without writing
SELECT
    COUNT(*) as total_records,
    COUNT(DISTINCT user_id) as unique_users,
    MIN(timestamp) as earliest_record,
    MAX(timestamp) as latest_record
FROM read_only_table
WHERE timestamp >= '2024-01-01'
```

## See also

* [Table Engines](/sql-reference/engines)
* [User Permissions](/administration/auth-tokens)
* [System Maintenance](/forward/monitoring)
* [Data Source Management](/forward/get-data-in/data-sources)
