Как обрабатывать ошибки в mysql

В этой статье мы расскажем вам, как использовать обработчик MySQL для обработки исключений или ошибок, возникающих в хранимых процедурах.

При возникновении ошибки внутри хранимой процедуры, важно исправить ее соответствующим образом, например, продолжая или останавливая исполнение операции и выдавая сообщение об ошибке.

MySQL предоставляет простой способ определить обработчики, которые обрабатывают ошибки, исходя из общих условий, таких как предупреждения или исключения из условий, например, конкретные коды ошибок.

  • Объявление обработчика
  • Примеры обработки ошибок MySQL
  • Пример обработчика MySQL в хранимых процедурах
    • Приоритет обработчиков MySQL
    • Использование проименованных условий ошибки

Чтобы объявить обработчик мы используем оператор DECLARE HANDLER:

DECLARE action HANDLER FOR condition_value statement;

Если значение условия совпадает со значением condition_value, MySQL выполнит оператор statement и продолжит или завершит текущий блок кода, исходя из значения action.

action может принимать следующие значения:

  • CONTINUE: исполнение блокированного кода (BEGIN … END) продолжается;
  • EXIT: выполнение блокированного кода, в котором был объявлен обработчик, завершается.

condition_value задает конкретное условие или класс условия, которые активируют обработчик.

condition_value может принимать одно из следующих значений:

  • код ошибки MySQL;
  • стандартное значение SQLSTATE. Или это может быть условие SQLWARNING, NOTFOUND или SQLEXCEPTION, которое является сокращением для класса значений SQLSTATE. Условие NOTFOUND используется для курсора или оператора SELECT INTO variable_list;
  • название условия, связанного либо с кодом ошибки MySQL, либо со значением SQLSTATE.

В качестве statement может использоваться простой оператор или составной оператор, вшитый с помощью ключевых слов BEGIN и END.

Давайте рассмотрим несколько примеров объявления обработчиков.

Обработчик, приведенный ниже, означает: когда происходит ошибка, устанавливается значение переменной has_error 1 и выполнение продолжается:

DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET has_error = 1;

Ниже приводится другой обработчик, который означает, что в случае возникновении любой ошибки, производится откат предыдущей операции, выдается сообщение об ошибке и осуществляется выход из текущего блока кода.

Если вы объявляете его внутри блока BEGIN END хранимой процедуры, он немедленно завершает хранимую процедуру:

DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 'An error has occurred, operation rollbacked and the stored procedure was terminated';
END;

Если строк для вывода больше нет, для вариантов cursor или оператора SELECT INTO, значение переменной no_row_found устанавливается равным 1 и продолжается исполнение:

DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_row_found = 1;

При возникновении ошибки дублирования ключа, выдается ошибка MySQL 1062. Следующий обработчик выдает сообщение об ошибке и продолжает выполнение:

DECLARE CONTINUE HANDLER FOR 1062
SELECT 'Error, duplicate key occurred';

Во-первых, для демонстрации мы создаем новую таблицу с именем article_tags:

CREATE TABLE article_tags(
    article_id INT,
    tag_id     INT,
    PRIMARY KEY(article_id,tag_id)
);

В таблице article_tags хранятся связи между статьями и тегами. К каждой статье может относиться несколько тегов и наоборот.

Для простоты, мы не будем создавать таблицы articles и tags, а также внешние ключи в таблице article_tags.

Во-вторых, мы создаем хранимую процедуру, которая вставляет пару идентификаторов статьи и тега в таблицу article_tags:

DELIMITER $$
 
CREATE PROCEDURE insert_article_tags(IN article_id INT, IN tag_id INT)
BEGIN
 
    DECLARE CONTINUE HANDLER FOR 1062
    SELECT CONCAT('duplicate keys (',article_id,',',tag_id,') found') AS msg;
 
    -- insert a new record into article_tags
    INSERT INTO article_tags(article_id,tag_id)
    VALUES(article_id,tag_id);
 
    -- return tag count for the article
    SELECT COUNT(*) FROM article_tags;
END

В-третьих, для статьи 1 мы добавляем идентификаторы тега 1, 2 и 3, с помощью вызова хранимой процедуры insert_article_tags:

CALL insert_article_tags(1,1);
CALL insert_article_tags(1,2);
CALL insert_article_tags(1,3);

Четвертое. Давайте попробуем вставить дубликат ключа, чтобы увидеть, действительно ли вызывается обработчик:

CALL insert_article_tags(1,3);

Мы получили сообщение об ошибке. Однако, поскольку мы объявили тип обработчика CONTINUE, хранимая процедура продолжает исполняться.

В результате, мы все равно получили список тегов для статьи:

Пример обработчика MySQL в хранимых процедурах

Если мы в объявлении обработчика изменим команду CONTINUE на EXIT, мы получим только сообщение об ошибке:

DELIMITER $$
 
CREATE PROCEDURE insert_article_tags_2(IN article_id INT, IN tag_id INT)
BEGIN
 
    DECLARE EXIT HANDLER FOR SQLEXCEPTION 
    SELECT 'SQLException invoked';
 
    DECLARE EXIT HANDLER FOR 1062 
        SELECT 'MySQL error code 1062 invoked';
 
    DECLARE EXIT HANDLER FOR SQLSTATE '23000'
    SELECT 'SQLSTATE 23000 invoked';
 
    -- insert a new record into article_tags
    INSERT INTO article_tags(article_id,tag_id)
       VALUES(article_id,tag_id);
 
    -- return tag count for the article
    SELECT COUNT(*) FROM article_tags;
END

Теперь, мы можем попробовать добавить дубликат ключа, чтобы увидеть результат:

CALL insert_article_tags_2(1,3);

Пример обработчика MySQL в хранимых процедурах - 2

В случае если у вас есть несколько обработчиков, которые имеют право обрабатывать ошибку, MySQL для обработки ошибки будет вызывать наиболее подходящий обработчик.

Ошибка всегда обозначается одним из кодов ошибки MySQL, так что MySQL в этом плане имеет возможность четко их идентифицировать.

Обозначения SQLSTATE для многих кодов ошибок MySQL менее специфичны. SQLEXCPETION или SQLWARNING представляют собой сокращения класса значений SQLSTATES, поэтому они имеют общий характер.

На основании правил приоритета обработчиков обработчик кода ошибки MySQL, обработчик SQLSTATE и обработчик SQLEXCEPTION имеют приоритеты один, два и три соответственно.

Предположим, что в хранимой процедуре insert_article_tags_3 мы объявляем три обработчика:

DELIMITER $$
 
CREATE PROCEDURE insert_article_tags_3(IN article_id INT, IN tag_id INT)
BEGIN
 
    DECLARE EXIT HANDLER FOR 1062 SELECT 'Duplicate keys error encountered';
    DECLARE EXIT HANDLER FOR SQLEXCEPTION SELECT 'SQLException encountered';
    DECLARE EXIT HANDLER FOR SQLSTATE '23000' SELECT 'SQLSTATE 23000';
 
    -- insert a new record into article_tags
    INSERT INTO article_tags(article_id,tag_id)
    VALUES(article_id,tag_id);
 
    -- return tag count for the article
    SELECT COUNT(*) FROM article_tags;
END

Теперь мы пробуем добавить в таблицу article_tags дубликат ключа через вызов хранимой процедуры:

CALL insert_article_tags_3(1,3);

Как видите, вызывается обработчик кода ошибки MySQL:

Приоритет обработчиков MySQL

Начинаем с объявления обработчика ошибки:

DECLARE EXIT HANDLER FOR 1051 SELECT 'Please create table abc first';
SELECT * FROM abc;

Что означает код 1051? Представьте, что у вас есть большая хранимая процедура, по всему коду которой разбросаны некорректные значения. Настоящий кошмар для разработчиков обслуживания.

К счастью, MySQL предоставляет нам оператор DECLARE CONDITION, который объявляет проименованное условие ошибки, связанное с условием.

Синтаксис оператора DECLARE CONDITION выглядит следующим образом:

DECLARE condition_name CONDITION FOR condition_value;

condition_value может представлять собой код ошибки MySQL, например 1015, или значение SQLSTATE. condition_value представляется с помощью condition_name.

После объявления вы можете обращаться к condition_name вместо condition_value.

Таким образом, мы можем переписать код, приведенный выше, следующим образом:

DECLARE table_not_found CONDITION for 1051;
DECLARE EXIT HANDLER FOR  table_not_found SELECT 'Please create table abc first';
SELECT * FROM abc;

Этот код, очевидно, более удобен для чтения, нежели предыдущий. Отметим, что объявление условия должно размещаться перед объявлением обработчика или объявлением курсора.

Summary: in this tutorial, you will learn how to use MySQL handler to handle errors encountered in stored procedures.

When an error occurs inside a stored procedure, it is important to handle it appropriately, such as continuing or exiting the current code block’s execution, and issuing a meaningful error message.

MySQL provides an easy way to define handlers that handle from general conditions such as warnings or exceptions to specific conditions e.g., specific error codes.

Declaring a handler

To declare a handler, you use the  DECLARE HANDLER statement as follows:

DECLARE action HANDLER FOR condition_value statement;Code language: SQL (Structured Query Language) (sql)

If a condition whose value matches the  condition_value , MySQL will execute the statement and continue or exit the current code block based on the action .

The action accepts one of the following values:

  • CONTINUE :  the execution of the enclosing code block ( BEGINEND ) continues.
  • EXIT : the execution of the enclosing code block, where the handler is declared, terminates.

The  condition_value specifies a particular condition or a class of conditions that activate the handler. The  condition_value accepts one of the following values:

  • A MySQL error code.
  • A standard SQLSTATE value. Or it can be an SQLWARNING , NOTFOUND or SQLEXCEPTION condition, which is shorthand for the class of SQLSTATE values. The NOTFOUND condition is used for a cursor or  SELECT INTO variable_list statement.
  • A named condition associated with either a MySQL error code or SQLSTATE value.

The statement could be a simple statement or a compound statement enclosing by the BEGIN and END keywords.

MySQL error handling examples

Let’s take some examples of declaring handlers.

The following handler set the value of the  hasError variable to 1 and continue the execution if an SQLEXCEPTION occurs

DECLARE CONTINUE HANDLER FOR SQLEXCEPTION 
SET hasError = 1;Code language: SQL (Structured Query Language) (sql)

The following handler rolls back the previous operations, issues an error message, and exit the current code block in case an error occurs. If you declare it inside the BEGIN END block of a stored procedure, it will terminate the stored procedure immediately.

DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
    ROLLBACK;
    SELECT 'An error has occurred, operation rollbacked and the stored procedure was terminated';
END;Code language: SQL (Structured Query Language) (sql)

The following handler sets the value of the  RowNotFound variable to 1 and continues execution if there is no more row to fetch in case of a cursor or SELECT INTO statement:

DECLARE CONTINUE HANDLER FOR NOT FOUND 
SET RowNotFound = 1;Code language: SQL (Structured Query Language) (sql)

If a duplicate key error occurs, the following handler issues an error message and continues execution.

DECLARE CONTINUE HANDLER FOR 1062
SELECT 'Error, duplicate key occurred';Code language: SQL (Structured Query Language) (sql)

MySQL handler example in stored procedures

First, create a new table named SupplierProductsfor the demonstration:

CREATE TABLE SupplierProducts (
    supplierId INT,
    productId INT,
    PRIMARY KEY (supplierId , productId)
);Code language: SQL (Structured Query Language) (sql)

The table SupplierProducts stores the relationships between the table suppliers and products. Each supplier may provide many products and each product can be provided by many suppliers. For the sake of simplicity, we don’t create Products and Suppliers tables, as well as the foreign keys in the  SupplierProducts table.

Second, create a stored procedure that inserts product id and supplier id into the SupplierProducts table:

CREATE PROCEDURE InsertSupplierProduct(
    IN inSupplierId INT, 
    IN inProductId INT
)
BEGIN
    -- exit if the duplicate key occurs
    DECLARE EXIT HANDLER FOR 1062
    BEGIN
 	SELECT CONCAT('Duplicate key (',inSupplierId,',',inProductId,') occurred') AS message;
    END;
    
    -- insert a new row into the SupplierProducts
    INSERT INTO SupplierProducts(supplierId,productId)
    VALUES(inSupplierId,inProductId);
    
    -- return the products supplied by the supplier id
    SELECT COUNT(*) 
    FROM SupplierProducts
    WHERE supplierId = inSupplierId;
    
END$$

DELIMITER ;Code language: SQL (Structured Query Language) (sql)

How it works.

The following exit handler terminates the stored procedure whenever a duplicate key occurs (with code 1062). In addition, it returns an error message.

DECLARE EXIT HANDLER FOR 1062
BEGIN
    SELECT CONCAT('Duplicate key (',supplierId,',',productId,') occurred') AS message;
END;Code language: SQL (Structured Query Language) (sql)

This statement inserts a row into the SupplierProducts table. If a duplicate key occurs, the code in the handler section will execute.

INSERT INTO SupplierProducts(supplierId,productId) 
VALUES(supplierId,productId);Code language: SQL (Structured Query Language) (sql)

Third, call the InsertSupplierProduct() to insert some rows into the SupplierProducts table:

CALL InsertSupplierProduct(1,1);
CALL InsertSupplierProduct(1,2);
CALL InsertSupplierProduct(1,3);Code language: SQL (Structured Query Language) (sql)

Fourth, attempt to insert a row whose values already exist in the SupplierProducts table:

CALL InsertSupplierProduct(1,3);Code language: SQL (Structured Query Language) (sql)

Here is the error message:

+------------------------------+
| message                      |
+------------------------------+
| Duplicate key (1,3) occurred |
+------------------------------+
1 row in set (0.01 sec)Code language: JavaScript (javascript)

Because the handler is an EXIT handler, the last statement does not execute:

SELECT COUNT(*) 
FROM SupplierProducts
WHERE supplierId = inSupplierId;Code language: SQL (Structured Query Language) (sql)

If  you change the EXIT in the handler declaration to CONTINUE , you will also get the number of products provided by the supplier:

DROP PROCEDURE IF EXISTS InsertSupplierProduct;

DELIMITER $$

CREATE PROCEDURE InsertSupplierProduct(
    IN inSupplierId INT, 
    IN inProductId INT
)
BEGIN
    -- exit if the duplicate key occurs
    DECLARE CONTINUE HANDLER FOR 1062
    BEGIN
	SELECT CONCAT('Duplicate key (',inSupplierId,',',inProductId,') occurred') AS message;
    END;
    
    -- insert a new row into the SupplierProducts
    INSERT INTO SupplierProducts(supplierId,productId)
    VALUES(inSupplierId,inProductId);
    
    -- return the products supplied by the supplier id
    SELECT COUNT(*) 
    FROM SupplierProducts
    WHERE supplierId = inSupplierId;
    
END$$

DELIMITER ;Code language: SQL (Structured Query Language) (sql)

Finally, call the stored procedure again to see the effect of the CONTINUE handler:

CALL InsertSupplierProduct(1,3);Code language: SQL (Structured Query Language) (sql)

Here is the output:

+----------+
| COUNT(*) |
+----------+
|        3 |
+----------+
1 row in set (0.01 sec)

Query OK, 0 rows affected (0.02 sec)Code language: SQL (Structured Query Language) (sql)

MySQL handler precedence

In case you have multiple handlers that handle the same error, MySQL will call the most specific handler to handle the error first based on the following rules:

  • An error always maps to a MySQL error code because in MySQL it is the most specific.
  • An SQLSTATE may map to many MySQL error codes, therefore, it is less specific.
  • An SQLEXCPETION or an SQLWARNING is the shorthand for a class of SQLSTATES values so it is the most generic.

Based on the handler precedence rules,  MySQL error code handler, SQLSTATE handler and SQLEXCEPTION takes the first, second and third precedence.

Suppose that we have three handlers in the handlers in the stored procedure insert_article_tags_3 :

DROP PROCEDURE IF EXISTS InsertSupplierProduct;

DELIMITER $$

CREATE PROCEDURE InsertSupplierProduct(
    IN inSupplierId INT, 
    IN inProductId INT
)
BEGIN
    -- exit if the duplicate key occurs
    DECLARE EXIT HANDLER FOR 1062 SELECT 'Duplicate keys error encountered' Message; 
    DECLARE EXIT HANDLER FOR SQLEXCEPTION SELECT 'SQLException encountered' Message; 
    DECLARE EXIT HANDLER FOR SQLSTATE '23000' SELECT 'SQLSTATE 23000' ErrorCode;
    
    -- insert a new row into the SupplierProducts
    INSERT INTO SupplierProducts(supplierId,productId)
    VALUES(inSupplierId,inProductId);
    
    -- return the products supplied by the supplier id
    SELECT COUNT(*) 
    FROM SupplierProducts
    WHERE supplierId = inSupplierId;
    
END$$

DELIMITER ;Code language: SQL (Structured Query Language) (sql)

Call the stored procedure to insert a duplicate key:

CALL InsertSupplierProduct(1,3);Code language: SQL (Structured Query Language) (sql)

Here is the output:

+----------------------------------+
| Message                          |
+----------------------------------+
| Duplicate keys error encountered |
+----------------------------------+
1 row in set (0.00 sec)

Query OK, 0 rows affected (0.01 sec)Code language: JavaScript (javascript)

As you see the MySQL error code handler is called.

Using a named error condition

Let’s start with an error handler declaration.

DELIMITER $$

CREATE PROCEDURE TestProc()
BEGIN

    DECLARE EXIT HANDLER FOR 1146 
    SELECT 'Please create table abc first' Message; 
        
    SELECT * FROM abc;
END$$

DELIMITER ;Code language: SQL (Structured Query Language) (sql)

What does the number 1146 really mean? Imagine you have stored procedures polluted with these numbers all over places; it will be difficult to understand and maintain the code.

Fortunately, MySQL provides you with the DECLARE CONDITION statement that declares a named error condition, which associates with a condition.

Here is the syntax of the DECLARE CONDITION statement:

DECLARE condition_name CONDITION FOR condition_value;Code language: SQL (Structured Query Language) (sql)

The condition_value  can be a MySQL error code such as 1146 or a SQLSTATE value. The condition_value is represented by the condition_name .

After the declaration, you can refer to condition_name instead of condition_value .

So you can rewrite the code above as follows:

DROP PROCEDURE IF EXISTS TestProc;

DELIMITER $$

CREATE PROCEDURE TestProc()
BEGIN
    DECLARE TableNotFound CONDITION for 1146 ; 

    DECLARE EXIT HANDLER FOR TableNotFound 
	SELECT 'Please create table abc first' Message; 
    SELECT * FROM abc;
END$$

DELIMITER ;Code language: SQL (Structured Query Language) (sql)

As you can see, the code is more obviously and readable than the previous one. Notice that the condition declaration must appear before handler or cursor declarations.

In this tutorial, you have learned how to use MySQL handlers to handle exception or errors occurred in stored procedures.

Was this tutorial helpful?

В этой статье мы расскажем вам, как использовать обработчик MySQL для обработки исключений или ошибок, возникающих в хранимых процедурах.

При возникновении ошибки внутри хранимой процедуры, важно исправить ее соответствующим образом, например, продолжая или останавливая исполнение операции и выдавая сообщение об ошибке.

MySQL предоставляет простой способ определить обработчики, которые обрабатывают ошибки, исходя из общих условий, таких как предупреждения или исключения из условий, например, конкретные коды ошибок.

  • Объявление обработчика
  • Примеры обработки ошибок MySQL
  • Пример обработчика MySQL в хранимых процедурах
    • Приоритет обработчиков MySQL
    • Использование проименованных условий ошибки

Чтобы объявить обработчик мы используем оператор DECLARE HANDLER:

DECLARE action HANDLER FOR condition_value statement;

Если значение условия совпадает со значением condition_value, MySQL выполнит оператор statement и продолжит или завершит текущий блок кода, исходя из значения action.

action может принимать следующие значения:

  • CONTINUE: исполнение блокированного кода (BEGIN … END) продолжается;
  • EXIT: выполнение блокированного кода, в котором был объявлен обработчик, завершается.

condition_value задает конкретное условие или класс условия, которые активируют обработчик.

condition_value может принимать одно из следующих значений:

  • код ошибки MySQL;
  • стандартное значение SQLSTATE. Или это может быть условие SQLWARNING, NOTFOUND или SQLEXCEPTION, которое является сокращением для класса значений SQLSTATE. Условие NOTFOUND используется для курсора или оператора SELECT INTO variable_list;
  • название условия, связанного либо с кодом ошибки MySQL, либо со значением SQLSTATE.

В качестве statement может использоваться простой оператор или составной оператор, вшитый с помощью ключевых слов BEGIN и END.

Давайте рассмотрим несколько примеров объявления обработчиков.

Обработчик, приведенный ниже, означает: когда происходит ошибка, устанавливается значение переменной has_error 1 и выполнение продолжается:

DECLARE CONTINUE HANDLER FOR SQLEXCEPTION SET has_error = 1;

Ниже приводится другой обработчик, который означает, что в случае возникновении любой ошибки, производится откат предыдущей операции, выдается сообщение об ошибке и осуществляется выход из текущего блока кода.

Если вы объявляете его внутри блока BEGIN END хранимой процедуры, он немедленно завершает хранимую процедуру:

DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
SELECT 'An error has occurred, operation rollbacked and the stored procedure was terminated';
END;

Если строк для вывода больше нет, для вариантов cursor или оператора SELECT INTO, значение переменной no_row_found устанавливается равным 1 и продолжается исполнение:

DECLARE CONTINUE HANDLER FOR NOT FOUND SET no_row_found = 1;

При возникновении ошибки дублирования ключа, выдается ошибка MySQL 1062. Следующий обработчик выдает сообщение об ошибке и продолжает выполнение:

DECLARE CONTINUE HANDLER FOR 1062
SELECT 'Error, duplicate key occurred';

Во-первых, для демонстрации мы создаем новую таблицу с именем article_tags:

CREATE TABLE article_tags(
    article_id INT,
    tag_id     INT,
    PRIMARY KEY(article_id,tag_id)
);

В таблице article_tags хранятся связи между статьями и тегами. К каждой статье может относиться несколько тегов и наоборот.

Для простоты, мы не будем создавать таблицы articles и tags, а также внешние ключи в таблице article_tags.

Во-вторых, мы создаем хранимую процедуру, которая вставляет пару идентификаторов статьи и тега в таблицу article_tags:

DELIMITER $$
 
CREATE PROCEDURE insert_article_tags(IN article_id INT, IN tag_id INT)
BEGIN
 
    DECLARE CONTINUE HANDLER FOR 1062
    SELECT CONCAT('duplicate keys (',article_id,',',tag_id,') found') AS msg;
 
    -- insert a new record into article_tags
    INSERT INTO article_tags(article_id,tag_id)
    VALUES(article_id,tag_id);
 
    -- return tag count for the article
    SELECT COUNT(*) FROM article_tags;
END

В-третьих, для статьи 1 мы добавляем идентификаторы тега 1, 2 и 3, с помощью вызова хранимой процедуры insert_article_tags:

CALL insert_article_tags(1,1);
CALL insert_article_tags(1,2);
CALL insert_article_tags(1,3);

Четвертое. Давайте попробуем вставить дубликат ключа, чтобы увидеть, действительно ли вызывается обработчик:

CALL insert_article_tags(1,3);

Мы получили сообщение об ошибке. Однако, поскольку мы объявили тип обработчика CONTINUE, хранимая процедура продолжает исполняться.

В результате, мы все равно получили список тегов для статьи:

Пример обработчика MySQL в хранимых процедурах

Если мы в объявлении обработчика изменим команду CONTINUE на EXIT, мы получим только сообщение об ошибке:

DELIMITER $$
 
CREATE PROCEDURE insert_article_tags_2(IN article_id INT, IN tag_id INT)
BEGIN
 
    DECLARE EXIT HANDLER FOR SQLEXCEPTION 
    SELECT 'SQLException invoked';
 
    DECLARE EXIT HANDLER FOR 1062 
        SELECT 'MySQL error code 1062 invoked';
 
    DECLARE EXIT HANDLER FOR SQLSTATE '23000'
    SELECT 'SQLSTATE 23000 invoked';
 
    -- insert a new record into article_tags
    INSERT INTO article_tags(article_id,tag_id)
       VALUES(article_id,tag_id);
 
    -- return tag count for the article
    SELECT COUNT(*) FROM article_tags;
END

Теперь, мы можем попробовать добавить дубликат ключа, чтобы увидеть результат:

CALL insert_article_tags_2(1,3);

Пример обработчика MySQL в хранимых процедурах - 2

В случае если у вас есть несколько обработчиков, которые имеют право обрабатывать ошибку, MySQL для обработки ошибки будет вызывать наиболее подходящий обработчик.

Ошибка всегда обозначается одним из кодов ошибки MySQL, так что MySQL в этом плане имеет возможность четко их идентифицировать.

Обозначения SQLSTATE для многих кодов ошибок MySQL менее специфичны. SQLEXCPETION или SQLWARNING представляют собой сокращения класса значений SQLSTATES, поэтому они имеют общий характер.

На основании правил приоритета обработчиков обработчик кода ошибки MySQL, обработчик SQLSTATE и обработчик SQLEXCEPTION имеют приоритеты один, два и три соответственно.

Предположим, что в хранимой процедуре insert_article_tags_3 мы объявляем три обработчика:

DELIMITER $$
 
CREATE PROCEDURE insert_article_tags_3(IN article_id INT, IN tag_id INT)
BEGIN
 
    DECLARE EXIT HANDLER FOR 1062 SELECT 'Duplicate keys error encountered';
    DECLARE EXIT HANDLER FOR SQLEXCEPTION SELECT 'SQLException encountered';
    DECLARE EXIT HANDLER FOR SQLSTATE '23000' SELECT 'SQLSTATE 23000';
 
    -- insert a new record into article_tags
    INSERT INTO article_tags(article_id,tag_id)
    VALUES(article_id,tag_id);
 
    -- return tag count for the article
    SELECT COUNT(*) FROM article_tags;
END

Теперь мы пробуем добавить в таблицу article_tags дубликат ключа через вызов хранимой процедуры:

CALL insert_article_tags_3(1,3);

Как видите, вызывается обработчик кода ошибки MySQL:

Приоритет обработчиков MySQL

Начинаем с объявления обработчика ошибки:

DECLARE EXIT HANDLER FOR 1051 SELECT 'Please create table abc first';
SELECT * FROM abc;

Что означает код 1051? Представьте, что у вас есть большая хранимая процедура, по всему коду которой разбросаны некорректные значения. Настоящий кошмар для разработчиков обслуживания.

К счастью, MySQL предоставляет нам оператор DECLARE CONDITION, который объявляет проименованное условие ошибки, связанное с условием.

Синтаксис оператора DECLARE CONDITION выглядит следующим образом:

DECLARE condition_name CONDITION FOR condition_value;

condition_value может представлять собой код ошибки MySQL, например 1015, или значение SQLSTATE. condition_value представляется с помощью condition_name.

После объявления вы можете обращаться к condition_name вместо condition_value.

Таким образом, мы можем переписать код, приведенный выше, следующим образом:

DECLARE table_not_found CONDITION for 1051;
DECLARE EXIT HANDLER FOR  table_not_found SELECT 'Please create table abc first';
SELECT * FROM abc;

Этот код, очевидно, более удобен для чтения, нежели предыдущий. Отметим, что объявление условия должно размещаться перед объявлением обработчика или объявлением курсора.

Правильная обработка ошибок позволяет сделать стабильное приложение, которое не будет завершаться неожиданно. В случае возникновения непредвиденной ситуации программа должна выдавать вменяемый ответ, почему она не хочет исполнится. И создание правильного механизма обработки ошибок — это задача программиста.

В этой статье рассмотрим два вида ошибок, которые могут возникнуть при программировании взаимодействия с базой данных. Первый тип — это ошибка подключения к базе данных. Второй тип — это ошибка выполнения запроса к базе данных. Для обработки этих ошибок будем использовать специальные функции для работы с базой.

Ошибка соединения с базой данных

Сразу приведём пример обработки ошибки с соединением с базой данных:

<?php 
   $host = 'localhost'; // адрес сервера
   $db_name = 'database'; // имя базы данных
   $user = 'user'; // имя пользователя
   $password = 'password'; // пароль

   // создание подключения к базе   
      $connection = mysqli_connect($host, $user, $password, $db_name);

   // проверка правильности подключения
      if(!$connection){ // при соединении с базой данных возникла ошибка
         echo 'Ошибка соединения: ' . mysqli_connect_error() . '<br>';
         echo 'Код ошибки: ' . mysqli_connect_errno();
      }else{ // соединение было установлено успешно
         // здесь можно делать запрос к базе, 
         // потому что соединение успешно установлено
      }
?>

В этом примере можно заметить функцию mysqli_connect_error. Она выводит текстовое описание ошибки подключения (на английском языке). В отличии от неё функция mysqli_connect_errno выводит числовой код ошибки, к примеру «1045».

Ошибка запроса к базе

Теперь попробуем доработать пример из предыдущего параграфа и добавить запрос к базе данных. Не будем вдаваться в детали, что будет записано в тексте SQL запроса, просто предположим, что он может завершиться ошибкой. К примеру, из-за неожиданного отключения сервера с базой данных от сети. В примере добавим проверку на наличие ошибок при выполнении запроса:

<?php 
   $host = 'localhost'; // адрес сервера
   $db_name = 'database'; // имя базы данных
   $user = 'user'; // имя пользователя
   $password = 'password'; // пароль

   // создание подключения к базе   
      $connection = mysqli_connect($host, $user, $password, $db_name);

      if(!$connection){ // проверка правильности подключения
         echo 'Ошибка соединения: ' . mysqli_connect_error() . '<br>';
         echo 'Код ошибки: ' . mysqli_connect_errno();
      }else{ // подключение успешно установлено

         // текст SQL запроса, который будет передан базе
            $query = 'SELECT * FROM `USERS`';

         // выполняем запрос к базе данных
            $result = mysqli_query($connection, $query);

            if(!$result){ // запрос завершился ошибкой
               echo 'Ошибка запроса: ' . mysqli_error($connection) . '<br>';
               echo 'Код ошибки: ' . mysqli_errno($connection);
            }else{ // запрос успешно выполнился
               while($row = $result->fetch_assoc()){
                  // обрабатываем полученные данные
               }
            }
         // закрываем соединение с базой
            mysqli_close($connection);
      }
?>

В этом примере есть две функции, которые работают с ошибками базы. Функция mysqli_error возвращает описание ошибки запроса (на английском языке), а функция mysqli_errno возвращает числовой код ошибки, к примеру, «1193».

Обратите внимание, что все функции обработки ошибок в этой статье (mysqli_connect_error, mysqli_connect_errno, mysqli_error, mysqli_errno) возвращают информацию только о последней ошибке. Но ошибок может быть несколько.

Была ли статья полезной?

Была ли эта статья полезна?

Есть вопрос?

хостинг для сайтов

Закажите недорогой хостинг

Заказать

всего от 290 руб

Библиотека mysqli — своеобразный мостик между процедурным и объектно-ориентированным стилем программирования. Поэтому она поддерживает два подхода обработки ошибок: процедурный и через исключения.

Первый, процедурный подход — вы проверяете результат выполнения каждой функции и если возвращается false, получаете сообщение при помощи функции mysqli_error()

$db = mysqli_connect("localhost", "user", "...", "test");
if (mysqli_connect_errno()) {
  echo "Ошибка установки соединения" . mysqli_connect_error();
  exit();
}
$sql = "INSERT INTO users (login, email, password, datetime) VALUES (?, ?, ?, ?)";
$stmt = mysqli_prepare($db, $sql);
if(!$stmt) {
  echo "Ошибка подготовки запроса: " . mysqli_error($db);
  exit();
}
if(!mysqli_stmt_bind_param($stmt, 'ssss', $login, $email, $password, $datetime)) {
  echo "Ошибка связывания параметров: " . mysqli_error($db);
  exit();
}
if(!mysqli_stmt_execute($stmt)) {
  echo "Ошибка выполнения запроса: " . mysqli_error($db);
  exit();
}
mysqli_stmt_close($stmt);
mysqli_close($db);

Разумеется это не очень удобно, особенно, в объектно-ориентированном коде, поэтому mysqli позволяет переключиться в режим генерации исключений (их несколько типов, в режиме отладки наиболее удобно использовать MYSQLI_REPORT_ALL). Задать режим можно при помощи функции mysqli_report()

mysqli_report(MYSQLI_REPORT_ALL); 

try {
  $db = mysqli_connect("localhost", "user", "...", "test");

  $sql = "INSERT INTO users (login, email, password, datetime) VALUES (?, ?, ?, ?)";
  $stmt = mysqli_prepare($db, $sql);
  mysqli_stmt_bind_param($stmt, 'ssss', $login, $email, $password, $datetime);
  mysqli_stmt_execute($stmt);
  mysqli_stmt_close($stmt);
  mysqli_close($db);
} catch (Exception $e) {
  echo $e->getMessage();
} 

Ошибки перехватываются при помощи стандартного механизма исключений. Не смотря на то, что вы выбрали процедурный стиль, вы можете воспользоваться перехватом исключений, который чаще применяется в объектно-ориентированном коде.

  • Исходники
  • Исходники PHP
  • [PHP] MySQL
  • MySQL: Обработка ошибок запросов

Written on 20 Января 2007.

Сообщение о последней ошибке можно получить через функцию mysql_error:

echo «Ошибка базы данных. MySQL пишет:», mysql_error();

Если результат функции пишется в переменную, можно проверить её:

$result = mysql_query($request);

if (!$result)

  echo "Ошибка базы данных. MySQL пишет:", mysql_error();

else {

  echo "<table>";

  while ($row = mysql_fetch_array($result))

    echo "<tr><td>", $row["field1"], "</td><td>", $row["field2"], "</td></tr>";

  echo "</table>";

  };

Если в переменную не пишем, то так:


$request = "UPDATE (...)";

mysql_query($request);

if (!mysql_error())

  echo "Обновление данных прошло успешно!";

else echo "Ошибка базы данных. MySQL пишет:", mysql_error();

Если запрос генерируется автоматически, можно выводить и сам запрос (полезно создавать переменную, которая бы его содержала, и использовать её в качестве параметра функции).

Если выбирать из этих двух, то второй, разумеется. Он на порядок лучше первого:
— в отличие от первого, он будет выдавать ошибки туда же, куда и весь остальной РНР. На машине разработчика это может быть экран, на боевом сайте — лог. Первый плюёт ошибки в браузер ВСЕГДА, чего на боевом сайте не должно быть никогда
— в отличие от первого, он сообщит номер строки и имя файла, где произошла ошибка, что является критически важным для того самого отлова ошибки. Рекомендую попробовать поискать ошибочный запрос в коде на пару тысяч строк по сообщению от первого варианта. Подробнее про то, как правильно обрабатывать ошибки, можно почитать здесь: Обработка ошибок, часть 1

Примечание: на самом деле ни тот, ни другой коды работать не будут, поскольку mysqli_error() тоже требует $link в обязательном порядке.

Дальше уже идут более продвинутые варианты.
Для начала, mysqli умеет кидать исключения из коробки:

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

и после этого любая ошибка mysqli будет порождать исключение.
Но у этого подхода есть два минуса:
1. Такой вариант может понадобиться только в случае, если обращения к mysqli_query разбросаны по всему коду, чего делать нельзя ни в коем случае.
2. В брошенном исключении будет отсутствовать сам запрос, который может быть очень полезен при отладке.

Поэтому идеальным вариантом будет такой:
Во-первых, все обращения к mysqli API в обязательном порядке надо завернуть в какую-либо библиотеку, которая возьмёт на себя выполнение всей грязной и повторяющейся работы. Пример такой библиотеки — SafeMysql
Во-вторых, в этой библиотеке оформить код обращения к mysqli_query такм образом:

$res = $link->query($query);
if (!$res) throw new Exception($link->error() ." [$query]");

В результате мы получим идеальную обработку ошибок:
— этот код уже из коробки будет так же следовать настройкам РНР, и не будет выдавать ошибки на экран на боевом сервере, но при этом программист всегда будет о ней проинформирован.
— этот код будет выдавать трассировку вызовов — бесценную информацию, без которой найти место, где произошла ошибка, будет очень сложно.
— брошенное исключение можно будет поймать в хендлере или блоке try..catch (однако если нет опыта работы с этими двумя вещами, то на первое время лучше оставить исключение как есть. В обработке ошибок есть много нюансов, неизвестных среднему кодеру, и поэтому лучше оставить эту задачу для РНР).

PHP и MySQL:

При совместном использовании PHP и MySQL вы в конце-концов столкнетесь с ситуацией, когда вы допустите какую-то ошибку в SQL-запросе, такую как опечатка в заголовке столбца или в ключевом слове, или что-то подобное. По умолчанию, PHP не покажет вам, в чем именно проблема, только что вы написали некорректный запрос. Давайте попробуем написать запрос с ошибкой, чтобы получить ответ от PHP:

$query = mysql_query("SELECT id, namme FROM test_users");
while($row = mysql_fetch_array($query))
    echo $row['id'] . " - " . $row["name"] . " is from " . $row["country"] . "<br />";

Это пример на подобие множества других в данном разделе руководства, но в данном случае мы допустили опечатку в названии столбца, что приводит к следующей ошибке:

Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in test.php on line 7
Перевод: Предупреждение: mysql_fetch_array(): переданный аргумент не является корректным результатом MySQL-запроса в файле test.php на строке 7

Как вы можете видеть, ошибка не будет выброшена, пока мы не попытаемся использовать результат, возвращенный функцией mysql_query(), что мы и делаем, когда вызываем функцию mysql_fetch_array(), которая в моем файле расположена на строке 7. Выведенная ошибка имеет очень размытую формулировку и не особенно полезна. Это сделано специально, поскольку знание о структуре базы данных делает сайт более уязвимым к атакам типа SQL-инъекций, эту проблему мы обсудим позже.

Возможно, вам удастся заметить ошибку и исправить ее во многих случаях, но если нет, вы можете использовать функцию mysql_error(), чтобы получить немного больше информации о проблеме. Эта функция просто возвращает все ошибки, полученные при выполнении SQL-запроса в последней вызванной функции. Вы должны использовать эту функцию, только чтобы находить и исправлять ошибки, а потом удалять ее, как только проблема решена. Рассмотрим предыдущий пример, но добавим в него вызов функции mysql_error(), чтобы получить больше информации:

$query = mysql_query("SELECT id, namme FROM test_users");
while($row = mysql_fetch_array($query))
    echo $row['id'] . " - " . $row["name"] . " is from " . $row["country"] . "<br />";
echo mysql_error();

Это даст вам гораздо более информативное сообщение об ошибке:

Unknown column ‘namme’ in ‘field list’
Перевод: Неизвестный столбец ‘namme’ в ‘field list’

Попробуйте сделать разные ошибки в SQL-запросе и посмотрите, какие сообщения об ошибках вернет вам MySQL через функцию mysql_error(), чтобы увидеть, как он реагирует. Это поможет вам быстрее идентифицировать SQL-ошибки в будущем. Приведенная в примере выше довольно очевидна, и ее легко исправить, в то время как другие ошибки могут скрываться от неопытного взгляда.

This article has been fully translated into the following languages:

  • Russian

Is your preferred language not on the list? Click here to help us translate this article into your language!


Понравилась статья? Поделить с друзьями:
  • Как обосновать ошибку в документе
  • Как обойти ошибку 404 на сайте
  • Как обойти ошибку сертификата в браузере
  • Как обойти ошибку 403 при парсинге
  • Как обойти ошибку приватности в опере