← Назад к вопросам

Что такое курсор в базе данных?

2.3 Middle🔥 101 комментариев
#Базы данных и SQL

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI23 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Курсор в базе данных

Курсор (Cursor) — это объект в базе данных, который действует как временный указатель на результаты SQL запроса. Курсор позволяет обрабатывать результаты построчно, вместо того чтобы загружать все результаты сразу в память.

Как работает курсор

Курсор функционирует как указатель, который:

  1. Выполняет запрос и сохраняет результаты в буфере
  2. Указывает на текущую строку в наборе результатов
  3. Перемещается по строкам (fetch next row)
  4. Закрывается после обработки всех данных
public class CursorExample {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/mydb";
        String query = "SELECT id, name, salary FROM employees WHERE dept_id = 10";
        
        try (Connection conn = DriverManager.getConnection(url, "user", "pass");
             Statement stmt = conn.createStatement();
             ResultSet rs = stmt.executeQuery(query)) {
            
            while (rs.next()) {
                int id = rs.getInt("id");
                String name = rs.getString("name");
                double salary = rs.getDouble("salary");
                System.out.printf("ID: %d, Name: %s, Salary: %.2f%n", id, name, salary);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Типы курсоров

1. Forward-Only Cursor (Однонаправленный)

Может двигаться только вперед (FETCH NEXT):

Statement stmt = conn.createStatement(
    ResultSet.TYPE_FORWARD_ONLY,
    ResultSet.CONCUR_READ_ONLY
);

Преимущества: быстрый, мало памяти

2. Static Cursor (Статический)

Зафиксирует снимок данных на момент открытия курсора:

Statement stmt = conn.createStatement(
    ResultSet.TYPE_SCROLL_INSENSITIVE,
    ResultSet.CONCUR_READ_ONLY
);

3. Scroll Cursor (Прокручиваемый)

Позволяет двигаться в любом направлении:

Statement stmt = conn.createStatement(
    ResultSet.TYPE_SCROLL_SENSITIVE,
    ResultSet.CONCUR_UPDATABLE
);

ResultSet rs = stmt.executeQuery(query);
rs.next();
rs.previous();
rs.absolute(10);
rs.first();
rs.last();

Движение по ResultSet

ResultSet rs = stmt.executeQuery(query);

rs.next();
rs.previous();
rs.relative(5);
rs.absolute(10);
rs.first();
rs.last();

if (rs.isBeforeFirst()) System.out.println("Перед первой");
if (rs.isAfterLast()) System.out.println("После последней");
if (rs.isFirst()) System.out.println("На первой");

Обновляемые курсоры

public class UpdatableCursor {
    public static void main(String[] args) {
        String url = "jdbc:postgresql://localhost:5432/mydb";
        String query = "SELECT id, name, salary FROM employees FOR UPDATE";
        
        try (Connection conn = DriverManager.getConnection(url, "user", "pass");
             Statement stmt = conn.createStatement(
                 ResultSet.TYPE_SCROLL_SENSITIVE,
                 ResultSet.CONCUR_UPDATABLE);
             ResultSet rs = stmt.executeQuery(query)) {
            
            while (rs.next()) {
                String name = rs.getString("name");
                double salary = rs.getDouble("salary");
                double newSalary = salary * 1.1;
                
                rs.updateDouble("salary", newSalary);
                rs.updateRow();
                
                System.out.printf("%s: %.2f to %.2f%n", name, salary, newSalary);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

Курсоры в хранимых процедурах

public class StoredProcedureCursor {
    public static void main(String[] args) {
        try (Connection conn = getConnection();
             CallableStatement call = conn.prepareCall("{call process_employees()}")) {
            
            call.execute();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

SQL пример (PostgreSQL):

CREATE PROCEDURE process_employees()
LANGUAGE plpgsql
AS $$
DECLARE
    emp_cursor CURSOR FOR
        SELECT id, name, salary FROM employees;
    emp_id INT;
    emp_name VARCHAR;
BEGIN
    OPEN emp_cursor;
    LOOP
        FETCH emp_cursor INTO emp_id, emp_name;
        EXIT WHEN NOT FOUND;
        RAISE NOTICE 'Employee: %', emp_name;
    END LOOP;
    CLOSE emp_cursor;
END;
$$;

Когда использовать курсоры

Используй курсоры когда:

  1. Нужна построчная обработка (не пакетная)
  2. Работаешь с очень большими наборами данных
  3. Логика сложная и зависит от каждой строки
  4. Нужна возможность обновления во время обработки
ResultSet rs = stmt.executeQuery(
    "SELECT id, text, score FROM reviews WHERE processed = false");

while (rs.next()) {
    String text = rs.getString("text");
    int score = rs.getInt("score");
    String sentiment = analyzeSentiment(text);
    int updatedScore = calculateScore(score, sentiment);
    
    rs.updateInt("score", updatedScore);
    rs.updateBoolean("processed", true);
    rs.updateRow();
}

НЕ используй курсоры когда:

  1. Можно использовать SQL операции (JOIN, GROUP BY, UPDATE)
  2. Нужны просто все данные — загрузи их сразу
  3. Нужна высокая производительность

Производительность

// Медленно (построчное обновление)
while (rs.next()) {
    int id = rs.getInt("id");
    PreparedStatement update = conn.prepareStatement("UPDATE ... WHERE id = ?");
    update.setInt(1, id);
    update.executeUpdate();
}

// Быстро (пакетное обновление)
String updateSql = "UPDATE employees SET salary = salary * 1.1 WHERE dept = ?";
PreparedStatement update = conn.prepareStatement(updateSql);
update.setString(1, "IT");
update.executeUpdate();

Закрытие курсора

try (ResultSet rs = stmt.executeQuery(query)) {
    while (rs.next()) {
        // обработка
    }
} // Автоматически закрывается при выходе из try-with-resources

// Или вручную
rs.close();
stmt.close();

Выводы

  • Курсор — указатель на результаты запроса
  • Forward-only — только вперед, быстро
  • Scroll — в любом направлении
  • Updatable — позволяет обновлять данные
  • В Java: ResultSet — это курсор
  • Best practice: используй SQL для пакетных операций, курсоры для сложной логики
  • Всегда закрывай курсоры (try-with-resources автоматизирует это)