Database Access Tutorial

Purpose and Scope

This page explains the standard Go tutorial path for accessing a relational database from an application. The reader-facing workflow is the one presented by the official database access tutorial: create a module, configure a real database, import a driver, open a handle, query multiple rows, query one row, and add data. The repository evidence here is centered on the machinery that makes that workflow portable across drivers: the driver interface, argument conversion, context-aware execution, scan support, and synchronization around closing resources.

The most important mental model is that application code usually talks to the standard database package, while database-specific code is supplied by a driver. The driver owns the wire protocol, authentication details, placeholder syntax, and database-specific value handling. The standard package owns the public shape of operations such as querying, executing, preparing, beginning transactions, scanning, and managing pooled connections. That separation is why the tutorial can teach one workflow while still letting the same application structure work with MySQL, PostgreSQL, SQLite, or another supported database.

Sources: src/database/sql/driver/driver.go, src/database/sql/convert.go, src/database/sql/ctxutil.go

Relevant Source Files

  • src/database/sql/closemu.go - Defines the close-aware synchronization primitive used by database/sql internals to allow reentrant reads while coordinating resource close operations.
  • src/database/sql/convert.go - Implements argument and scan-related conversions, including named value validation, driver value checking, column conversion, and fallback default parameter conversion.
  • src/database/sql/ctxutil.go - Bridges context-aware database operations to older driver interfaces, covering prepare, exec, query, statement execution, statement query, and transaction begin behavior.
  • src/database/sql/driver/driver.go - Defines the public driver-side interfaces that database implementations provide to integrate with database/sql, including connection, context, validation, session reset, query, exec, and row metadata capabilities.
  • src/database/sql/driver/types.go - Defines driver value conversion contracts, including Value, NamedValue, Valuer, ValueConverter, and built-in converters used for consistent driver behavior.
  • src/database/sql/internal/sql.go - Provides the internal ScanContext wrapper shared between database/sql and database/sql/driver without exposing the underlying value directly to users.

Core Primitives

A database application begins with a database handle rather than a single physical connection. In user code, the handle represents access to a pool and is safe to share across application code. The source evidence describes the lower layer: a driver can open or connect to database sessions, and the standard package maintains idle connections for reuse rather than requiring drivers to cache them. This matters in the tutorial because opening the handle is not the same as proving every later query will succeed; actual work still goes through driver connections and can fail because of network, authentication, statement, or value-conversion errors.

The driver contract defines a small set of portable values that can cross the standard-package boundary. Driver values include nil, integers, floating point values, booleans, byte slices, strings, and time values, with room for custom handling through driver interfaces. Application types can implement a value method so they can convert themselves before execution. Conversely, values returned from the database must be scanned into application destinations. These contracts let ordinary Go structs and primitive fields participate in SQL operations while keeping each driver responsible for database-specific representation details.

Sources: src/database/sql/driver/driver.go, src/database/sql/driver/types.go, src/database/sql/internal/sql.go

Tutorial Flow

Start the tutorial by creating a normal Go module for the application and choosing a relational database system. The official tutorial uses a small records database to keep the examples focused on the program flow rather than schema complexity. After the module exists, import the driver package for side effects so it can register itself, then use the standard package as the public API. This import pattern is intentionally split: your code depends on stable database abstractions, while the selected driver supplies the concrete implementation behind those abstractions.

Once the driver is available, open a handle with a driver name and a data source string in the format expected by that driver. After opening, applications usually verify connectivity before relying on the handle for requests. From there, the main operations are querying multiple rows, querying a single row, and executing statements that change data. Each operation has two conceptually separate inputs: the SQL text and the values to bind to placeholders. Keeping those inputs separate is both a portability practice and a security practice.

rows, err := db.QueryContext(ctx, "SELECT id, title FROM album WHERE artist = ?", artist)

The example above shows the important shape rather than a driver-specific prescription. The placeholder marker can vary by database and driver, so a PostgreSQL driver may use a different marker form than a MySQL driver. The crucial rule is that parameter values are passed as arguments instead of being formatted into the SQL string. Repository code supports this model by converting caller arguments into driver named values, validating names, consulting driver-specific checkers when present, and falling back to standard conversion when the driver does not provide a specialized path.

Sources: src/database/sql/convert.go, src/database/sql/driver/types.go

System-to-Code Mapping

When an application calls a query or execution method with parameters, the standard package must turn ordinary Go arguments into driver-ready values. The conversion path checks whether the statement or connection supports named value checking, whether a statement exposes column conversion, and whether the value itself can produce a driver value. That layered design gives drivers a way to accept custom types, reject invalid input early, remove per-query option arguments, or enforce database column constraints before sending a request over the network.

Context-aware APIs are another key part of the tutorial workflow, especially for servers. The context utility layer first prefers modern driver interfaces that accept a context directly. If a driver only supports an older interface, the helper converts named values to positional values, checks cancellation before starting work, and handles cancellation after prepare or begin in the cases where cleanup is possible. For transactions, it also rejects read-only or non-default isolation options when the underlying driver cannot represent them safely.

Sources: src/database/sql/ctxutil.go, src/database/sql/convert.go, src/database/sql/driver/driver.go

Driver Contracts and Portability

The driver package documentation is explicit that most application code should use the standard database package rather than the driver package directly. Driver authors, however, should implement modern connector and context interfaces so the standard package can create connections efficiently and propagate cancellation. Connections should also implement validation, session reset, and ping behavior where possible. These interfaces are not tutorial ceremony; they are what allow a simple call in application code to participate in pooling, liveness checks, cancellation, and safe reuse across requests.

Rows and metadata are also part of the portability boundary. A driver can report multiple result sets and column type details, including scan type, database type name, length, nullability, and precision or scale. Application code may not need these features in the first tutorial, but they explain why scanning is not merely string parsing. The standard layer needs enough information to help map returned database values into user destinations, and the internal scan context wrapper lets the two packages share scan-related state without turning that state into public API surface.

Sources: src/database/sql/driver/driver.go, src/database/sql/internal/sql.go

Safety, Cancellation, and Resource Lifetime

The official SQL injection guidance aligns with the source-level conversion design: pass parameter values separately from the SQL string. Formatting values directly into a statement invites unexpected SQL fragments to become executable text. Passing values as arguments lets the standard package and driver treat the statement and the data independently, and it gives the conversion pipeline a chance to validate types before execution. This does not remove the need to understand each database’s placeholder syntax, but it keeps the basic tutorial pattern safe by default.

Cancellation is equally important in real programs. Web handlers, background jobs, and command-line tools should prefer context-aware query, exec, prepare, and transaction methods so callers can stop work that is no longer useful. The repository helper code demonstrates the compatibility rule: use the driver’s context-capable method if it exists, otherwise check cancellation around the older method where feasible. That means application code can adopt context-oriented APIs even while the ecosystem contains drivers at different implementation levels.

Resource lifetime is protected by both public API rules and internal synchronization. The close-aware mutex gives database internals a way to coordinate closing with concurrent read-style operations while permitting reentrant reads. Reads can proceed in the common case, but a close operation obtains exclusive access when the state permits. For tutorial readers, the practical lesson is simpler: close rows, statements, and handles when their lifetime ends, and use defers carefully so resources are returned promptly rather than waiting until long-running functions finish.

Sources: src/database/sql/closemu.go, src/database/sql/ctxutil.go, src/database/sql/convert.go

Practical Next Steps

After completing the basic database access tutorial, expand the example in the direction your application needs. Add context-aware calls throughout the code, handle the no-row case separately from other query errors, keep statement parameters separate from SQL text, and decide where transactions should define atomic units of work. If you are writing application code, stay at the standard database API level. If you are implementing or evaluating a driver, use the driver contracts to check support for context, validation, session reset, custom values, row metadata, and transaction options.

Related pages should be read in task order. Use the database overview to understand handles, connection pools, statements, and rows at a conceptual level. Then read querying and changing data for result scanning and execution details, prepared statements and transactions for lifetime and atomicity rules, and cancellation and SQL injection for production safety practices. Driver authors or advanced debuggers should also inspect the standard library API and compatibility pages, because exported interfaces in this area are long-lived contracts across Go releases.

Sources: src/database/sql/driver/driver.go, src/database/sql/driver/types.go, src/database/sql/ctxutil.go