MySQL Stored Functions — DETERMINISTIC Causes Replica Drift
Wrong DETERMINISTIC on a MySQL stored function caused replica drift.
20+ years shipping high-throughput database systems. Lessons pulled from things that broke in production.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- MySQL stored functions: named, reusable SQL logic returning one scalar value
- Called inline in SELECT, WHERE, ORDER BY — like built-in functions
- DETERMINISTIC declaration affects query optimization and replication correctness
- Performance: Function call in WHERE can't use indexes without functional index (MySQL 8.0+ only)
- Biggest mistake: Forgetting DELIMITER change causes cryptic syntax errors
- Production insight: Wrong DETERMINISTIC label causes silent replica drift
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.
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.
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.
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.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.
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.
- 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.
Calling a Stored Function From a Stored Procedure — Stop Writing Useless Wrappers
A stored function returns one value. A stored procedure does work. These are not the same thing, yet I've seen devs call a function from a procedure just so they can have a procedure do something a SELECT already could. That's cargo cult engineering.
Call a function from a procedure when you need to compute a value mid-logic that is reused across multiple statements. For example: calculate a discount rate once, then use it in an UPDATE, an INSERT, and a logging row. The function keeps the business rule in one place instead of scattered across three SQL statements.
Performance trap: if your function is not DETERMINISTIC and reads data, each call inside the procedure triggers a new query. That's fine for one call. For a loop calling it 10,000 times? You will learn why we have temp tables.
Here's the pattern: declare a local variable, assign the function result to it, then use that variable everywhere. Don't call the function multiple times — that defeats the point.
Error Handling Inside Stored Functions — Because Your Code WILL Fail
Stored functions have a dirty secret: they cannot use handler statements like DECLARE ... HANDLER that stored procedures can. If your function hits a constraint violation, a foreign key failure, or a division by zero, it will silently return NULL or throw an unhandled exception that kills the calling query.
This is not theory. I inherited a function that divided by a column that could be zero. The function worked for two years until a data migration inserted a zero. Suddenly every SELECT that used that function returned errors. Not NULL. Full stop errors.
Your only defense: validate inputs and use defensive SQL patterns. Check for NULL, check for division by zero, check for out-of-range values. Use IF statements before any risky operation. If the logic is complex enough to need exception handling, it should be a stored procedure — not a function.
If you absolutely must handle an error inside a function, consider using CONTINUE handlers with a simple workaround: set a flag variable and check it after the risky operation. It's ugly, but it works.
MySQL Stored Functions vs Stored Procedures
Stored functions and stored procedures are both database objects that encapsulate SQL logic, but they serve different purposes and have distinct characteristics. A stored function must return a single value and can be used directly in SQL expressions like SELECT, WHERE, or computed columns. In contrast, a stored procedure can perform multiple operations, return multiple result sets, and does not require a return value. Functions are limited to input parameters only (IN), while procedures support IN, OUT, and INOUT parameters. Functions cannot use statements that produce result sets (like SELECT without INTO), but procedures can. Additionally, functions are often required to be deterministic for replication safety, whereas procedures have more flexibility. Understanding these differences helps you choose the right tool: use functions for computations and data transformations within queries, and use procedures for complex business logic, batch processing, or operations that require transaction control.
MySQL 8.4: New SQL Features for Stored Functions
MySQL 8.4 introduces several enhancements that improve the usability and performance of stored functions. One notable feature is the ability to use window functions and CTEs (Common Table Expressions) inside stored functions, which was previously restricted. This allows for more complex analytical computations within a function. Additionally, MySQL 8.4 improves error handling with the RESIGNAL statement, enabling functions to propagate custom error messages. The new SQL standard compliance also allows for optional DETERMINISTIC clause enforcement in binary logging, reducing replica drift risks. Another improvement is the support for DEFAULT values in function parameters, simplifying function calls. For example, you can now define a function with a default parameter value, making it optional. These features make stored functions more powerful and flexible for modern application development. However, be aware that some features like window functions may impact performance if used excessively inside functions called in large queries.
Deterministic vs Non-Deterministic Functions: Binary Logging Impact
The DETERMINISTIC attribute is critical for MySQL replication and binary logging. A deterministic function always produces the same output for the same input, while a non-deterministic function may return different results (e.g., using RAND(), NOW(), or UUID()). When binary logging is set to STATEMENT or MIXED mode, MySQL logs the actual SQL statements. If a non-deterministic function is used in a statement, replicas may compute different values, leading to data inconsistency (replica drift). To prevent this, MySQL requires that stored functions used in statements that modify data (INSERT...SELECT, UPDATE, DELETE) be declared DETERMINISTIC or have a specific characteristic like NO SQL or READS SQL DATA. Otherwise, replication may fail or produce incorrect results. In ROW-based logging, the actual row changes are logged, so non-deterministic functions are safe, but performance may be impacted. Best practice: always declare functions as DETERMINISTIC if they are, and avoid non-deterministic functions in replication-critical statements. If you must use non-deterministic logic, consider using ROW-based logging or moving the logic to the application layer.
The Silent Replica Drift: How a Wrong DETERMINISTIC Declaration Caused Hours of Data Inconsistency
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.- 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.
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.SHOW WARNINGS; to get the exact lineCheck for missing semicolons inside the body| File | Command / Code | Purpose |
|---|---|---|
| create_loyalty_tier_function.sql | USE ecommerce_db; | What a Stored Function Actually Is (and When to Reach for On |
| query_with_loyalty_tier.sql | SELECT | Calling Your Function |
| determinism_examples.sql | DELIMITER $$ | DETERMINISTIC vs NOT DETERMINISTIC |
| manage_stored_functions.sql | SHOW FUNCTION STATUS WHERE Db = 'ecommerce_db'; | Managing Functions |
| performance_comparison.sql | SELECT p.product_name, p.price | Performance Considerations for Stored Functions in Productio |
| FulfillmentOrderDiscount.sql | DELIMITER $$ | Calling a Stored Function From a Stored Procedure |
| SafeCommissionCalc.sql | DELIMITER $$ | Error Handling Inside Stored Functions |
| function_vs_procedure.sql | CREATE FUNCTION calculate_discount(price DECIMAL(10,2), discount_rate DECIMAL(5,... | MySQL Stored Functions vs Stored Procedures |
| mysql84_features.sql | CREATE FUNCTION get_ranked_price(product_category INT) | MySQL 8.4 |
| deterministic_example.sql | CREATE FUNCTION add_tax(amount DECIMAL(10,2)) | Deterministic vs Non-Deterministic Functions |
Key takeaways
Interview Questions on This Topic
What is the difference between a stored function and a stored procedure in MySQL, and how do you decide which one to use?
Frequently Asked Questions
20+ years shipping high-throughput database systems. Lessons pulled from things that broke in production.
That's MySQL & PostgreSQL. Mark it forged?
8 min read · try the examples if you haven't