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

Что такое натуральный ключ?

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

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

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

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

Натуральный ключ (Natural Key)

Натуральный ключ — это один или несколько столбцов в таблице базы данных, которые естественным образом идентифицируют уникальную запись на основе её реальных данных. Натуральный ключ состоит из столбцов, которые имеют естественное значение в контексте бизнес-логики, в отличие от искусственных/суррогатных ключей (ID, UUID), которые создаются исключительно для идентификации.

Другое название — бизнес-ключ (business key), так как его значения имеют смысл в контексте бизнеса.

Примеры натуральных ключей

Таблица пользователей:

CREATE TABLE users (
    id SERIAL PRIMARY KEY,                    -- Суррогатный ключ
    email VARCHAR(255) UNIQUE NOT NULL,       -- Натуральный ключ
    username VARCHAR(100) UNIQUE NOT NULL,    -- Натуральный ключ
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT NOW()
);

-- Натуральный ключ здесь — это (email) или (username)
-- В реальном мире пользователя идентифицируют по его email, а не по ID

Таблица сотрудников компании:

CREATE TABLE employees (
    id SERIAL PRIMARY KEY,                    -- Суррогатный ключ
    employee_id VARCHAR(20) UNIQUE NOT NULL,  -- Натуральный ключ (табельный номер)
    first_name VARCHAR(100) NOT NULL,
    last_name VARCHAR(100) NOT NULL,
    email VARCHAR(255) UNIQUE,                -- Натуральный ключ
    department_id INT NOT NULL,
    CONSTRAINT uk_employee_id UNIQUE (employee_id),
    CONSTRAINT uk_email UNIQUE (email)
);

-- Натуральные ключи: employee_id, email
-- В компании сотрудника идентифицируют по табельному номеру или email

Таблица с составным натуральным ключом:

CREATE TABLE course_enrollments (
    id SERIAL PRIMARY KEY,                    -- Суррогатный ключ
    student_id INT NOT NULL,
    course_id INT NOT NULL,
    semester VARCHAR(20) NOT NULL,
    grade CHAR(1),
    -- Натуральный ключ: уникальная комбинация студента, курса и семестра
    CONSTRAINT uk_enrollment UNIQUE (student_id, course_id, semester),
    FOREIGN KEY (student_id) REFERENCES students(id),
    FOREIGN KEY (course_id) REFERENCES courses(id)
);

-- Натуральный ключ: (student_id, course_id, semester)
-- В реальности мы идентифицируем запись по этой комбинации

Натуральный ключ vs Суррогатный ключ

Натуральный ключ (Natural Key):

  • Состоит из реальных данных
  • Имеет значение в бизнесе
  • Пример: email пользователя, номер паспорта, ISBN книги
CREATE TABLE books (
    id SERIAL PRIMARY KEY,           -- Суррогатный
    isbn VARCHAR(20) UNIQUE NOT NULL, -- Натуральный
    title VARCHAR(255),
    author VARCHAR(255)
);

Суррогатный ключ (Surrogate Key):

  • Создан искусственно для идентификации
  • Не имеет бизнес-смысла
  • Пример: SERIAL ID, UUID

Использование натурального ключа в Java

Пример с JPA/Hibernate:

import javax.persistence.*;
import java.time.LocalDateTime;

@Entity
@Table(name = "users")
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // Суррогатный ключ
    
    @Column(unique = true, nullable = false)
    private String email; // Натуральный ключ
    
    @Column(unique = true, nullable = false)
    private String username; // Натуральный ключ
    
    private String passwordHash;
    
    private LocalDateTime createdAt;
    
    // Конструктор, геттеры, сеттеры...
    
    public User(String email, String username) {
        this.email = email;
        this.username = username;
    }
}

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
    
    // Поиск по натуральному ключу
    Optional<User> findByEmail(String email);
    Optional<User> findByUsername(String username);
    
    // Проверка существования по натуральному ключу
    boolean existsByEmail(String email);
    boolean existsByUsername(String username);
}

@Service
public class UserService {
    
    @Autowired
    private UserRepository userRepository;
    
    // Поиск по натуральному ключу вместо ID
    public User findUserByEmail(String email) {
        return userRepository.findByEmail(email)
            .orElseThrow(() -> new UserNotFoundException(
                "Пользователь с email " + email + " не найден"
            ));
    }
    
    public boolean isEmailRegistered(String email) {
        return userRepository.existsByEmail(email);
    }
}

Составной натуральный ключ

import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;

// Класс составного натурального ключа
@Embeddable
public class EnrollmentKey implements Serializable {
    
    @Column(name = "student_id")
    private Integer studentId;
    
    @Column(name = "course_id")
    private Integer courseId;
    
    @Column(name = "semester")
    private String semester;
    
    public EnrollmentKey() {}
    
    public EnrollmentKey(Integer studentId, Integer courseId, String semester) {
        this.studentId = studentId;
        this.courseId = courseId;
        this.semester = semester;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        EnrollmentKey that = (EnrollmentKey) o;
        return Objects.equals(studentId, that.studentId) &&
               Objects.equals(courseId, that.courseId) &&
               Objects.equals(semester, that.semester);
    }
    
    @Override
    public int hashCode() {
        return Objects.hash(studentId, courseId, semester);
    }
    
    // Геттеры, сеттеры...
}

@Entity
@Table(name = "course_enrollments")
public class CourseEnrollment {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // Суррогатный ключ
    
    @EmbeddedId
    private EnrollmentKey enrollmentKey; // Натуральный составной ключ
    
    @Column(name = "grade")
    private Character grade;
    
    public CourseEnrollment() {}
    
    public CourseEnrollment(Integer studentId, Integer courseId, 
                           String semester) {
        this.enrollmentKey = new EnrollmentKey(studentId, courseId, semester);
    }
    
    // Геттеры, сеттеры...
}

@Repository
public interface CourseEnrollmentRepository 
        extends JpaRepository<CourseEnrollment, Long> {
    
    // Поиск по натуральному ключу
    Optional<CourseEnrollment> findByEnrollmentKey(EnrollmentKey key);
}

Преимущества натурального ключа

1. Семантика — ключ имеет смысл для людей:

// Понятно, что пользователь идентифицируется по email
User user = userService.findUserByEmail("ivan@example.com");

2. Быстрый поиск — часто ищут по натуральному ключу:

-- Быстрый запрос по натуральному ключу (обычно индексирован)
SELECT * FROM users WHERE email = 'ivan@example.com';

-- Вместо этого, если бы пришлось искать через суррогатный ключ
SELECT * FROM users WHERE id = 12345; -- Нужно сначала найти ID

3. Отсутствие orphaned records — проще контролировать целостность:

CREATE TABLE accounts (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    FOREIGN KEY (email) REFERENCES users(email)
);

-- При удалении пользователя все его счета удалятся автоматически

Недостатки натурального ключа

1. Изменяемость — натуральные ключи могут измениться:

// Email пользователя изменился
// Нужно обновить все внешние ключи, ссылающиеся на этот email
UPDATE user_profiles SET email = 'newemail@example.com' 
WHERE email = 'oldemail@example.com';

2. Сложность — составной натуральный ключ усложняет код:

// Вместо простого findById(1), нужно создавать сложный объект ключа
EnrollmentKey key = new EnrollmentKey(studentId, courseId, semester);
CourseEnrollment enrollment = repository.findByEnrollmentKey(key);

3. Производительность — натуральные ключи обычно больше по размеру:

-- Натуральный ключ (строка, может быть длинным)
CREATE INDEX idx_email ON users(email);

-- Суррогатный ключ (обычно 8 байт BIGINT)
CREATE INDEX idx_id ON users(id);

Best Practice: гибридный подход

import javax.persistence.*;

@Entity
@Table(name = "users",
    uniqueConstraints = @UniqueConstraint(columnNames = "email")
)
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; // Суррогатный ключ — для внешних ключей
    
    @Column(unique = true, nullable = false)
    private String email; // Натуральный ключ — для бизнес-логики
    
    private String username;
    private String passwordHash;
    
    // В большинстве современных приложений используют оба ключа
}

// Использование:
UserRepository repository = ...; // autowired

// Внутренние операции часто используют ID для производительности
User user = repository.findById(1L); // быстро

// Бизнес-логика использует натуральный ключ для смысла
User user = repository.findByEmail("ivan@example.com"); // понятно

В современной Java практике обычно используют оба типа ключей одновременно: суррогатный ключ (ID) для внутренних операций и производительности, а натуральный ключ для бизнес-логики и запросов пользователей.

Что такое натуральный ключ? | PrepBro