VillageSQL is a drop-in replacement for MySQL with extensions.
All examples in this guide work on VillageSQL. Install Now →
BETWEEN. VillageSQL’s INET type makes the schema more expressive and handles IPv4 and IPv6 in the same table.
How IP Geolocation Works
There’s no MySQL function that maps an IP to a country. Geolocation is a lookup problem: you have a table of IP ranges — each row says “addresses from X to Y are in country Z” — and you find which range contains the incoming IP. Free databases like MaxMind GeoLite2 provide these ranges as CSV files you import into MySQL.The Standard Approach: Integer Ranges
MaxMind GeoLite2 (and similar databases) ship withnetwork as a CIDR string and the start/end of each range as integers. The integer approach is compact and index-friendly:
start_ip enables efficient range scanning. The limitation is that it only works for IPv4. A separate table (with VARBINARY(16) ranges) is needed for IPv6.
With VillageSQL: Unified IPv4/IPv6 with INET
VillageSQL’sINET type stores both IPv4 and IPv6 addresses with natural ordering, so you can use a single table for both families. inet_compare() takes the place of BETWEEN for range checks.
Storing by CIDR network (alternative schema)
If your GeoIP data provides CIDR blocks rather than start/end pairs, useCIDR for the network column and resolve containment via subnet queries:
Choosing an Approach
| Approach | IPv6 | Schema | Performance |
|---|---|---|---|
INT UNSIGNED range + BETWEEN | No (separate table) | Two separate IPv4/IPv6 tables | Fast — integer index range scan |
INET range + inet_compare | Yes (unified) | Single table for both | Good — indexed on start_ip |
CIDR network + subnet match | Yes (unified) | Single table | Slower — per-row network computation |
INT UNSIGNED + BETWEEN approach is hard to beat. For schemas that need to handle both IPv4 and IPv6 in one table, INET with inet_compare is cleaner and avoids maintaining two parallel tables.
Comparing Geolocation Data Sources
The query schema is only half the decision — the data source determines accuracy, freshness, and cost. Here’s how the common options compare:| Data Source | Country accuracy | City accuracy | Latency | Cost | MySQL integration |
|---|---|---|---|---|---|
| MaxMind GeoLite2 (free) | ~98% | ~80% | In-process (SQL or .mmdb) | Free, registration required | Import CSV; update weekly |
| MaxMind GeoIP2 (paid) | ~99.8% | ~85–90% | In-process | From $20/mo | Same import flow as GeoLite2 |
| ip-api.com (free tier) | ~99% | ~83% | HTTP, ~30–100 ms | Free (45 req/min); paid removes limit | Application-layer HTTP call; results stored in MySQL |
| ip-api.com (pro) | ~99% | ~83% | HTTP, ~30–100 ms | From $15/mo | Same; HTTPS + no rate limit |
| ipinfo.io | ~99% | ~85% | HTTP, ~30–100 ms | Free (50k req/mo); paid tiers above | Application-layer; results can be cached in MySQL |
When to Use Each Approach
| Scenario | Recommended approach |
|---|---|
| Batch analytics on stored access logs | GeoLite2 in MySQL — join directly in SQL |
| Real-time lookup on every HTTP request (high volume) | MaxMind .mmdb reader in application code — no SQL round-trip |
| Low-volume enrichment (<45 req/min), no DB import overhead | ip-api.com free tier via HTTP |
| IPv4 only, maximum query speed | INT UNSIGNED + BETWEEN with GeoLite2 |
| IPv4 + IPv6 in a single table | VillageSQL INET + inet_compare with GeoLite2 |
| City, ASN, or ISP data needed | GeoIP2 Precision (paid) or ipinfo.io |
| Air-gapped environment, no external API calls | MaxMind GeoLite2 (local import) |
| Prototyping / testing with no setup | ip-api.com free tier |
Importing GeoLite2 Data
MaxMind GeoLite2 (free, requires registration) provides CSV files. For the range-based schema:.mmdb format and official client libraries for languages like Python, Node.js, and Go that are faster than SQL lookups for high-volume scenarios.
Frequently Asked Questions
Should I do geolocation in MySQL or in application code?
For high-volume lookups (every HTTP request), application-layer geolocation using MaxMind’s.mmdb reader library is faster — binary search in memory, no SQL round-trip. SQL-based geolocation makes sense for batch analytics, reporting queries, and data pipelines that already run in MySQL.
How do I keep the GeoIP database current?
MaxMind updates GeoLite2 weekly. Script a periodic download andTRUNCATE + reload of the geolocation table. Alternatively, use MaxMind’s mmdbinspect or a refresh script to apply delta updates.
How accurate is IP geolocation in MySQL?
Accuracy depends entirely on the data source, not the MySQL schema. At the country level, MaxMind GeoLite2 (free) is accurate for roughly 98% of IPv4 addresses. At the city level it drops to around 80%. For higher accuracy, MaxMind’s paid GeoIP2 Precision product reaches ~99.8% country and ~85–90% city. No database is perfect — accuracy is typically lower for mobile networks, VPNs, and some Asia-Pacific ranges. The MySQL query approach has no effect on accuracy; you get exactly what the imported data says.Can MySQL look up IP geolocation without a plugin or external API?
Yes. The standard approach — and what this guide covers — uses a GeoIP database (like MaxMind GeoLite2) imported into a regular MySQL table as integer ranges. Lookups are plain SQLBETWEEN queries. No plugin, no external API, no network call at query time. The only dependency is importing and periodically refreshing the flat CSV files from MaxMind. VillageSQL’s vsql_network_address extension adds an INET type for cleaner IPv4/IPv6 handling, but it’s not required for the basic approach.
Can I join geolocation with my access logs in one query?
Yes — that’s one of the main reasons to keep GeoIP data in MySQL:Troubleshooting
| Problem | Solution |
|---|---|
FUNCTION inet_from_string does not exist | Run INSTALL EXTENSION vsql_network_address |
| Lookup returns no results | Check that the IP range for that block was imported; try SELECT COUNT(*) FROM geoip_ranges |
| Lookup returns wrong country | GeoIP databases have coverage gaps — verify against MaxMind’s web lookup tool |
LOAD DATA INFILE fails | Check secure_file_priv setting; alternatively use application-side batch inserts |

