Production insight: Wrong DETERMINISTIC label causes silent replica drift
Plain-English First
Imagine your favorite coffee shop has a secret recipe for calculating the total price of any order — including tax, loyalty discounts, and cup size. Instead of every barista doing that math from scratch each time, the recipe lives in one laminated card behind the counter. A MySQL stored function is exactly that laminated card: a named, reusable piece of logic you write once, store inside the database, and call any time you need that calculation — no copy-pasting, no inconsistency.
Every production database eventually accumulates the same calculation scattered across dozens of queries — tax rates, age from a birthdate, discounted prices, formatted phone numbers. When that logic changes (and it always does), you're hunting through hundreds of SQL files trying to update every copy. That's not a maintenance strategy; that's a liability. MySQL stored functions exist to solve exactly this problem by letting you define reusable, named logic that lives inside the database itself.
Unlike a stored procedure, a stored function is purpose-built to compute and return a single value. It plugs directly into a SELECT statement, a WHERE clause, or a computed column — anywhere an expression is valid. That distinction matters enormously: you're not running a batch process, you're enriching a query with encapsulated intelligence. The database engine treats your function like any built-in function such as NOW() or UPPER(), which means your code reads naturally and your business rules stay consistent regardless of which application, microservice, or analyst is querying the data.
By the end of this article you'll know how to write a stored function from scratch, understand the determinism rules that trip up even experienced developers, call functions inside real queries, and avoid the four most common mistakes that cause silent errors or permission failures in production. You'll also walk away knowing exactly how to answer stored-function questions in a technical interview.
What a Stored Function Actually Is (and When to Reach for One)
A stored function is a named database object that accepts zero or more input parameters, executes SQL logic, and returns exactly one scalar value — a number, a string, a date, a boolean equivalent. The key word is 'returns'. That return value is what separates a stored function from a stored procedure. A procedure does things; a function computes a value.
Use a stored function when you have a calculation or transformation that: 1. Appears in multiple queries across your application. 2. Needs to stay consistent — if the rule changes, you want one place to update. 3. Produces a single result you can embed inline, like in a SELECT list or a WHERE condition.
Classic real-world candidates include: calculating a customer's current loyalty tier based on their total spend, deriving a human-readable order status label from a numeric status code, computing net price after dynamic discount rules, or formatting an international phone number from raw digits.
What you should NOT use a stored function for: anything that modifies data (INSERT, UPDATE, DELETE). MySQL technically allows data modification inside functions in some configurations, but it's a trap — we'll cover exactly why in the Gotchas section.
create_loyalty_tier_function.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
-- ============================================================-- SCENARIO: An e-commerce platform needs to show each customer-- their loyalty tier (Bronze / Silver / Gold) based on their-- total lifetime spend. This logic appears in dashboards, emails,-- and reports — so it must live in one place.-- ============================================================-- Step 1: Switch to your target databaseUSE ecommerce_db;
-- Step 2: Tell MySQL what delimiter to use while defining the function.-- Without this, MySQL would interpret the first semicolon inside the-- function body as the END of the CREATE statement — which breaks everything.DELIMITER $$
CREATEFUNCTIONget_loyalty_tier(
total_spend DECIMAL(10, 2) -- lifetime spend in dollars
)
RETURNSVARCHAR(10) -- will return 'Bronze', 'Silver', or 'Gold'DETERMINISTIC-- same input always gives same output (no randomness, no NOW())COMMENT'Returns customer loyalty tier based on lifetime spend thresholds'BEGIN-- Declare a local variable to hold our resultDECLARE tier_label VARCHAR(10);
-- Apply the business rule: thresholds defined by the product teamIF total_spend >= 1000.00THENSET tier_label = 'Gold';
ELSEIF total_spend >= 250.00THENSET tier_label = 'Silver';
ELSESET tier_label = 'Bronze'; -- default for new or low-spend customersENDIF;
RETURN tier_label; -- every code path MUST hit a RETURNEND$$
-- Step 3: Restore the normal semicolon delimiterDELIMITER ;
Output
Query OK, 0 rows affected (0.02 sec)
Watch Out: DELIMITER Is Not Optional
Forgetting to change the DELIMITER before a function body is the single most common reason CREATE FUNCTION fails with a syntax error. MySQL's client sees the first semicolon inside BEGIN...END and thinks the statement is over. Always wrap your function definition with DELIMITER $$ ... DELIMITER ; when using the CLI or a script file. GUI tools like MySQL Workbench handle this automatically.
Production Insight
On a busy primary, forgetting DELIMITER in a migration script caused a deployment rollback that took 20 minutes.
The error message '... at line 2' never mentions DELIMITER — it just shows the wrong line.
Rule: Always include DELIMITER $$ at the start of every CREATE FUNCTION script, even in CI.
Key Takeaway
Stored functions return one value and live in the database.
Change DELIMITER before CREATE, restore after END.
All paths in the body must hit a RETURN — no path = NULL.
Calling Your Function — Inside SELECT, WHERE, and Computed Columns
Once your function exists, you call it exactly like any built-in MySQL function — by name, with parentheses, passing the required arguments. There's no special keyword, no EXEC, no CALL. This is what makes stored functions so powerful: they integrate invisibly into ordinary SQL.
The examples below use a customers table with columns: customer_id, full_name, email, and lifetime_spend. Notice how the function call in the SELECT list creates a clean derived column, the WHERE clause uses it for filtering, and ORDER BY uses it for sorting — all without any application-side logic.
One performance note worth understanding: when you call a stored function in a WHERE clause on a large table, MySQL cannot use an index on that expression unless you're on MySQL 8.0+ with a functional index defined for it. For high-traffic filter conditions, consider materializing the tier into a real indexed column and updating it on a schedule. For dashboard and reporting queries that run occasionally, inline function calls are perfectly fine.
query_with_loyalty_tier.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
-- ============================================================-- Assume this customers table exists:-- CREATE TABLE customers (-- customer_id INT PRIMARY KEY AUTO_INCREMENT,-- full_name VARCHAR(100),-- email VARCHAR(150),-- lifetime_spend DECIMAL(10,2) DEFAULT 0.00-- );---- With sample data:-- (1, 'Alice Hartman', 'alice@example.com', 1250.75)-- (2, 'Ben Okafor', 'ben@example.com', 310.00)-- (3, 'Cleo Ramirez', 'cleo@example.com', 89.50)-- ============================================================-- USE CASE 1: Show every customer with their computed loyalty tierSELECT
customer_id,
full_name,
lifetime_spend,
get_loyalty_tier(lifetime_spend) AS loyalty_tier -- function called inlineFROM customers
ORDERBY lifetime_spend DESC;
-- USE CASE 2: Filter — show only Gold-tier customers for a VIP email campaignSELECT
full_name,
email
FROM customers
WHEREget_loyalty_tier(lifetime_spend) = 'Gold';
-- USE CASE 3: Quick sanity check — test the function directlySELECTget_loyalty_tier(1500.00) AS result_gold,
get_loyalty_tier(500.00) AS result_silver,
get_loyalty_tier(10.00) AS result_bronze;
Pro Tip: Always Test Your Function in Isolation First
Before embedding a new function inside a complex JOIN query, always run SELECT your_function(test_value) AS result; on its own. It takes 10 seconds and immediately tells you if the logic is correct before you spend 20 minutes debugging a query that was never the problem.
Production Insight
A team in production added a function call to a WHERE clause on a 2 million row table.
The query dev team thought was 'fast enough' in staging went from 200ms to 12 seconds.
Rule: Profile any function call in WHERE with EXPLAIN; if it says 'Using where; Using filesort', consider a generated column.
Key Takeaway
Functions integrate into SQL seamlessly.
Test in isolation before embedding in complex queries.
High-frequency filter conditions need an indexed materialization, not an inline function.
DETERMINISTIC vs NOT DETERMINISTIC — Why This Label Changes Everything
When you declare a stored function, MySQL asks you to describe its behavior with one of these characteristics: DETERMINISTIC, NOT DETERMINISTIC, or READS SQL DATA / NO SQL / MODIFIES SQL DATA. Most tutorials gloss over these as bureaucratic box-ticking. They're not — they directly affect query optimization, binary log replication, and whether your function is even allowed to run in certain server configurations.
DETERMINISTIC means: given the same inputs, the function will always return the same output. Think of it like a pure math function — f(5) always equals 25. MySQL's optimizer can cache the result and reuse it within the same query execution, which can be a meaningful performance win when the function is called thousands of times in a large result set.
NOT DETERMINISTIC means the output can vary even with identical inputs — because it relies on the current time, a random number, or data in the database. Functions that call NOW(), RAND(), or run a SELECT to fetch changing data must be declared NOT DETERMINISTIC.
The dangerous middle ground: declaring a function DETERMINISTIC when it actually isn't. MySQL trusts your declaration. If you lie, the optimizer may cache a stale result, and your queries silently return wrong answers — no error, no warning. This is especially bad on replicated setups where binary logging can produce inconsistent replicas.
determinism_examples.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
-- ============================================================-- EXAMPLE 1: Correctly declared DETERMINISTIC function-- Converts a raw numeric discount rate (e.g. 0.15) to a-- human-readable label. Same input = same output. Always.-- ============================================================DELIMITER $$
CREATEFUNCTIONformat_discount_label(
discount_rate DECIMAL(5, 4) -- e.g. 0.1500 for 15%
)
RETURNSVARCHAR(20)
DETERMINISTICNOSQL-- no database reads or writesBEGINRETURNCONCAT(FORMAT(discount_rate * 100, 0), '% OFF');
END$$
DELIMITER ;
-- Test it:SELECTformat_discount_label(0.1500) AS label;
-- Output: '15% OFF'-- ============================================================-- EXAMPLE 2: Correctly declared NOT DETERMINISTIC function-- Calculates how many days ago an order was placed.-- The result changes every day — it reads the current date.-- ============================================================DELIMITER $$
CREATEFUNCTIONdays_since_order(
order_placed_at DATE
)
RETURNSINTNOTDETERMINISTIC-- result changes with time; optimizer must NOT cache itNOSQLBEGINRETURNDATEDIFF(CURDATE(), order_placed_at);
END$$
DELIMITER ;
-- Test it (assuming today is 2025-07-15):SELECTdays_since_order('2025-07-01') AS days_ago;
-- Output: 14-- ============================================================-- EXAMPLE 3: Function that READS from a table-- Gets a product's current category name by product_id.-- Requires READS SQL DATA because it runs a SELECT.-- ============================================================DELIMITER $$
CREATEFUNCTIONget_product_category(
product_id_input INT
)
RETURNSVARCHAR(100)
NOTDETERMINISTIC-- data in the table can change between callsREADSSQLDATA-- required: this function runs a SELECT statementBEGINDECLARE category_name VARCHAR(100);
SELECT c.category_name
INTO category_name -- stores query result into the local variableFROM products p
JOIN categories c ON p.category_id = c.category_id
WHERE p.product_id = product_id_input
LIMIT1; -- LIMIT 1 prevents 'Result consisted of more than one row' errorRETURNIFNULL(category_name, 'Uncategorized'); -- graceful fallback if no matchEND$$
DELIMITER ;
Output
-- format_discount_label(0.1500):
+---------+
| label |
+---------+
| 15% OFF |
+---------+
-- days_since_order('2025-07-01') on 2025-07-15:
+----------+
| days_ago |
+----------+
| 14 |
+----------+
Interview Gold: Why Does DETERMINISTIC Matter for Replication?
In statement-based binary logging (MySQL's default replication mode), the server logs the function call, not its result. When a replica replays that call, a NOT DETERMINISTIC function like one using NOW() could produce a different value than it did on the primary — causing data drift. Marking functions correctly isn't just an optimizer hint; it's a replication correctness guarantee. Interviewers love this answer.
Production Insight
A misdeclared DETERMINISTIC function caused a weekly batch report on a read replica to show different customer counts than the primary.
The replica had cached the function result from an earlier execution with different data.
Fix: inspect function body for any non-deterministic call (NOW, RAND, table reads) and correct the declaration.
Key Takeaway
DETERMINISTIC is an optimizer and replication contract — not a suggestion.
If your function calls any non-deterministic function, it must be NOT DETERMINISTIC.
When in doubt, default to NOT DETERMINISTIC.
Managing Functions — View, Alter, Drop, and Control Permissions
Creating a function is only the beginning. In real projects you need to inspect what's deployed, update logic when business rules change, and control who can execute sensitive functions. MySQL gives you clean tooling for all of this — but the workflow is less obvious than it should be.
MySQL has no ALTER FUNCTION statement for changing the body. To update a function's logic, you DROP it and CREATE it again. This feels crude but is intentional — it forces an explicit, auditable replacement rather than silent in-place mutation. In CI/CD pipelines, this means your migration scripts should always DROP FUNCTION IF EXISTS before recreating it.
Permissions matter more than most developers realize. The EXECUTE privilege controls who can call a function. The CREATE ROUTINE and ALTER ROUTINE privileges control who can create or drop them. In production, your application's database user should only have EXECUTE — never CREATE ROUTINE. That separation means a SQL injection attack can't overwrite your business logic even if it somehow gets write access.
manage_stored_functions.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
-- ============================================================-- INSPECTING FUNCTIONS-- ============================================================-- List all stored functions in the current databaseSHOWFUNCTIONSTATUSWHEREDb = 'ecommerce_db';
-- View the exact CREATE statement for an existing function-- (useful for code review, documentation, or migrating servers)SHOWCREATEFUNCTION get_loyalty_tier;
-- Query the information schema for richer metadataSELECT
ROUTINE_NAME,
ROUTINE_TYPE,
DATA_TYPE AS return_type,
IS_DETERMINISTIC,
ROUTINE_COMMENT AS description,
CREATED,
LAST_ALTERED
FROM information_schema.ROUTINESWHERE ROUTINE_SCHEMA = 'ecommerce_db'AND ROUTINE_TYPE = 'FUNCTION'ORDERBY ROUTINE_NAME;
-- ============================================================-- UPDATING A FUNCTION (drop-and-recreate pattern)-- The product team changed the Gold threshold from $1000 to $750-- ============================================================-- Always use IF EXISTS to avoid an error if it somehow doesn't existDROPFUNCTIONIFEXISTS get_loyalty_tier;
DELIMITER $$
CREATEFUNCTIONget_loyalty_tier(
total_spend DECIMAL(10, 2)
)
RETURNSVARCHAR(10)
DETERMINISTICCOMMENT'v2: Gold threshold lowered to $750 per 2025 loyalty program update'BEGINDECLARE tier_label VARCHAR(10);
IF total_spend >= 750.00THEN-- updated thresholdSET tier_label = 'Gold';
ELSEIF total_spend >= 250.00THENSET tier_label = 'Silver';
ELSESET tier_label = 'Bronze';
ENDIF;
RETURN tier_label;
END$$
DELIMITER ;
-- ============================================================-- CONTROLLING PERMISSIONS-- ============================================================-- Grant the app's read-only user permission to CALL functions-- but NOT to create or modify themGRANTEXECUTEONFUNCTION ecommerce_db.get_loyalty_tier TO'app_readonly'@'%';
-- Grant a developer account full routine management rightsGRANTCREATEROUTINE, ALTERROUTINE, EXECUTEON ecommerce_db.*
TO'dev_user'@'localhost';
-- Revoke all routine privileges from a deprecated service accountREVOKEEXECUTEON ecommerce_db.* FROM'legacy_service'@'%';
-- Verify current grants on the functionSHOWGRANTSFOR'app_readonly'@'%';
Pro Tip: Use ROUTINE_COMMENT as Living Documentation
The COMMENT clause on CREATE FUNCTION is stored in information_schema.ROUTINES.ROUTINE_COMMENT and shows up in SHOW FUNCTION STATUS. Use it like a git commit message — include the version, the date, and what changed. It's the cheapest form of database-layer documentation you'll ever write.
Production Insight
A CI/CD pipeline that did not drop functions before recreating caused a stuck migration when a function's signature changed but the old function was not dropped first.
The error 'Function already exists' halted deployment for an hour.
Rule: Always use DROP FUNCTION IF EXISTS before CREATE FUNCTION in migration scripts.
Key Takeaway
MySQL has no ALTER FUNCTION body — use drop-and-recreate.
Grant app users only EXECUTE, never CREATE ROUTINE.
Use ROUTINE_COMMENT for version tracking.
Performance Considerations for Stored Functions in Production
Stored functions are not free. Every time you call them, MySQL executes the function body. For simple DETERMINISTIC functions with NO SQL, the overhead is small — on par with built-in functions. But functions that read data (READS SQL DATA) can become a hidden performance bomb.
When a function is called in a WHERE clause, MySQL executes it once per row. If the function itself runs a SELECT query, you get an N+1 problem: the outer query returns N rows, and each row triggers an inner query. A query that would take 10ms as a JOIN can balloon to 30 seconds using a function in WHERE.
The fix depends on use case
For lookup-style functions (e.g., get category name by ID), replace the function call with a JOIN to the lookup table.
For computation-heavy functions (e.g., complex calculations), consider a generated column with a stored value, especially if you filter or sort on the result.
For functions used in SELECT lists on reporting queries, inline calls are fine as long as the function is DETERMINISTIC and NO SQL — the overhead is minimal.
Another common performance trap: functions that call other functions. If each nested function reads data, the cost multiplies. Review the full execution plan with EXPLAIN to see how many times the function is invoked.
performance_comparison.sqlSQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
-- ============================================================-- BAD: Function that reads a table called in WHERE-- Runs the SELECT inside get_product_category for every row of the outer query-- ============================================================SELECT p.product_name, p.price
FROM products p
WHEREget_product_category(p.product_id) = 'Electronics';
-- EXPLAIN shows: "Using where" with a note that a function is applied per row-- ============================================================-- GOOD: Replace with JOIN-- ============================================================SELECT p.product_name, p.price
FROM products p
JOIN categories c ON p.category_id = c.category_id
WHERE c.category_name = 'Electronics';
-- EXPLAIN shows index usage on categories.name-- ============================================================-- BETTER: Use a generated column for frequently filtered computed values-- ============================================================ALTERTABLE products
ADDCOLUMN category_name VARCHAR(100)
GENERATEDALWAYSAS (get_category_name(category_id)) VIRTUAL;
-- Now you can create an index on it (MySQL 8.0+):CREATEINDEX idx_category_name ONproducts(category_name);
SELECT product_name, price
FROM products
WHERE category_name = 'Electronics'; -- uses index-- ============================================================-- Quick check: How many times does your function get called?-- Run this to see if the function appears in the execution plan-- ============================================================EXPLAINFORMAT=JSONSELECT ... FROM ... WHEREmy_function(col) = X;
-- Look for "materialized_from_subquery" or "dependent" in the JSON plan
-- No index used, full table scan with function evaluation per row
The N+1 Trap: Function in WHERE
A SELECT that returns 50,000 rows and calls a READS SQL DATA function in the WHERE clause executes 50,001 queries: one for the outer query and one per row for the function. That's a full table scan multiplied by another query. Monitor slow query logs for functions appearing as 'dependent subqueries'.
Production Insight
A production incident: a reporting query that filtered by a function took 47 seconds instead of 200ms.
The function called another function that read a 10MB configuration table.
After replacing the function call with a JOIN to the configuration table, the query dropped to 180ms.
Rule: If a function reads data, do NOT use it in WHERE — replace with a JOIN or a generated column.
Key Takeaway
Stored functions in SELECT lists are cheap.
Stored functions in WHERE clauses are expensive — especially if they read data.
Materialize and index frequent filter conditions instead of using functions.
Should You Use a Stored Function or a Different Approach?
IfFunction is DETERMINISTIC, NO SQL, called in SELECT list
→
UseSafe to use — overhead negligible.
IfFunction is READS SQL DATA, called in WHERE clause on large table
→
UseReplace with JOIN to the lookup table.
IfFunction result is filtered or sorted on frequently
→
UseMaterialize as a generated column and index it.
IfFunction is called only in SELECT list on small result sets
→
UseFunction use is fine — no significant performance impact.
● Production incidentPOST-MORTEMseverity: high
The Silent Replica Drift: How a Wrong DETERMINISTIC Declaration Caused Hours of Data Inconsistency
Symptom
A scheduled report on the read replica showed different loyalty tier counts than the primary. The campaign sent 'Gold' offers to customers who had actually dropped to 'Silver' days earlier.
Assumption
The function was safe to mark DETERMINISTIC because it used only input parameters and a simple IF statement.
Root cause
The function called another function that internally used CURDATE() to calculate 'days since last purchase'. That inner function was NOT DETERMINISTIC, but the outer loyalty tier function was declared DETERMINISTIC. MySQL trusted the declaration and cached results on the primary, but the replica replayed the statement binary log — which included the outer function call — and because the outer was marked DETERMINISTIC, the optimizer on the replica reused a stale cached result.
Fix
1. Drop the outer function. 2. Recreate it with NOT DETERMINISTIC and READS SQL DATA (because it reads from the inner function that reads data). 3. Run a full data reconciliation script to correct the loyalty tier assignments on both primary and replica. 4. Add a test in CI that verifies inner function determinism matches outer declarations.
Key lesson
Determinism is transitive — if your function calls another function, the least deterministic member forces the whole chain to be NOT DETERMINISTIC.
Never trust a function's determinism without reviewing every function it calls.
When in doubt, use NOT DETERMINISTIC as the default. The performance penalty is negligible compared to the cost of data inconsistency.
Production debug guideSymptom → Action pairs for diagnosing function failures without guesswork5 entries
Symptom · 01
CREATE FUNCTION fails with syntax error near first line of body
→
Fix
Check DELIMITER. You must change the delimiter before the function body so MySQL doesn't interpret the first semicolon inside BEGIN...END as the end of the statement. Use: DELIMITER $$ ... DELIMITER ;
Symptom · 02
Function returns NULL unexpectedly
→
Fix
Isolate the function call: SELECT your_function(test_input). Verify all code paths return a value. If the function runs a SELECT ... INTO, ensure the query returns a row; use IFNULL or a fallback. Check for missing RETURN in an ELSE branch.
Symptom · 03
Function returns wrong value in production but works locally
→
Fix
Check determinism declaration. If the function uses NOW(), RAND(), or reads from a table, it must be NOT DETERMINISTIC. Compare execution plans on primary vs replica — wrong determinism causes cached results to differ.
Symptom · 04
Slow query after adding function to WHERE clause
→
Fix
Verify the function contains a SELECT (reads SQL data). If so, it's called per row — potential O(n²) behavior. Replace with a JOIN to a precomputed value, add a generated column, or use a functional index (MySQL 8.0+).
Symptom · 05
Function exists but calling it gives 'EXECUTE command denied to user'
→
Fix
Check grants: SHOW GRANTS FOR 'user'@'host'. Grant EXECUTE on the specific function: GRANT EXECUTE ON FUNCTION db.func_name TO 'user'@'host'. Application users should only have EXECUTE, not CREATE ROUTINE.
★ Stored Function Quick Debug Cheat SheetFive most common stored function issues — grab the command and fix in under 60 seconds.
Syntax error on CREATE FUNCTION−
Immediate action
Insert DELIMITER $$ before CREATE and DELIMITER ; after END
Commands
SHOW WARNINGS; to get the exact line
Check for missing semicolons inside the body
Fix now
Wrap with DELIMITER $$ ... $$ DELIMITER ;
Function returns NULL but shouldn't+
Immediate action
Test with literal input
Commands
SELECT your_function(valid_input);
SHOW CREATE FUNCTION your_function;
Fix now
Add ELSE clause with default RETURN value
Query slow with function in WHERE+
Immediate action
Check if function reads a table
Commands
SHOW CREATE FUNCTION your_function;
EXPLAIN SELECT ... WHERE your_function(col) = X;
Fix now
Replace with JOIN or generated column
Permission denied on function execution+
Immediate action
Check current user grants
Commands
SHOW GRANTS FOR CURRENT_USER();
Identify function schema: SELECT ROUTINE_SCHEMA, ROUTINE_NAME FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_TYPE='FUNCTION';
Fix now
GRANT EXECUTE ON FUNCTION db.func TO 'user'@'host';
Data inconsistency between primary and replica+
Immediate action
Identify all NOT DETERMINISTIC functions used in replicated queries
Commands
SELECT ROUTINE_NAME, IS_DETERMINISTIC FROM INFORMATION_SCHEMA.ROUTINES WHERE ROUTINE_TYPE='FUNCTION';
Check binary log format: SHOW VARIABLES LIKE 'binlog_format';
Fix now
Recreate incorrectly marked functions with correct determinism and test with pt-table-checksum
A stored function returns exactly one scalar value and can be called inline anywhere an expression is valid
SELECT list, WHERE clause, ORDER BY, computed columns — unlike a stored procedure which requires CALL.
2
DETERMINISTIC is not just a hint; it affects query optimization, binary log behavior, and replication correctness. Incorrectly labeling a non-deterministic function causes silent wrong results on replicas
always declare it honestly.
3
MySQL has no ALTER FUNCTION for changing the body
the correct update pattern is DROP FUNCTION IF EXISTS followed by CREATE FUNCTION. Build this into every migration script from day one.
4
Grant your application user only EXECUTE on specific functions, never CREATE ROUTINE. This means a compromised app account cannot overwrite your database's business logic
a simple but powerful security boundary.
5
Stored functions that read data in WHERE clauses cause N+1 query patterns. Replace them with JOINs or use generated columns for performance-critical filtering.
Common mistakes to avoid
4 patterns
×
Forgetting the DELIMITER change before the function body
Symptom
MySQL treats the first semicolon inside BEGIN...END as the end of the CREATE FUNCTION statement, giving a cryptic syntax error on the line after the first semicolon in your body.
Fix
Always wrap the entire CREATE FUNCTION block with DELIMITER $$ at the top and DELIMITER ; at the bottom. GUI tools like MySQL Workbench do this automatically, but CLI and script files do not.
×
Declaring a NOT DETERMINISTIC function as DETERMINISTIC to 'avoid warnings'
Symptom
MySQL trusts your declaration and may cache or log the function incorrectly. In replicated setups, this causes silent data drift on replicas with no error thrown.
Fix
If the function calls NOW(), CURDATE(), RAND(), UUID(), or runs a SELECT on a mutable table, it must be declared NOT DETERMINISTIC. When in doubt, NOT DETERMINISTIC is the safe default.
×
Using a function that does a SELECT inside a high-frequency WHERE clause without understanding the N+1 cost
Symptom
If your function runs a SELECT query and you call it in the WHERE clause of a query returning 50,000 rows, MySQL executes that inner SELECT 50,000 times. The query becomes orders of magnitude slower.
Fix
For lookup-style functions used on large datasets, consider a JOIN instead of a function call, or materialize the computed column into the table with a generated column or scheduled update.
×
Not granting EXECUTE privilege to application users
Symptom
The application gets an 'EXECUTE command denied' error when trying to call the function, even if it has SELECT permissions on the underlying tables.
Fix
Explicitly grant EXECUTE on the function to the application database user: GRANT EXECUTE ON FUNCTION db.func_name TO 'app_user'@'%'. Do not grant CREATE ROUTINE to application users.
INTERVIEW PREP · PRACTICE MODE
Interview Questions on This Topic
Q01SENIOR
What is the difference between a stored function and a stored procedure ...
Q02SENIOR
What does the DETERMINISTIC keyword mean when declaring a stored functio...
Q03SENIOR
Can a MySQL stored function execute an INSERT or UPDATE statement? What ...
Q04SENIOR
How do you update the logic of an existing stored function in MySQL?
Q01 of 04SENIOR
What is the difference between a stored function and a stored procedure in MySQL, and how do you decide which one to use?
ANSWER
A stored function returns exactly one scalar value via a RETURN statement and can be used inline in SQL statements like SELECT, WHERE, or ORDER BY. A stored procedure does not directly return a value; it executes actions and may return output via OUT parameters or result sets. Use a function when you need a reusable calculation or lookup that can be embedded in a query. Use a procedure when you need to perform a sequence of operations, including data modifications, and when you don't need to embed the call in a query. Functions are also used inside triggers; procedures cannot be called from triggers.
Q02 of 04SENIOR
What does the DETERMINISTIC keyword mean when declaring a stored function, and what happens if you incorrectly mark a non-deterministic function as deterministic in a replicated MySQL setup?
ANSWER
DETERMINISTIC means that the function returns the same result for the same input parameters every time it is called, regardless of time, random values, or data changes. If you incorrectly mark a function as DETERMINISTIC when it actually uses NOW(), RAND(), or reads from a table that can change, the optimizer may cache stale results, and in statement-based replication, the binary log replays the function call with incorrect caching, producing data inconsistency between primary and replica. The replica could compute a different value than the primary did, leading to silent data drift. Always declare NOT DETERMINISTIC if the function depends on non-deterministic factors.
Q03 of 04SENIOR
Can a MySQL stored function execute an INSERT or UPDATE statement? What are the risks of doing so, and why do most style guides forbid it?
ANSWER
Technically, MySQL allows a stored function to execute INSERT, UPDATE, or DELETE statements, provided the function is not declared with NO SQL. However, doing so breaks the contract that a function is a pure computation like built-in functions. The main risks are: (1) The function can no longer be called in read-only contexts (e.g., on a read replica if binary log format is row-based). (2) It violates the principle of least surprise — a SELECT statement should not have side effects. (3) It can cause replication issues if the function is called on a replica that expects no data changes. Most style guides forbid it to maintain clarity and to keep functions safe for use in any SQL expression.
Q04 of 04SENIOR
How do you update the logic of an existing stored function in MySQL?
ANSWER
MySQL does not support ALTER FUNCTION to change the function body. You must drop the function first with DROP FUNCTION IF EXISTS, then recreate it with CREATE FUNCTION. This is intentional to make changes auditable and to prevent silent mutations. In migration scripts, always include the DROP before the CREATE. You can use SHOW CREATE FUNCTION to capture the current definition, edit it, and then apply the drop-and-recreate pattern.
01
What is the difference between a stored function and a stored procedure in MySQL, and how do you decide which one to use?
SENIOR
02
What does the DETERMINISTIC keyword mean when declaring a stored function, and what happens if you incorrectly mark a non-deterministic function as deterministic in a replicated MySQL setup?
SENIOR
03
Can a MySQL stored function execute an INSERT or UPDATE statement? What are the risks of doing so, and why do most style guides forbid it?
SENIOR
04
How do you update the logic of an existing stored function in MySQL?
SENIOR
FAQ · 5 QUESTIONS
Frequently Asked Questions
01
Can a MySQL stored function call another stored function?
Yes, absolutely. You can call any accessible stored function from inside another function's body, just like calling a built-in function. Just be careful about recursion — MySQL supports it but requires SET SESSION max_sp_recursion_depth to a value greater than 0, and deep recursion can cause stack overflows. Keep nesting shallow and purposeful.
Was this helpful?
02
Why do I get 'This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA' error?
This error appears when binary logging is enabled (which it is by default on most production servers) and you try to create a function without explicitly declaring its data access characteristic. MySQL requires the declaration so it can log replication events safely. Fix it by adding DETERMINISTIC, NOT DETERMINISTIC, NO SQL, or READS SQL DATA to your CREATE FUNCTION statement — pick the one that accurately describes what your function does.
Was this helpful?
03
What's the difference between a stored function and a generated column in MySQL?
A generated column computes a value at the row level and can optionally be stored and indexed on disk — ideal for expressions you filter or sort on frequently. A stored function computes a value on demand, at query time, and can contain conditional logic, local variables, and SELECT statements. Use a generated column for simple, indexable expressions; use a stored function for complex reusable logic that goes beyond a single expression.
Was this helpful?
04
Can I create a stored function in MySQL without specifying DETERMINISTIC or NOT DETERMINISTIC?
If binary logging is enabled (which is the default in most MySQL configurations), you must explicitly declare one of DETERMINISTIC, NOT DETERMINISTIC, NO SQL, or READS SQL DATA. If binary logging is disabled, the declaration is technically optional, but it's always a best practice to declare it for clarity and to avoid issues if logging is later enabled.
Was this helpful?
05
What is the maximum number of parameters a MySQL stored function can accept?
MySQL stored functions can accept up to 1023 parameters, consistent with the maximum number of columns or parameters in a routine. Beyond that, you should consider using a JSON parameter or redesigning the function to accept fewer inputs.