It was a Friday afternoon when the alerts started coming in.
Response times had jumped from 50ms to 8 seconds across the board. Other queries were timing out. The on-call engineer got paged at 2am. We didn't get back to a clean state until Sunday.
The culprit was a single query. SELECT * against a 10 million row table with no WHERE clause. It had passed code review. It looked fine. It wasn't fine.
That weekend I started writing down every pattern like it I'd seen cause a production incident. Not style issues, not formatting, the queries that actually hurt you.
Here's what the list looked like after two years.
The ones that kill performance
Leading wildcards are probably the most common. LIKE '%keyword%' looks harmless and works fine in development. What it actually does is tell the database it cannot use the index because it doesn't know what the string starts with. Every row gets scanned. On a small table you never notice. On a large one you've just written a query that gets slower every week as the table grows.
Function calls on indexed columns do the same thing silently. WHERE LOWER(email) = 'john@example.com' forces the database to apply the function to every row before it can compare. The index on email is useless. Write it as WHERE email = 'john@example.com' and store emails lowercased at insert time instead.
Implicit type coercions are the sneaky one. If user_id is an integer and you write WHERE user_id = '123' with a string, most databases will cast every value in the column to match your input type. Index bypassed, full scan, no warning anywhere.
The ones that cause data loss
DELETE FROM users is valid SQL. It will execute without complaint. It will delete every row in the table. There is no undo.
Same with UPDATE users SET status = 'inactive' with no WHERE clause. Every row gets updated. These happen in migrations more than anywhere else, someone writes the query, tests it on a filtered subset locally, forgets the WHERE clause, runs it on prod.
The security ones
Dynamic SQL is still showing up in codebases in 2025. EXEC('SELECT * FROM users WHERE id = ' + @userId) is a textbook injection vector. The fix is parameterized queries, always.
Hardcoded credentials in SQL files are more common than you'd think. UPDATE config SET api_key = 'sk_live_abc123' sitting in a migration file, committed to git, forever.
The cost ones
This one is specific to columnar and serverless databases like Athena, BigQuery and Redshift. These engines bill you per byte scanned. SELECT * on a wide table with 50 columns when you needed 3 means you're paying for 50 columns of data on every single query. At scale that adds up fast.
What I built
After two years of collecting these patterns I turned them into a static analyzer called SlowQL. You point it at your SQL files and it flags the issues before anything ships. 171 rules across security, performance, reliability, compliance and cost. Works offline, zero dependencies, plugs into CI as a pre-commit hook or a build step.
The philosophy is the same as any other static analysis tool. You don't wait for a JavaScript runtime error to find a type mismatch, you let the linter catch it at write time. SQL deserves the same treatment.
pip install slowql
github.com/makroumi/slowql
The patterns above are a small sample of what it catches. If you've been burned by something not on this list I'd genuinely like to hear it.
For further actions, you may consider blocking this person and/or reporting abuse
Top comments (0)