Why I Finally Stopped Treating My Database Like a Black Box

Introduction

When I started working with Oracle SQL, my relationship with databases was simple: they stored my data, and I fetched it when I needed it. SELECT, INSERT, UPDATE, DELETE—that was my comfort zone. Anything beyond that felt like DBA territory, not something I had to worry about. After all, I was writing application code, not tuning Oracle.

But reality caught up quickly. I noticed that most production issues weren’t because our Java or Python code was “bad.” They came from slow queries, unexpected locks, and mysterious database errors. The more I built, the clearer it became that treating the database as a “black box” was costing serious time. Debugging sessions stretched longer than necessary, partly due to both me and ChatGPT hallucinating.

Working with Oracle SQL and PL/SQL further solidified this realization. Stored procedures, triggers, functions—I used to think they were overkill. But I started to see how much performance I could gain (or lose) based on a single design decision. A well-placed index could save me hours of waiting, while one poorly written query could bring an entire system to its knees. (This did happen to me BTW!)

That’s when I realized: knowing databases only at the CRUD level is like driving a Ferrari in first gear. (I copied this line from ChatGPT…but it’s true) You’ll move, but you’ll never hit the performance or control the machine is capable of.

In this post, I’ll share how I broke out of the CRUD trap, what Oracle and my team taught me (both the good and the ugly), and why every developer—especially juniors like me—needs to go deeper into databases. Not to become a DBA, but to become a developer who actually understands the backbone of their application.

The CRUD Trap: Why It’s Holding You Back

When I first learnt to code with a database (yep, it was MongoDB), CRUD felt like enough. Create a row, read it, update it, delete it—that covers most app features, right? For a long time, I thought of the database as nothing more than a fancy storage box. As long as the app worked and the tests passed, I didn’t care how the database handled things internally.

That mindset led me straight into what I now call the CRUD trap.

The “Database as Dumb Storage” Mentality

MongoDB crippled my SQL abilities, so I leaned on ORMs (Object-Relational Mappers) like they were magic. They saved me from writing SQL, but they also blinded me to what was actually happening underneath. When a query was slow, I assumed the problem was my code—or worse, I thought “we’ll just scale with more servers later.” Spoiler: throwing hardware at bad queries doesn’t fix them.

I also fell for the idea that ORMS eliminates the need to know SQL. They don’t. They just hide SQL from you until something breaks, and then you’re stuck staring at logs with queries you don’t understand.

The Real Costs of CRUD-Only Knowledge

Here’s what you can run into:

  • Performance disasters: An N+1 query slipped into production, generated by the ORM. Everything may look fine locally, but under load, it means disaster.

  • Scaling nightmares: An app that worked fine with a few users fell apart when traffic grew. Suddenly, 1,000 concurrent users meant blocked queries and timeouts everywhere.

  • Data corruption surprises: Without really understanding transactions or isolation levels, you may have race conditions that silently corrupt data. (These may be a bit easier to spot if you have your CS fundamentals on point)

The CRUD trap isn’t just about writing basic queries—it’s about staying blind to how your database behaves under real-world conditions. Once I realized that, I knew I had to step up and learn the stuff I had been avoiding.

My Oracle Journey: Lessons from the Trenches

Working with Oracle SQL and PL/SQL was my first real push beyond the CRUD/ORM bubble. At first, it felt intimidating—suddenly I was writing stored procedures, handling triggers, and looking at execution plans that looked more like hieroglyphs than code. But over time, I began to see why people say “process data where it lives.”

What PL/SQL Taught Me

  • Data-centric thinking: Instead of pulling thousands of rows into my application just to loop through them, I learned to let the database handle it in one go. A single well-written PL/SQL block could replace pages of application code. (I remember when I wrote a PL/SQL procedure that took 7 hours to complete. Cursors FTW!)

  • Performance gains: By reducing round-trips between the app and database, I saw query times drop drastically. I didn’t fully appreciate network latency until I watched a job go from minutes to seconds.

  • Complex business logic in one place: Multi-step operations—like validating input, updating multiple tables, and logging results—could all live inside a single transaction. That consistency was powerful. Especially in a complex system with different logically interdependent parts.

The Dark Side I Discovered

Of course, it wasn’t all smooth sailing. For every win, there was a tradeoff.

  • Maintenance hell: Debugging stored procedures at 2 AM is no fun. Error messages weren’t always clear, and tracking down the cause of a failure deep inside a PL/SQL block was painful. (Not all PL/SQL procedures/packages written were very debuggable either)

  • Vendor lock-in: The more business logic we pushed into Oracle-specific PL/SQL, the harder it became to even think about migrating to another database. I realized that too much reliance on proprietary features can paint you into a corner.

  • Blurred responsibility: Business logic lived partly in the application and partly in the database. This made it harder for new team members to figure out where a certain rule was enforced.

  • Synchronous Bottleneck: When you make a call to a PL/SQL procedure, it is typically a synchronous operation. Your application thread makes the call and then blocks, waiting for the entire operation to complete before it can do anything else. The entire time the database is doing this complex work (which could be CPU-intensive on the DB server), your application server is sitting idle, holding open a connection and waiting for a response. This increases response time for the end-user and ties up application resources (threads/connections) that could be serving other requests.

Oracle forced me to see that databases aren’t just storage—they’re engines. But it also showed me the danger of leaning too heavily on them for everything. That balance—between application logic, ORM convenience, and database power—is something I’m still learning.

The Essential Database Skills Every Developer Needs

Once I accepted that databases weren’t just “dumb storage,” I had to figure out what to actually learn ( I still am!). The problem is that “database knowledge” is a bottomless pit—you could study internals for years. As a junior dev, I focus on the skills that have the biggest impact on my day-to-day work and on fixing real production issues.

Here’s what I found mattered most for me:

4.1 Understanding Database Behavior & Performance

One of the first “aha” moments I had was reading execution plans. Oracle showed me that what I thought was “just a simple query” could be doing a full table scan of millions of rows.

  • Learn how your database fetches pages from disk, caches them, and reuses them. Suddenly, you’ll see why some queries are blazing fast and others crawl.

  • Execution plans are like X-rays for your queries—without them, you’re guessing.

  • Outdated statistics once made one of my queries 100x slower until I learned why the optimizer was making “bad” choices.

4.2 Transactions & Data Consistency

This part was fairly clear because of my college stuff—I knew the ACID acronym and what it meant.

  • Understanding ACID beyond the acronym saved me from silent data corruption.

  • I learned that isolation levels are tradeoffs: “read committed” prevents some bugs but not all, and “serializable” can lock up performance if misused.

  • One of my best understandings was deadlock—two transactions waiting on each other forever. Learning why that happens gave me confidence I never had with databases before.

4.3 Indexes: Your Performance Best Friend

Indexes were another turning point. I thought they were just “something DBAs handled.” Then I saw how the right index dropped a query from minutes to milliseconds.

  • Beyond the basics, composite indexes and Oracle’s bitmap indexes opened my eyes to the different tradeoffs.

  • But I also learned that indexes aren’t free—they can slow down inserts and updates. Balance matters.

4.4 Advanced Query Techniques

PL/SQL forced me to learn things I would have happily ignored:

  • Joins aren’t all the same—hash joins, nested loops, and merge joins behave differently at scale.

  • Window functions felt like magic once I learned them—suddenly I didn’t need ugly cursor loops. (still a bit shaky with those. AI helps!)

  • CTEs (WITH clauses) made my complex queries readable and maintainable.

ORMs: The Double-Edged Sword

For a while, I thought learning SQL and PL/SQL meant I could throw away ORMs. I was wrong. In most modern stacks, ORMs are unavoidable. They’re the glue between application code and the database, and honestly, they’re a huge productivity boost. (Especially for people like me who come from NoSQL backgrounds) But I learnt they can also be dangerous if you treat them as magic.

Why ORMs Are Essential

  • Developer productivity: As a junior, ORMs let me build features quickly without writing hundreds of lines of boilerplate SQL.

  • Type safety and abstraction: I could write code in my primary language (JavaScript, Python, etc.) and let the ORM handle mapping objects to tables.

  • Security benefits: ORMs protect against SQL injection out of the box—something beginners (like me at first) can easily get wrong.

  • Rapid prototyping: For CRUD-heavy apps, nothing beats scaffolding models and having queries “just work.”

The Problems I can run into

From looking around on the internet, I could see that ORMs were not bulletproof**.**

  • You can get hit with the N+1 query problem: fetching one user triggered a separate query for each related row. In production, this can tank performance.

  • ORMs can sometimes generate inefficient queries that no human would ever write.

  • Change tracking overhead can bloat memory usage in one of my apps.

The Right Way to Use ORMs (What I am going to do)

  • Always monitor query generation: Log queries in dev mode so I can see what’s happening under the hood.

  • Use raw SQL when needed: For complex reporting or batch updates, I’ll bypass the ORM. It’s not betrayal—it’s being practical.

  • Batch operations: ORMs aren’t great at bulk inserts/updates—sometimes raw SQL or stored procedures are the better option.

In short: ORMs aren’t the enemy, but they’re not a free pass either. The sweet spot is knowing enough SQL to understand and override your ORM when it misbehaves.

Stored Procedures vs Application Logic vs ORMs

One of the toughest lessons I’ve learned as a junior dev is that there’s no single “right” place for business logic. Sometimes it belongs in the application, sometimes in the database, and sometimes the ORM handles it just fine. The challenge is knowing which tool to use and when.

The Three-Way Decision Matrix

  • Simple CRUD operations → ORMs shine here. They save time, reduce boilerplate, and make your code easier to maintain. I don’t hand-write SQL just to fetch a user profile or do basic auth stuff anymore.

  • Complex business logic → This usually belongs in the application layer. It’s easier to test, version control, and debug in code than in a giant PL/SQL procedure. I learned the hard way that debugging a 500-line stored procedure at 2 AM is a nightmare.

  • Data-intensive operations → If you’re doing heavy aggregations or transformations, sometimes the database is the best place. Letting Oracle process millions of rows in one optimized query is far better than dragging that data into app code.

  • Reporting & analytics queries → Often best handled with raw SQL or database views. ORMs can’t always express these queries efficiently, and PL/SQL can make them too rigid.

Making the Right Choice

When I was starting, I often defaulted to whatever was easiest: procedures for everything, or raw SQL in JAVA code when the team said so. Now I ask myself a few questions first:

  • Can my team maintain this? If the answer is no, then putting it in PL/SQL just because it’s “faster” is a bad idea.

  • What are the performance requirements? For mission-critical paths (like payment processing), I consider stored procs or carefully optimized SQL.

  • How complex is deployment? Changing app code is usually easier than deploying updated stored procedures in production.

  • How will I test this? Application logic is easier to unit test. Database logic often requires integration testing.

The Balance I’m Learning

The truth is, it’s not about choosing one approach forever—it’s about balancing tradeoffs.

  • ORMs are great for speed and safety, but can betray you if you don’t understand the SQL behind them.

  • Application logic is maintainable, but can be slower if you’re moving tons of data out of the database.

  • Stored procedures are powerful but can lock you into a vendor and create long-term maintenance pain.

As a junior dev, I used to think the answer was to pick one and stick with it. Now I see that the real skill is knowing when to switch gears. There are countless approaches, and none are inherently wrong. The only thing is that there is always something better.

Scaling and Performance

At some point, everyone hits the wall where a query that felt “instant” during development now crawls when there are too many records. I learned the hard way that scaling a database isn’t just about throwing more hardware at it.

  • Query Design Matters: A poorly written query can break your system faster than a lack of RAM. Simple changes—like selecting only the fields you need instead of SELECT *—can massively cut down response times.

  • Partitioning and Sharding: Once datasets get too large, you can’t keep everything in one neat table. Horizontal partitioning (sharding) and vertical partitioning (splitting tables by columns) are real tools, not just academic jargon.

  • Connection Pooling: Early on, I let every request open a new connection. Unsurprisingly, the DB server keeled over under load. Pooling transformed my app’s stability.

Performance tuning isn’t a one-time activity—it’s a continuous loop of observing, testing, and adjusting.

Things I’m looking forward to learning

I’ve mostly traditionally approached databases—writing SQL queries, procedures, and making schema changes directly. But I know that’s not sustainable when projects scale. What I want to explore next is the idea of treating the database like code.

Some areas I’ve only scratched the surface of (or haven’t explored yet) include:

  • Migration Frameworks – Tools like Liquibase, Flyway, Alembic are on my radar, but I haven’t used them. I want to understand how they handle upgrades, rollbacks, and CI/CD integration.

  • Automated Database Builds – The idea of being able to spin up a database from scratch, seeded with data, using a single command, sounds powerful—and I want to get there.

  • Cross-Team Collaboration – I’d like to learn how developers, DBAs, and DevOps folks actually coordinate database changes in practice when following this model.

I suspect that once I start adopting these practices, my database skills will evolve from reactive fixes to deliberate design.

The Mindset Shift: From CRUD to Craft

So far, most of my work has been around CRUD operations and PL/SQL. But I realize databases can be so much more, and this is where I want to level up.

Things I haven’t explored yet, but want to:

  • Data Modeling for Scale – Normalization I know, but I haven’t dived into when to denormalize, how to structure schemas for analytics, or how modern systems like Postgres + JSONB balance relational and flexible storage.

  • Security & Access Control – I’ve relied on defaults so far, but I want to understand fine-grained roles, row-level security, and modern practices for multi-tenant applications.

  • Event-Driven Databases – Triggers I know about, but I haven’t explored how databases can participate in event-driven architectures (e.g., Postgres + Kafka, CDC pipelines).

  • Postgres & Beyond – My experience is heavily Oracle-centric. I want to broaden into Postgres and explore what features I’ve been missing out on.

For me, this mindset shift is about moving from just “getting the data out” to designing data systems intentionally—something I know I haven’t mastered yet, but want to.


If you still have any queries you can reach out to me on my LinkedIn / GitHub / Twitter.

Cheers!

0
Subscribe to my newsletter

Read articles from Saptarshi Bhattacharya directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Saptarshi Bhattacharya
Saptarshi Bhattacharya