Orca, the Redis-compatible protocol in PolarDB for MySQL, supports Lua scripting for atomic execution of multiple commands in a single database interaction. Use Lua scripts to implement distributed locks, rate limiters, and conditional updates—without the latency and race condition risks of multiple round trips.
Prerequisites
Before you begin, make sure you have:
A PolarDB cluster running MySQL 8.0.2 with minor engine version 8.0.2.2.33 or later
An ORCA Endpoint configured with Read/Write set to Read/Write (Automatic Read/Write Splitting)
Key concepts
Atomicity — All commands in a Lua script execute as a single, indivisible unit. No other command or script can interrupt execution, which prevents partial writes and intermediate state.
Script caching — Load a script once with SCRIPT LOAD to get its SHA1 checksum, then invoke it repeatedly with EVALSHA using only the short SHA1 value. This reduces network traffic compared to sending the full script on every call.
Script cache volatility — The script cache is not persisted. It is cleared after a cluster restart, a high-availability (HA) switchover, or an explicit SCRIPT FLUSH call. Design your application to catch NOSCRIPT errors and reload scripts automatically.
Commands
All Lua scripting commands follow the same parameter convention: pass key names through KEYS[] and other arguments through ARGV[]. Array indices start at 1.
For the full Redis specification, see Scripting with Lua and EVAL command reference on redis.io.
| Command | Syntax | Description |
|---|---|---|
EVAL | EVAL script numkeys [key [key ...]] [arg [arg ...]] | Execute a Lua script directly. numkeys is a non-negative integer specifying how many KEYS[] arguments follow. |
EVAL_RO | EVAL_RO script numkeys [key [key ...]] [arg [arg ...]] | Execute a Lua script in read-only mode. Returns an error if the script contains write commands. Use this for read/write splitting scenarios. |
EVALSHA | EVALSHA sha1 numkeys [key [key ...]] [arg [arg ...]] | Execute a cached script by its SHA1 checksum. Returns a NOSCRIPT error if the script is not in the cache—reload it with EVAL or SCRIPT LOAD, then retry. |
EVALSHA_RO | EVALSHA_RO sha1 numkeys [key [key ...]] [arg [arg ...]] | Execute a cached script in read-only mode. Same behavior as EVALSHA with the write restriction of EVAL_RO. |
SCRIPT LOAD | SCRIPT LOAD script | Load a script into the cache and return its SHA1 checksum. Use the checksum with EVALSHA for subsequent calls. |
SCRIPT EXISTS | SCRIPT EXISTS sha1 [sha1 ...] | Check whether scripts are cached. Returns 1 for each cached script, 0 for each missing script. |
SCRIPT KILL | SCRIPT KILL | Terminate the currently executing script. Orca automatically rolls back any data changes made by the script, including write scripts. |
SCRIPT FLUSH | SCRIPT FLUSH [ASYNC | SYNC] | Clear all cached Lua scripts from the cluster. SYNC (default) blocks until complete; ASYNC clears the cache in the background. |
Unsupported commands
The following commands cannot be called inside a Lua script using redis.call() or redis.pcall():
Transaction control:
WATCH,UNWATCH,MULTI,EXEC,DISCARDBlocking commands:
BLPOP,BRPOPLua scripting commands:
EVAL,EVAL_RO,EVALSHA,EVALSHA_RO,SCRIPTPub/Sub commands:
SUBSCRIBE,UNSUBSCRIBE,PSUBSCRIBE,PUNSUBSCRIBEConnection management:
AUTH,HELLO,CLIENT
Calling any of these returns an error immediately.
Quick start
The following examples use the test key set by SET polardb orca.
EVAL
EVAL — Execute a script directly:
EVAL "return redis.call('GET', KEYS[1])" 1 polardbOutput:
"orca"SCRIPT LOAD
SCRIPT LOAD — Cache a script and get its SHA1 checksum:
SCRIPT LOAD "return redis.call('GET', KEYS[1])"Output:
"d3c21d0c2b9ca22f82737626a27bcaf5d288f99f"EVALSHA
EVALSHA — Run the cached script using its SHA1 checksum:
EVALSHA d3c21d0c2b9ca22f82737626a27bcaf5d288f99f 1 polardbOutput:
"orca"SCRIPT EXISTS
SCRIPT EXISTS — Check whether scripts are in the cache (first SHA1 exists, second does not):
SCRIPT EXISTS d3c21d0c2b9ca22f82737626a27bcaf5d288f99f ffffffffffffffffffffffffffffffffffffffffOutput:
1) (integer) 1
2) (integer) 0Common scenarios
Implement an atomic counter
Use Lua scripting to avoid race conditions from concurrent INCR operations. The SCRIPT LOAD + EVALSHA pattern keeps the script content off the wire for subsequent calls, reducing network traffic and improving throughput.
1. Write the script and save it locally:
-- counter.lua
-- Initialize the key to 0 if it does not exist, then increment by 1.
if redis.call('EXISTS', KEYS[1]) == 0 then
redis.call('SET', KEYS[1], 0)
end
return redis.call('INCR', KEYS[1])2. Load the script to get its SHA1 checksum:
SCRIPT LOAD "if redis.call('EXISTS', KEYS[1]) == 0 then redis.call('SET', KEYS[1], 0) end return redis.call('INCR', KEYS[1])"Output:
"b547eabbcde73b25330442e4f4e4dc1783b91241"3. Execute the script using its SHA1 checksum:
EVALSHA b547eabbcde73b25330442e4f4e4dc1783b91241 1 my_counterOutput on first call:
(integer) 1Output on second call:
(integer) 2Run a read-only script
Use EVAL_RO or EVALSHA_RO to guarantee that a script does not perform write operations. This is useful for read/write splitting scenarios where scripts must only touch read replicas.
Before using EVAL_RO, confirm your script calls no write commands such as SET, DEL, HSET, or LPUSH. If it does, use EVAL instead.
EVAL_RO "return {redis.call('GET', KEYS[1]), redis.call('TTL', KEYS[1])}" 1 polardbOutput:
1) "orca"
2) (integer) -1If the script contains write commands, EVAL_RO returns (error) ERR Write commands are not allowed from read-only scripts.
Stop a long-running script
If a script runs longer than expected and blocks the cluster, terminate it with SCRIPT KILL. Orca automatically rolls back all changes the script has made.
SCRIPT KILLOutput:
OKSCRIPT KILL terminates only the currently executing script. You cannot target a specific script by SHA1.
Best practices
Use SCRIPT LOAD + EVALSHA for production workloads
All examples below use SCRIPT LOAD at startup to cache scripts, then EVALSHA for every subsequent call. This pattern avoids retransmitting the full script on each request.
1. Load the script during application initialization:
# Cache a script that sets a key's value
SCRIPT LOAD "return redis.call('set', KEYS[1], ARGV[1])"Output:
"55b22c0d0cedf3866879ce7c854970626dcef0c3"Store this SHA1 value in your application.
2. Execute the script using the cached SHA1:
# Set k1 = v1
EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k1 v1
# Set k2 = v2 using the same cached script
EVALSHA 55b22c0d0cedf3866879ce7c854970626dcef0c3 1 k2 v23. Handle `NOSCRIPT` errors:
If EVALSHA returns a NOSCRIPT error, the script is no longer in the cache. Switch to EVAL or SCRIPT LOAD to re-execute and re-cache the script, then resume using EVALSHA.
Parameterize scripts with KEYS[] and ARGV[]
Pass all variable data through KEYS[] and ARGV[] instead of hard-coding values in the script body. Hard-coded scripts are unique strings—each variation generates a separate cache entry, which inflates memory usage and defeats caching.
The following example shows the anti-pattern and the correct approach:
-- Anti-pattern: each hard-coded variant creates a separate cache entry
EVAL "return 'Hello'" 0
EVAL "return 'Scripting!'" 0
-- Correct: one parameterized script reuses the same cache entry for any value
EVAL "return ARGV[1]" 0 Hello
EVAL "return ARGV[1]" 0 Scripting!Keep scripts short and fast
Lua scripts execute atomically and block all other commands during execution. Long-running or large-batch scripts increase cluster latency for every other client.
Avoid complex loops or large-scale data traversals inside scripts.
The default execution timeout is 300 seconds. A script that exceeds this limit is a signal to simplify the logic.
Split complex operations into multiple smaller scripts instead of one large one.
Clear the script cache safely
Use SCRIPT FLUSH to remove all cached scripts. After flushing, any call to EVALSHA or EVALSHA_RO returns a NOSCRIPT error until scripts are reloaded.
On clusters with many cached scripts, SCRIPT FLUSH may block the cluster for an extended period. Run this command during off-peak hours.
SCRIPT FLUSHOutput:
OKKeep script source code in your application and implement automatic recovery for NOSCRIPT errors. On error, reload the script with SCRIPT LOAD or re-register it with EVAL.
FAQ
How do I fix `NOSCRIPT No matching script. Please use EVAL.`?
The script is no longer in the cluster cache. This happens after a cluster restart, an HA switchover, or a SCRIPT FLUSH call. In your client, catch NOSCRIPT errors and fall back to EVAL or SCRIPT LOAD to re-register the script. After re-registration, EVALSHA calls work again as expected.
What should I do when a script times out?
The default timeout is 300 seconds. A timeout usually means the script logic is too complex or processes too much data. To recover, run SCRIPT KILL—PolarDB rolls back all changes the script made. To prevent recurrence, optimize the script to avoid inefficient loops or large-scale data reads, or split the logic into multiple smaller scripts.
Why does `EVAL_RO` return `ERR Write commands are not allowed from read-only scripts.`?
EVAL_RO and EVALSHA_RO prohibit all write operations. Check the script for write commands such as SET, HSET, or DEL and remove them. If the script needs to write data, use EVAL or EVALSHA instead.