[{"data":1,"prerenderedAt":3909},["ShallowReactive",2],{"page-\u002Fasync-engines-dialects-and-connection-pooling\u002Fselecting-async-drivers-for-sqlite-mysql-and-postgres\u002F":3},{"id":4,"title":5,"body":6,"description":3901,"extension":3902,"meta":3903,"navigation":123,"path":3905,"seo":3906,"stem":3907,"__hash__":3908},"content\u002Fasync-engines-dialects-and-connection-pooling\u002Fselecting-async-drivers-for-sqlite-mysql-and-postgres\u002Findex.md","Selecting Async Drivers for SQLite, MySQL, and Postgres",{"type":7,"value":8,"toc":3877},"minimark",[9,13,35,40,55,58,87,90,93,205,212,367,370,374,388,393,927,931,934,1069,1095,1099,1106,1110,1135,1333,1348,1352,1370,1461,1465,1469,1499,1510,1515,1523,1528,1588,1592,1600,1613,1622,1806,1824,1828,1869,1892,1896,1903,1906,1920,1924,1940,2265,2269,2273,2276,2281,2330,2335,2372,2710,2718,2722,2725,3231,3234,3238,3257,3339,3350,3392,3400,3404,3532,3536,3542,3567,3596,3602,3608,3612,3644,3873],[10,11,5],"h1",{"id":12},"selecting-async-drivers-for-sqlite-mysql-and-postgres",[14,15,16,17,22,23,27,28,27,31,34],"p",{},"Choosing the right async database driver is one of the first architecture decisions you make when building an async Python service, and the answer is backend-specific. This guide — part of the ",[18,19,21],"a",{"href":20},"\u002Fasync-engines-dialects-and-connection-pooling\u002F","Async Engines, Dialects, and Connection Pooling"," reference — maps each major backend to its mature async driver options, explains the URL scheme differences (",[24,25,26],"code",{},"postgresql+asyncpg:\u002F\u002F",", ",[24,29,30],{},"mysql+aiomysql:\u002F\u002F",[24,32,33],{},"sqlite+aiosqlite:\u002F\u002F","), and gives you the practical knowledge to pick the right driver for production, staging, and test environments.",[36,37,39],"h2",{"id":38},"concept-execution-model","Concept & Execution Model",[14,41,42,43,46,47,50,51,54],{},"SQLAlchemy 2.0 introduced a unified async surface through ",[24,44,45],{},"create_async_engine()"," and ",[24,48,49],{},"AsyncSession",". The engine accepts a dialect-qualified URL where the prefix before ",[24,52,53],{},"+"," identifies the backend (postgresql, mysql, sqlite) and the suffix identifies the driver. SQLAlchemy's dialect layer translates ORM operations into driver-specific wire calls transparently — but the driver's execution model, pooling behaviour, and feature set differ significantly.",[14,56,57],{},"The three async drivers you will encounter in everyday Python async development are:",[59,60,61,72,81],"ul",{},[62,63,64,46,68,71],"li",{},[65,66,67],"strong",{},"asyncpg",[65,69,70],{},"psycopg3"," for PostgreSQL",[62,73,74,46,77,80],{},[65,75,76],{},"aiomysql",[65,78,79],{},"asyncmy"," for MySQL and MariaDB",[62,82,83,86],{},[65,84,85],{},"aiosqlite"," for SQLite",[14,88,89],{},"Each driver wraps a native async or asyncio-compatible interface for its respective database protocol. None of them share code; they have distinct connection lifecycle semantics, distinct pool compatibility characteristics, and distinct behaviours when confronted with SQLAlchemy's autobegin\u002Fautoflush machinery.",[14,91,92],{},"The relationship between URL scheme, driver package, and SQLAlchemy dialect string:",[94,95,100],"pre",{"className":96,"code":97,"language":98,"meta":99,"style":99},"language-python shiki shiki-themes github-light github-dark","# PostgreSQL — asyncpg (native async, binary protocol)\n\"postgresql+asyncpg:\u002F\u002Fuser:password@host:5432\u002Fdbname\"\n\n# PostgreSQL — psycopg3 (libpq-backed async)\n\"postgresql+psycopg:\u002F\u002Fuser:password@host:5432\u002Fdbname\"\n\n# MySQL — aiomysql (asyncio wrapper over pymysql)\n\"mysql+aiomysql:\u002F\u002Fuser:password@host:3306\u002Fdbname\"\n\n# MySQL — asyncmy (pure-Python async MySQL)\n\"mysql+asyncmy:\u002F\u002Fuser:password@host:3306\u002Fdbname\"\n\n# SQLite — aiosqlite (asyncio wrapper over sqlite3)\n\"sqlite+aiosqlite:\u002F\u002F\u002Fpath\u002Fto\u002Flocal.db\"\n\n# SQLite — in-memory (tests\u002FCI)\n\"sqlite+aiosqlite:\u002F\u002F\"\n","python","",[24,101,102,111,118,125,131,137,142,148,154,159,165,171,176,182,188,193,199],{"__ignoreMap":99},[103,104,107],"span",{"class":105,"line":106},"line",1,[103,108,110],{"class":109},"sJ8bj","# PostgreSQL — asyncpg (native async, binary protocol)\n",[103,112,114],{"class":105,"line":113},2,[103,115,117],{"class":116},"sZZnC","\"postgresql+asyncpg:\u002F\u002Fuser:password@host:5432\u002Fdbname\"\n",[103,119,121],{"class":105,"line":120},3,[103,122,124],{"emptyLinePlaceholder":123},true,"\n",[103,126,128],{"class":105,"line":127},4,[103,129,130],{"class":109},"# PostgreSQL — psycopg3 (libpq-backed async)\n",[103,132,134],{"class":105,"line":133},5,[103,135,136],{"class":116},"\"postgresql+psycopg:\u002F\u002Fuser:password@host:5432\u002Fdbname\"\n",[103,138,140],{"class":105,"line":139},6,[103,141,124],{"emptyLinePlaceholder":123},[103,143,145],{"class":105,"line":144},7,[103,146,147],{"class":109},"# MySQL — aiomysql (asyncio wrapper over pymysql)\n",[103,149,151],{"class":105,"line":150},8,[103,152,153],{"class":116},"\"mysql+aiomysql:\u002F\u002Fuser:password@host:3306\u002Fdbname\"\n",[103,155,157],{"class":105,"line":156},9,[103,158,124],{"emptyLinePlaceholder":123},[103,160,162],{"class":105,"line":161},10,[103,163,164],{"class":109},"# MySQL — asyncmy (pure-Python async MySQL)\n",[103,166,168],{"class":105,"line":167},11,[103,169,170],{"class":116},"\"mysql+asyncmy:\u002F\u002Fuser:password@host:3306\u002Fdbname\"\n",[103,172,174],{"class":105,"line":173},12,[103,175,124],{"emptyLinePlaceholder":123},[103,177,179],{"class":105,"line":178},13,[103,180,181],{"class":109},"# SQLite — aiosqlite (asyncio wrapper over sqlite3)\n",[103,183,185],{"class":105,"line":184},14,[103,186,187],{"class":116},"\"sqlite+aiosqlite:\u002F\u002F\u002Fpath\u002Fto\u002Flocal.db\"\n",[103,189,191],{"class":105,"line":190},15,[103,192,124],{"emptyLinePlaceholder":123},[103,194,196],{"class":105,"line":195},16,[103,197,198],{"class":109},"# SQLite — in-memory (tests\u002FCI)\n",[103,200,202],{"class":105,"line":201},17,[103,203,204],{"class":116},"\"sqlite+aiosqlite:\u002F\u002F\"\n",[14,206,207,208,211],{},"SQLAlchemy builds an ",[24,209,210],{},"AsyncEngine"," from any of these URLs with identical Python code:",[94,213,215],{"className":96,"code":214,"language":98,"meta":99,"style":99},"from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession\n\nengine = create_async_engine(\n    \"postgresql+asyncpg:\u002F\u002Fapp:secret@db:5432\u002Fproduction\",\n    pool_size=20,\n    max_overflow=10,\n    pool_pre_ping=True,\n    pool_recycle=1800,\n    echo=False,\n)\n\nAsyncSessionFactory = async_sessionmaker(\n    engine,\n    class_=AsyncSession,\n    expire_on_commit=False,\n)\n",[24,216,217,233,237,248,256,270,282,294,306,318,323,327,337,342,352,363],{"__ignoreMap":99},[103,218,219,223,227,230],{"class":105,"line":106},[103,220,222],{"class":221},"szBVR","from",[103,224,226],{"class":225},"sVt8B"," sqlalchemy.ext.asyncio ",[103,228,229],{"class":221},"import",[103,231,232],{"class":225}," create_async_engine, async_sessionmaker, AsyncSession\n",[103,234,235],{"class":105,"line":113},[103,236,124],{"emptyLinePlaceholder":123},[103,238,239,242,245],{"class":105,"line":120},[103,240,241],{"class":225},"engine ",[103,243,244],{"class":221},"=",[103,246,247],{"class":225}," create_async_engine(\n",[103,249,250,253],{"class":105,"line":127},[103,251,252],{"class":116},"    \"postgresql+asyncpg:\u002F\u002Fapp:secret@db:5432\u002Fproduction\"",[103,254,255],{"class":225},",\n",[103,257,258,262,264,268],{"class":105,"line":133},[103,259,261],{"class":260},"s4XuR","    pool_size",[103,263,244],{"class":221},[103,265,267],{"class":266},"sj4cs","20",[103,269,255],{"class":225},[103,271,272,275,277,280],{"class":105,"line":139},[103,273,274],{"class":260},"    max_overflow",[103,276,244],{"class":221},[103,278,279],{"class":266},"10",[103,281,255],{"class":225},[103,283,284,287,289,292],{"class":105,"line":144},[103,285,286],{"class":260},"    pool_pre_ping",[103,288,244],{"class":221},[103,290,291],{"class":266},"True",[103,293,255],{"class":225},[103,295,296,299,301,304],{"class":105,"line":150},[103,297,298],{"class":260},"    pool_recycle",[103,300,244],{"class":221},[103,302,303],{"class":266},"1800",[103,305,255],{"class":225},[103,307,308,311,313,316],{"class":105,"line":156},[103,309,310],{"class":260},"    echo",[103,312,244],{"class":221},[103,314,315],{"class":266},"False",[103,317,255],{"class":225},[103,319,320],{"class":105,"line":161},[103,321,322],{"class":225},")\n",[103,324,325],{"class":105,"line":167},[103,326,124],{"emptyLinePlaceholder":123},[103,328,329,332,334],{"class":105,"line":173},[103,330,331],{"class":225},"AsyncSessionFactory ",[103,333,244],{"class":221},[103,335,336],{"class":225}," async_sessionmaker(\n",[103,338,339],{"class":105,"line":178},[103,340,341],{"class":225},"    engine,\n",[103,343,344,347,349],{"class":105,"line":184},[103,345,346],{"class":260},"    class_",[103,348,244],{"class":221},[103,350,351],{"class":225},"AsyncSession,\n",[103,353,354,357,359,361],{"class":105,"line":190},[103,355,356],{"class":260},"    expire_on_commit",[103,358,244],{"class":221},[103,360,315],{"class":266},[103,362,255],{"class":225},[103,364,365],{"class":105,"line":195},[103,366,322],{"class":225},[14,368,369],{},"Swapping the URL is all that changes between backends from the application's perspective. The sections below examine what changes underneath.",[36,371,373],{"id":372},"query-construction-async-execution-patterns","Query Construction & Async Execution Patterns",[14,375,376,377,380,381,380,384,387],{},"SQLAlchemy 2.0's ",[24,378,379],{},"select()"," \u002F ",[24,382,383],{},"execute()",[24,385,386],{},"scalars()"," pipeline is dialect-agnostic. The same query code runs against Postgres, MySQL, or SQLite without modification for standard operations. Driver differences surface in type-specific behaviour, auto-increment semantics, and SQL dialect extensions.",[389,390,392],"h3",{"id":391},"standard-async-query-pattern","Standard async query pattern",[94,394,396],{"className":96,"code":395,"language":98,"meta":99,"style":99},"import asyncio\nfrom sqlalchemy import select\nfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine\nfrom sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column\n\n\nclass Base(DeclarativeBase):\n    pass\n\n\nclass User(Base):\n    __tablename__ = \"users\"\n\n    id: Mapped[int] = mapped_column(primary_key=True)\n    email: Mapped[str] = mapped_column(unique=True, nullable=False)\n    tenant_id: Mapped[int] = mapped_column(nullable=False)\n    is_active: Mapped[bool] = mapped_column(default=True)\n\n\nasync def get_active_users(session: AsyncSession, tenant_id: int) -> list[User]:\n    stmt = (\n        select(User)\n        .where(User.tenant_id == tenant_id)\n        .where(User.is_active.is_(True))\n        .order_by(User.email)\n    )\n    result = await session.execute(stmt)\n    return result.scalars().all()\n\n\nasync def main(db_url: str) -> None:\n    engine = create_async_engine(db_url, echo=False)\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n    factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)\n    async with factory() as session:\n        users = await get_active_users(session, tenant_id=1)\n        print(f\"Found {len(users)} active users\")\n    await engine.dispose()\n\n# Works with any of the URL schemes above — swap the string, keep the logic.\nasyncio.run(main(\"postgresql+asyncpg:\u002F\u002Fapp:secret@localhost\u002Fmydb\"))\n",[24,397,398,405,417,428,440,444,448,466,471,475,479,493,503,507,535,567,588,611,616,621,641,652,658,670,681,687,693,707,716,721,726,750,770,788,797,802,830,845,868,896,905,910,916],{"__ignoreMap":99},[103,399,400,402],{"class":105,"line":106},[103,401,229],{"class":221},[103,403,404],{"class":225}," asyncio\n",[103,406,407,409,412,414],{"class":105,"line":113},[103,408,222],{"class":221},[103,410,411],{"class":225}," sqlalchemy ",[103,413,229],{"class":221},[103,415,416],{"class":225}," select\n",[103,418,419,421,423,425],{"class":105,"line":120},[103,420,222],{"class":221},[103,422,226],{"class":225},[103,424,229],{"class":221},[103,426,427],{"class":225}," AsyncSession, async_sessionmaker, create_async_engine\n",[103,429,430,432,435,437],{"class":105,"line":127},[103,431,222],{"class":221},[103,433,434],{"class":225}," sqlalchemy.orm ",[103,436,229],{"class":221},[103,438,439],{"class":225}," DeclarativeBase, Mapped, mapped_column\n",[103,441,442],{"class":105,"line":133},[103,443,124],{"emptyLinePlaceholder":123},[103,445,446],{"class":105,"line":139},[103,447,124],{"emptyLinePlaceholder":123},[103,449,450,453,457,460,463],{"class":105,"line":144},[103,451,452],{"class":221},"class",[103,454,456],{"class":455},"sScJk"," Base",[103,458,459],{"class":225},"(",[103,461,462],{"class":455},"DeclarativeBase",[103,464,465],{"class":225},"):\n",[103,467,468],{"class":105,"line":150},[103,469,470],{"class":221},"    pass\n",[103,472,473],{"class":105,"line":156},[103,474,124],{"emptyLinePlaceholder":123},[103,476,477],{"class":105,"line":161},[103,478,124],{"emptyLinePlaceholder":123},[103,480,481,483,486,488,491],{"class":105,"line":167},[103,482,452],{"class":221},[103,484,485],{"class":455}," User",[103,487,459],{"class":225},[103,489,490],{"class":455},"Base",[103,492,465],{"class":225},[103,494,495,498,500],{"class":105,"line":173},[103,496,497],{"class":225},"    __tablename__ ",[103,499,244],{"class":221},[103,501,502],{"class":116}," \"users\"\n",[103,504,505],{"class":105,"line":178},[103,506,124],{"emptyLinePlaceholder":123},[103,508,509,512,515,518,521,523,526,529,531,533],{"class":105,"line":184},[103,510,511],{"class":266},"    id",[103,513,514],{"class":225},": Mapped[",[103,516,517],{"class":266},"int",[103,519,520],{"class":225},"] ",[103,522,244],{"class":221},[103,524,525],{"class":225}," mapped_column(",[103,527,528],{"class":260},"primary_key",[103,530,244],{"class":221},[103,532,291],{"class":266},[103,534,322],{"class":225},[103,536,537,540,543,545,547,549,552,554,556,558,561,563,565],{"class":105,"line":190},[103,538,539],{"class":225},"    email: Mapped[",[103,541,542],{"class":266},"str",[103,544,520],{"class":225},[103,546,244],{"class":221},[103,548,525],{"class":225},[103,550,551],{"class":260},"unique",[103,553,244],{"class":221},[103,555,291],{"class":266},[103,557,27],{"class":225},[103,559,560],{"class":260},"nullable",[103,562,244],{"class":221},[103,564,315],{"class":266},[103,566,322],{"class":225},[103,568,569,572,574,576,578,580,582,584,586],{"class":105,"line":195},[103,570,571],{"class":225},"    tenant_id: Mapped[",[103,573,517],{"class":266},[103,575,520],{"class":225},[103,577,244],{"class":221},[103,579,525],{"class":225},[103,581,560],{"class":260},[103,583,244],{"class":221},[103,585,315],{"class":266},[103,587,322],{"class":225},[103,589,590,593,596,598,600,602,605,607,609],{"class":105,"line":201},[103,591,592],{"class":225},"    is_active: Mapped[",[103,594,595],{"class":266},"bool",[103,597,520],{"class":225},[103,599,244],{"class":221},[103,601,525],{"class":225},[103,603,604],{"class":260},"default",[103,606,244],{"class":221},[103,608,291],{"class":266},[103,610,322],{"class":225},[103,612,614],{"class":105,"line":613},18,[103,615,124],{"emptyLinePlaceholder":123},[103,617,619],{"class":105,"line":618},19,[103,620,124],{"emptyLinePlaceholder":123},[103,622,624,627,630,633,636,638],{"class":105,"line":623},20,[103,625,626],{"class":221},"async",[103,628,629],{"class":221}," def",[103,631,632],{"class":455}," get_active_users",[103,634,635],{"class":225},"(session: AsyncSession, tenant_id: ",[103,637,517],{"class":266},[103,639,640],{"class":225},") -> list[User]:\n",[103,642,644,647,649],{"class":105,"line":643},21,[103,645,646],{"class":225},"    stmt ",[103,648,244],{"class":221},[103,650,651],{"class":225}," (\n",[103,653,655],{"class":105,"line":654},22,[103,656,657],{"class":225},"        select(User)\n",[103,659,661,664,667],{"class":105,"line":660},23,[103,662,663],{"class":225},"        .where(User.tenant_id ",[103,665,666],{"class":221},"==",[103,668,669],{"class":225}," tenant_id)\n",[103,671,673,676,678],{"class":105,"line":672},24,[103,674,675],{"class":225},"        .where(User.is_active.is_(",[103,677,291],{"class":266},[103,679,680],{"class":225},"))\n",[103,682,684],{"class":105,"line":683},25,[103,685,686],{"class":225},"        .order_by(User.email)\n",[103,688,690],{"class":105,"line":689},26,[103,691,692],{"class":225},"    )\n",[103,694,696,699,701,704],{"class":105,"line":695},27,[103,697,698],{"class":225},"    result ",[103,700,244],{"class":221},[103,702,703],{"class":221}," await",[103,705,706],{"class":225}," session.execute(stmt)\n",[103,708,710,713],{"class":105,"line":709},28,[103,711,712],{"class":221},"    return",[103,714,715],{"class":225}," result.scalars().all()\n",[103,717,719],{"class":105,"line":718},29,[103,720,124],{"emptyLinePlaceholder":123},[103,722,724],{"class":105,"line":723},30,[103,725,124],{"emptyLinePlaceholder":123},[103,727,729,731,733,736,739,741,744,747],{"class":105,"line":728},31,[103,730,626],{"class":221},[103,732,629],{"class":221},[103,734,735],{"class":455}," main",[103,737,738],{"class":225},"(db_url: ",[103,740,542],{"class":266},[103,742,743],{"class":225},") -> ",[103,745,746],{"class":266},"None",[103,748,749],{"class":225},":\n",[103,751,753,756,758,761,764,766,768],{"class":105,"line":752},32,[103,754,755],{"class":225},"    engine ",[103,757,244],{"class":221},[103,759,760],{"class":225}," create_async_engine(db_url, ",[103,762,763],{"class":260},"echo",[103,765,244],{"class":221},[103,767,315],{"class":266},[103,769,322],{"class":225},[103,771,773,776,779,782,785],{"class":105,"line":772},33,[103,774,775],{"class":221},"    async",[103,777,778],{"class":221}," with",[103,780,781],{"class":225}," engine.begin() ",[103,783,784],{"class":221},"as",[103,786,787],{"class":225}," conn:\n",[103,789,791,794],{"class":105,"line":790},34,[103,792,793],{"class":221},"        await",[103,795,796],{"class":225}," conn.run_sync(Base.metadata.create_all)\n",[103,798,800],{"class":105,"line":799},35,[103,801,124],{"emptyLinePlaceholder":123},[103,803,805,808,810,813,816,818,821,824,826,828],{"class":105,"line":804},36,[103,806,807],{"class":225},"    factory ",[103,809,244],{"class":221},[103,811,812],{"class":225}," async_sessionmaker(engine, ",[103,814,815],{"class":260},"class_",[103,817,244],{"class":221},[103,819,820],{"class":225},"AsyncSession, ",[103,822,823],{"class":260},"expire_on_commit",[103,825,244],{"class":221},[103,827,315],{"class":266},[103,829,322],{"class":225},[103,831,833,835,837,840,842],{"class":105,"line":832},37,[103,834,775],{"class":221},[103,836,778],{"class":221},[103,838,839],{"class":225}," factory() ",[103,841,784],{"class":221},[103,843,844],{"class":225}," session:\n",[103,846,848,851,853,855,858,861,863,866],{"class":105,"line":847},38,[103,849,850],{"class":225},"        users ",[103,852,244],{"class":221},[103,854,703],{"class":221},[103,856,857],{"class":225}," get_active_users(session, ",[103,859,860],{"class":260},"tenant_id",[103,862,244],{"class":221},[103,864,865],{"class":266},"1",[103,867,322],{"class":225},[103,869,871,874,876,879,882,885,888,891,894],{"class":105,"line":870},39,[103,872,873],{"class":266},"        print",[103,875,459],{"class":225},[103,877,878],{"class":221},"f",[103,880,881],{"class":116},"\"Found ",[103,883,884],{"class":266},"{len",[103,886,887],{"class":225},"(users)",[103,889,890],{"class":266},"}",[103,892,893],{"class":116}," active users\"",[103,895,322],{"class":225},[103,897,899,902],{"class":105,"line":898},40,[103,900,901],{"class":221},"    await",[103,903,904],{"class":225}," engine.dispose()\n",[103,906,908],{"class":105,"line":907},41,[103,909,124],{"emptyLinePlaceholder":123},[103,911,913],{"class":105,"line":912},42,[103,914,915],{"class":109},"# Works with any of the URL schemes above — swap the string, keep the logic.\n",[103,917,919,922,925],{"class":105,"line":918},43,[103,920,921],{"class":225},"asyncio.run(main(",[103,923,924],{"class":116},"\"postgresql+asyncpg:\u002F\u002Fapp:secret@localhost\u002Fmydb\"",[103,926,680],{"class":225},[389,928,930],{"id":929},"backend-specific-type-behaviour","Backend-specific type behaviour",[14,932,933],{},"The most common source of cross-backend surprises involves types that SQLAlchemy maps differently per dialect:",[935,936,937,956],"table",{},[938,939,940],"thead",{},[941,942,943,947,950,953],"tr",{},[944,945,946],"th",{},"SQLAlchemy type",[944,948,949],{},"PostgreSQL",[944,951,952],{},"MySQL",[944,954,955],{},"SQLite",[957,958,959,983,1004,1029,1054],"tbody",{},[941,960,961,967,973,978],{},[962,963,964],"td",{},[24,965,966],{},"Boolean",[962,968,969,970],{},"native ",[24,971,972],{},"BOOLEAN",[962,974,975],{},[24,976,977],{},"TINYINT(1)",[962,979,980],{},[24,981,982],{},"INTEGER 0\u002F1",[941,984,985,990,995,1001],{},[962,986,987],{},[24,988,989],{},"DateTime",[962,991,992],{},[24,993,994],{},"TIMESTAMP WITH TIME ZONE",[962,996,997,1000],{},[24,998,999],{},"DATETIME"," (no TZ)",[962,1002,1003],{},"stored as text\u002Ffloat",[941,1005,1006,1011,1015,1024],{},[962,1007,1008],{},[24,1009,1010],{},"UUID",[962,1012,969,1013],{},[24,1014,1010],{},[962,1016,1017,1020,1021],{},[24,1018,1019],{},"CHAR(36)"," or ",[24,1022,1023],{},"BINARY(16)",[962,1025,1026,1028],{},[24,1027,1019],{}," text",[941,1030,1031,1036,1043,1048],{},[962,1032,1033],{},[24,1034,1035],{},"JSON",[962,1037,969,1038,380,1041],{},[24,1039,1040],{},"JSONB",[24,1042,1035],{},[962,1044,1045,1047],{},[24,1046,1035],{}," (MySQL 5.7.8+)",[962,1049,1050,1053],{},[24,1051,1052],{},"TEXT"," (serialised)",[941,1055,1056,1061,1064,1067],{},[962,1057,1058],{},[24,1059,1060],{},"ARRAY",[962,1062,1063],{},"native array types",[962,1065,1066],{},"not supported",[962,1068,1066],{},[14,1070,1071,1072,27,1074,1076,1077,1079,1080,1083,1084,1087,1088,1090,1091,1094],{},"When writing portable code that runs against multiple backends (Postgres in production, SQLite in tests), avoid ",[24,1073,1060],{},[24,1075,1040],{},", and Postgres-specific column types in your ORM models. Use ",[24,1078,1035],{}," from ",[24,1081,1082],{},"sqlalchemy"," (not ",[24,1085,1086],{},"sqlalchemy.dialects.postgresql",") and ",[24,1089,966],{}," rather than ",[24,1092,1093],{},"SmallInteger"," for boolean columns.",[36,1096,1098],{"id":1097},"state-management-session-boundaries","State Management & Session Boundaries",[14,1100,1101,1102,1105],{},"Session semantics are the same across all backends at the SQLAlchemy level — ",[24,1103,1104],{},"async with session.begin():"," always delimits a transaction — but the underlying connection lifecycle differs in ways that affect production configuration.",[389,1107,1109],{"id":1108},"connection-pooling-compatibility-by-backend","Connection pooling compatibility by backend",[14,1111,1112,1113,1116,1117,1120,1121,1124,1125,1020,1128,1130,1131,1134],{},"SQLAlchemy's ",[24,1114,1115],{},"QueuePool"," (the default) works with asyncpg, psycopg3, and aiomysql\u002Fasyncmy. For aiosqlite (SQLite), you ",[65,1118,1119],{},"must"," use ",[24,1122,1123],{},"StaticPool"," for in-memory databases (where a new connection equals a new blank database) and should use ",[24,1126,1127],{},"NullPool",[24,1129,1123],{}," for file-based databases in test contexts. SQLite's threading model predates async and its C library enforces per-thread connection ownership unless ",[24,1132,1133],{},"check_same_thread=False"," is explicitly set — aiosqlite handles this automatically, but the pool must be configured to hand back the same underlying connection.",[94,1136,1138],{"className":96,"code":1137,"language":98,"meta":99,"style":99},"from sqlalchemy.pool import StaticPool\n\n# In-memory SQLite: StaticPool + shared connection across sessions\ntest_engine = create_async_engine(\n    \"sqlite+aiosqlite:\u002F\u002F\",\n    connect_args={\"check_same_thread\": False},\n    poolclass=StaticPool,\n)\n\n# File-based SQLite in tests: NullPool to avoid thread-crossing\nfile_engine = create_async_engine(\n    \"sqlite+aiosqlite:\u002F\u002F\u002F.\u002Ftest.db\",\n    connect_args={\"check_same_thread\": False},\n    poolclass=StaticPool,\n)\n\n# Production Postgres: QueuePool (default) with explicit sizing\nprod_engine = create_async_engine(\n    \"postgresql+asyncpg:\u002F\u002Fapp:secret@db:5432\u002Fproduction\",\n    pool_size=20,\n    max_overflow=10,\n    pool_pre_ping=True,\n    pool_recycle=1800,\n)\n",[24,1139,1140,1152,1156,1161,1170,1177,1198,1208,1212,1216,1221,1230,1237,1253,1261,1265,1269,1274,1283,1289,1299,1309,1319,1329],{"__ignoreMap":99},[103,1141,1142,1144,1147,1149],{"class":105,"line":106},[103,1143,222],{"class":221},[103,1145,1146],{"class":225}," sqlalchemy.pool ",[103,1148,229],{"class":221},[103,1150,1151],{"class":225}," StaticPool\n",[103,1153,1154],{"class":105,"line":113},[103,1155,124],{"emptyLinePlaceholder":123},[103,1157,1158],{"class":105,"line":120},[103,1159,1160],{"class":109},"# In-memory SQLite: StaticPool + shared connection across sessions\n",[103,1162,1163,1166,1168],{"class":105,"line":127},[103,1164,1165],{"class":225},"test_engine ",[103,1167,244],{"class":221},[103,1169,247],{"class":225},[103,1171,1172,1175],{"class":105,"line":133},[103,1173,1174],{"class":116},"    \"sqlite+aiosqlite:\u002F\u002F\"",[103,1176,255],{"class":225},[103,1178,1179,1182,1184,1187,1190,1193,1195],{"class":105,"line":139},[103,1180,1181],{"class":260},"    connect_args",[103,1183,244],{"class":221},[103,1185,1186],{"class":225},"{",[103,1188,1189],{"class":116},"\"check_same_thread\"",[103,1191,1192],{"class":225},": ",[103,1194,315],{"class":266},[103,1196,1197],{"class":225},"},\n",[103,1199,1200,1203,1205],{"class":105,"line":144},[103,1201,1202],{"class":260},"    poolclass",[103,1204,244],{"class":221},[103,1206,1207],{"class":225},"StaticPool,\n",[103,1209,1210],{"class":105,"line":150},[103,1211,322],{"class":225},[103,1213,1214],{"class":105,"line":156},[103,1215,124],{"emptyLinePlaceholder":123},[103,1217,1218],{"class":105,"line":161},[103,1219,1220],{"class":109},"# File-based SQLite in tests: NullPool to avoid thread-crossing\n",[103,1222,1223,1226,1228],{"class":105,"line":167},[103,1224,1225],{"class":225},"file_engine ",[103,1227,244],{"class":221},[103,1229,247],{"class":225},[103,1231,1232,1235],{"class":105,"line":173},[103,1233,1234],{"class":116},"    \"sqlite+aiosqlite:\u002F\u002F\u002F.\u002Ftest.db\"",[103,1236,255],{"class":225},[103,1238,1239,1241,1243,1245,1247,1249,1251],{"class":105,"line":178},[103,1240,1181],{"class":260},[103,1242,244],{"class":221},[103,1244,1186],{"class":225},[103,1246,1189],{"class":116},[103,1248,1192],{"class":225},[103,1250,315],{"class":266},[103,1252,1197],{"class":225},[103,1254,1255,1257,1259],{"class":105,"line":184},[103,1256,1202],{"class":260},[103,1258,244],{"class":221},[103,1260,1207],{"class":225},[103,1262,1263],{"class":105,"line":190},[103,1264,322],{"class":225},[103,1266,1267],{"class":105,"line":195},[103,1268,124],{"emptyLinePlaceholder":123},[103,1270,1271],{"class":105,"line":201},[103,1272,1273],{"class":109},"# Production Postgres: QueuePool (default) with explicit sizing\n",[103,1275,1276,1279,1281],{"class":105,"line":613},[103,1277,1278],{"class":225},"prod_engine ",[103,1280,244],{"class":221},[103,1282,247],{"class":225},[103,1284,1285,1287],{"class":105,"line":618},[103,1286,252],{"class":116},[103,1288,255],{"class":225},[103,1290,1291,1293,1295,1297],{"class":105,"line":623},[103,1292,261],{"class":260},[103,1294,244],{"class":221},[103,1296,267],{"class":266},[103,1298,255],{"class":225},[103,1300,1301,1303,1305,1307],{"class":105,"line":643},[103,1302,274],{"class":260},[103,1304,244],{"class":221},[103,1306,279],{"class":266},[103,1308,255],{"class":225},[103,1310,1311,1313,1315,1317],{"class":105,"line":654},[103,1312,286],{"class":260},[103,1314,244],{"class":221},[103,1316,291],{"class":266},[103,1318,255],{"class":225},[103,1320,1321,1323,1325,1327],{"class":105,"line":660},[103,1322,298],{"class":260},[103,1324,244],{"class":221},[103,1326,303],{"class":266},[103,1328,255],{"class":225},[103,1330,1331],{"class":105,"line":672},[103,1332,322],{"class":225},[14,1334,1335,1336,1338,1339,1342,1343,1347],{},"The full walkthrough for in-memory SQLite in async test fixtures — including the ",[24,1337,1123],{}," gotcha and ",[24,1340,1341],{},"pytest-asyncio"," session scope — is covered in the guide on ",[18,1344,1346],{"href":1345},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fselecting-async-drivers-for-sqlite-mysql-and-postgres\u002Fusing-aiosqlite-for-async-tests-and-local-development\u002F","using aiosqlite for async tests and local development",".",[389,1349,1351],{"id":1350},"transaction-autocommit-differences","Transaction autocommit differences",[14,1353,1354,1355,1358,1359,1362,1363,1366,1367,1369],{},"MySQL's default isolation level is ",[24,1356,1357],{},"REPEATABLE READ",", while PostgreSQL defaults to ",[24,1360,1361],{},"READ COMMITTED",". SQLite has no concept of server-side isolation — isolation is entirely a per-connection concern. When targeting MySQL with aiomysql or asyncmy, be aware that ",[24,1364,1365],{},"SELECT"," inside a transaction takes a consistent snapshot at the transaction's start, which can surprise developers accustomed to Postgres ",[24,1368,1361],{}," semantics where a re-query mid-transaction sees committed data from other sessions.",[94,1371,1373],{"className":96,"code":1372,"language":98,"meta":99,"style":99},"# Explicitly set isolation level per engine — works for all async drivers\nengine_mysql = create_async_engine(\n    \"mysql+aiomysql:\u002F\u002Fapp:secret@mysql:3306\u002Fproduction\",\n    isolation_level=\"READ COMMITTED\",   # override MySQL default\n    pool_size=10,\n    max_overflow=5,\n    pool_pre_ping=True,\n    pool_recycle=3600,   # MySQL drops idle connections at 8h by default\n)\n",[24,1374,1375,1380,1389,1396,1412,1422,1433,1443,1457],{"__ignoreMap":99},[103,1376,1377],{"class":105,"line":106},[103,1378,1379],{"class":109},"# Explicitly set isolation level per engine — works for all async drivers\n",[103,1381,1382,1385,1387],{"class":105,"line":113},[103,1383,1384],{"class":225},"engine_mysql ",[103,1386,244],{"class":221},[103,1388,247],{"class":225},[103,1390,1391,1394],{"class":105,"line":120},[103,1392,1393],{"class":116},"    \"mysql+aiomysql:\u002F\u002Fapp:secret@mysql:3306\u002Fproduction\"",[103,1395,255],{"class":225},[103,1397,1398,1401,1403,1406,1409],{"class":105,"line":127},[103,1399,1400],{"class":260},"    isolation_level",[103,1402,244],{"class":221},[103,1404,1405],{"class":116},"\"READ COMMITTED\"",[103,1407,1408],{"class":225},",   ",[103,1410,1411],{"class":109},"# override MySQL default\n",[103,1413,1414,1416,1418,1420],{"class":105,"line":133},[103,1415,261],{"class":260},[103,1417,244],{"class":221},[103,1419,279],{"class":266},[103,1421,255],{"class":225},[103,1423,1424,1426,1428,1431],{"class":105,"line":139},[103,1425,274],{"class":260},[103,1427,244],{"class":221},[103,1429,1430],{"class":266},"5",[103,1432,255],{"class":225},[103,1434,1435,1437,1439,1441],{"class":105,"line":144},[103,1436,286],{"class":260},[103,1438,244],{"class":221},[103,1440,291],{"class":266},[103,1442,255],{"class":225},[103,1444,1445,1447,1449,1452,1454],{"class":105,"line":150},[103,1446,298],{"class":260},[103,1448,244],{"class":221},[103,1450,1451],{"class":266},"3600",[103,1453,1408],{"class":225},[103,1455,1456],{"class":109},"# MySQL drops idle connections at 8h by default\n",[103,1458,1459],{"class":105,"line":156},[103,1460,322],{"class":225},[36,1462,1464],{"id":1463},"advanced-driver-specific-patterns","Advanced Driver-Specific Patterns",[389,1466,1468],{"id":1467},"postgresql-asyncpg-vs-psycopg3-decision-boundary","PostgreSQL: asyncpg vs psycopg3 decision boundary",[14,1470,1471,1472,27,1475,27,1477,27,1479,1482,1483,1486,1487,1490,1491,1494,1495,1498],{},"For PostgreSQL, you have two strong driver options. asyncpg offers native-async execution with binary-protocol transfer for all supported types (zero string-serialisation overhead for ",[24,1473,1474],{},"INTEGER",[24,1476,1010],{},[24,1478,1040],{},[24,1480,1481],{},"TIMESTAMP","), making it the highest-throughput choice for OLTP workloads. psycopg3 wraps ",[24,1484,1485],{},"libpq"," with asyncio events, supports PostgreSQL's ",[24,1488,1489],{},"COPY"," protocol natively through ",[24,1492,1493],{},"cursor.copy()",", and integrates more cleanly with PgBouncer's transaction-pooling mode because its ",[24,1496,1497],{},"prepare_threshold=None"," setting disables server-side prepared statements without requiring the connection state surgery needed for asyncpg.",[14,1500,1501,1502,1505,1506,1347],{},"The detailed comparison — including prepared-statement cache sizes, PgBouncer ",[24,1503,1504],{},"statement_cache_size=0"," requirements, and COPY throughput benchmarks — is in the dedicated guide on ",[18,1507,1509],{"href":1508},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fchoosing-between-asyncpg-and-psycopg-async-drivers\u002F","choosing between asyncpg and psycopg3",[14,1511,1512],{},[65,1513,1514],{},"Rule of thumb:",[59,1516,1517,1520],{},[62,1518,1519],{},"asyncpg for latency-sensitive OLTP APIs, pure-Python deployments (no libpq system dependency), and read-heavy workloads",[62,1521,1522],{},"psycopg3 for bulk-ingest pipelines using native COPY, PgBouncer transaction-pool compatibility without configuration surgery, and teams migrating from psycopg2",[14,1524,1525],{},[65,1526,1527],{},"Installation:",[94,1529,1533],{"className":1530,"code":1531,"language":1532,"meta":99,"style":99},"language-bash shiki shiki-themes github-light github-dark","# asyncpg\npip install asyncpg sqlalchemy[asyncio]\n\n# psycopg3\npip install \"psycopg[async]\" sqlalchemy[asyncio]\n# Optional binary extension for better performance:\npip install \"psycopg[binary,async]\"\n","bash",[24,1534,1535,1540,1554,1558,1563,1574,1579],{"__ignoreMap":99},[103,1536,1537],{"class":105,"line":106},[103,1538,1539],{"class":109},"# asyncpg\n",[103,1541,1542,1545,1548,1551],{"class":105,"line":113},[103,1543,1544],{"class":455},"pip",[103,1546,1547],{"class":116}," install",[103,1549,1550],{"class":116}," asyncpg",[103,1552,1553],{"class":116}," sqlalchemy[asyncio]\n",[103,1555,1556],{"class":105,"line":120},[103,1557,124],{"emptyLinePlaceholder":123},[103,1559,1560],{"class":105,"line":127},[103,1561,1562],{"class":109},"# psycopg3\n",[103,1564,1565,1567,1569,1572],{"class":105,"line":133},[103,1566,1544],{"class":455},[103,1568,1547],{"class":116},[103,1570,1571],{"class":116}," \"psycopg[async]\"",[103,1573,1553],{"class":116},[103,1575,1576],{"class":105,"line":139},[103,1577,1578],{"class":109},"# Optional binary extension for better performance:\n",[103,1580,1581,1583,1585],{"class":105,"line":144},[103,1582,1544],{"class":455},[103,1584,1547],{"class":116},[103,1586,1587],{"class":116}," \"psycopg[binary,async]\"\n",[389,1589,1591],{"id":1590},"mysqlmariadb-aiomysql-vs-asyncmy","MySQL\u002FMariaDB: aiomysql vs asyncmy",[14,1593,1594,1595,46,1597,1599],{},"For MySQL and MariaDB, the two async driver options are ",[65,1596,76],{},[65,1598,79],{},". Both use the MySQL binary protocol over asyncio but have distinct maintenance and feature trajectories.",[14,1601,1602,1604,1605,1608,1609,1612],{},[65,1603,76],{}," is the older, more widely deployed option. It is an asyncio port of PyMySQL and has broad compatibility across MySQL 5.7, MySQL 8.x, and MariaDB. Most existing SQLAlchemy async MySQL documentation targets aiomysql. Its ",[24,1606,1607],{},"pool_recycle"," configuration is important because MySQL's ",[24,1610,1611],{},"wait_timeout"," (default 8 hours, often 600 seconds on managed cloud instances) silently drops idle connections.",[14,1614,1615,1617,1618,1621],{},[65,1616,79],{}," is a newer, pure-Python async MySQL driver with a cleaner internal architecture and faster development velocity as of 2024–2025. It supports MySQL 8.x features like ",[24,1619,1620],{},"caching_sha2_password"," authentication (which aiomysql had early-adoption issues with) and MariaDB 10.x column-level encoding changes.",[94,1623,1625],{"className":96,"code":1624,"language":98,"meta":99,"style":99},"# aiomysql — battle-tested, widest compatibility\nengine_aiomysql = create_async_engine(\n    \"mysql+aiomysql:\u002F\u002Fapp:secret@mysql:3306\u002Fproduction?charset=utf8mb4\",\n    pool_size=15,\n    max_overflow=5,\n    pool_pre_ping=True,\n    pool_recycle=3600,          # recycle before MySQL wait_timeout\n    connect_args={\n        \"connect_timeout\": 10,\n        \"autocommit\": False,    # SQLAlchemy manages transactions\n    },\n)\n\n# asyncmy — modern driver, prefer for MySQL 8.x \u002F MariaDB 10.6+\nengine_asyncmy = create_async_engine(\n    \"mysql+asyncmy:\u002F\u002Fapp:secret@mysql:3306\u002Fproduction?charset=utf8mb4\",\n    pool_size=15,\n    max_overflow=5,\n    pool_pre_ping=True,\n    pool_recycle=3600,\n)\n",[24,1626,1627,1632,1641,1648,1659,1669,1679,1693,1702,1713,1728,1733,1737,1741,1746,1755,1762,1772,1782,1792,1802],{"__ignoreMap":99},[103,1628,1629],{"class":105,"line":106},[103,1630,1631],{"class":109},"# aiomysql — battle-tested, widest compatibility\n",[103,1633,1634,1637,1639],{"class":105,"line":113},[103,1635,1636],{"class":225},"engine_aiomysql ",[103,1638,244],{"class":221},[103,1640,247],{"class":225},[103,1642,1643,1646],{"class":105,"line":120},[103,1644,1645],{"class":116},"    \"mysql+aiomysql:\u002F\u002Fapp:secret@mysql:3306\u002Fproduction?charset=utf8mb4\"",[103,1647,255],{"class":225},[103,1649,1650,1652,1654,1657],{"class":105,"line":127},[103,1651,261],{"class":260},[103,1653,244],{"class":221},[103,1655,1656],{"class":266},"15",[103,1658,255],{"class":225},[103,1660,1661,1663,1665,1667],{"class":105,"line":133},[103,1662,274],{"class":260},[103,1664,244],{"class":221},[103,1666,1430],{"class":266},[103,1668,255],{"class":225},[103,1670,1671,1673,1675,1677],{"class":105,"line":139},[103,1672,286],{"class":260},[103,1674,244],{"class":221},[103,1676,291],{"class":266},[103,1678,255],{"class":225},[103,1680,1681,1683,1685,1687,1690],{"class":105,"line":144},[103,1682,298],{"class":260},[103,1684,244],{"class":221},[103,1686,1451],{"class":266},[103,1688,1689],{"class":225},",          ",[103,1691,1692],{"class":109},"# recycle before MySQL wait_timeout\n",[103,1694,1695,1697,1699],{"class":105,"line":150},[103,1696,1181],{"class":260},[103,1698,244],{"class":221},[103,1700,1701],{"class":225},"{\n",[103,1703,1704,1707,1709,1711],{"class":105,"line":156},[103,1705,1706],{"class":116},"        \"connect_timeout\"",[103,1708,1192],{"class":225},[103,1710,279],{"class":266},[103,1712,255],{"class":225},[103,1714,1715,1718,1720,1722,1725],{"class":105,"line":161},[103,1716,1717],{"class":116},"        \"autocommit\"",[103,1719,1192],{"class":225},[103,1721,315],{"class":266},[103,1723,1724],{"class":225},",    ",[103,1726,1727],{"class":109},"# SQLAlchemy manages transactions\n",[103,1729,1730],{"class":105,"line":167},[103,1731,1732],{"class":225},"    },\n",[103,1734,1735],{"class":105,"line":173},[103,1736,322],{"class":225},[103,1738,1739],{"class":105,"line":178},[103,1740,124],{"emptyLinePlaceholder":123},[103,1742,1743],{"class":105,"line":184},[103,1744,1745],{"class":109},"# asyncmy — modern driver, prefer for MySQL 8.x \u002F MariaDB 10.6+\n",[103,1747,1748,1751,1753],{"class":105,"line":190},[103,1749,1750],{"class":225},"engine_asyncmy ",[103,1752,244],{"class":221},[103,1754,247],{"class":225},[103,1756,1757,1760],{"class":105,"line":195},[103,1758,1759],{"class":116},"    \"mysql+asyncmy:\u002F\u002Fapp:secret@mysql:3306\u002Fproduction?charset=utf8mb4\"",[103,1761,255],{"class":225},[103,1763,1764,1766,1768,1770],{"class":105,"line":201},[103,1765,261],{"class":260},[103,1767,244],{"class":221},[103,1769,1656],{"class":266},[103,1771,255],{"class":225},[103,1773,1774,1776,1778,1780],{"class":105,"line":613},[103,1775,274],{"class":260},[103,1777,244],{"class":221},[103,1779,1430],{"class":266},[103,1781,255],{"class":225},[103,1783,1784,1786,1788,1790],{"class":105,"line":618},[103,1785,286],{"class":260},[103,1787,244],{"class":221},[103,1789,291],{"class":266},[103,1791,255],{"class":225},[103,1793,1794,1796,1798,1800],{"class":105,"line":623},[103,1795,298],{"class":260},[103,1797,244],{"class":221},[103,1799,1451],{"class":266},[103,1801,255],{"class":225},[103,1803,1804],{"class":105,"line":643},[103,1805,322],{"class":225},[14,1807,1808,1811,1812,1815,1816,1819,1820,1823],{},[65,1809,1810],{},"MariaDB note:"," MariaDB 10.6+ introduced a strict ",[24,1813,1814],{},"latin1"," vs ",[24,1817,1818],{},"utf8mb4"," default collation change that breaks implicit character set assumptions. Always specify ",[24,1821,1822],{},"?charset=utf8mb4"," in your connection URL for both drivers.",[14,1825,1826],{},[65,1827,1527],{},[94,1829,1831],{"className":1530,"code":1830,"language":1532,"meta":99,"style":99},"# aiomysql\npip install aiomysql sqlalchemy[asyncio]\n\n# asyncmy\npip install asyncmy sqlalchemy[asyncio]\n",[24,1832,1833,1838,1849,1853,1858],{"__ignoreMap":99},[103,1834,1835],{"class":105,"line":106},[103,1836,1837],{"class":109},"# aiomysql\n",[103,1839,1840,1842,1844,1847],{"class":105,"line":113},[103,1841,1544],{"class":455},[103,1843,1547],{"class":116},[103,1845,1846],{"class":116}," aiomysql",[103,1848,1553],{"class":116},[103,1850,1851],{"class":105,"line":120},[103,1852,124],{"emptyLinePlaceholder":123},[103,1854,1855],{"class":105,"line":127},[103,1856,1857],{"class":109},"# asyncmy\n",[103,1859,1860,1862,1864,1867],{"class":105,"line":133},[103,1861,1544],{"class":455},[103,1863,1547],{"class":116},[103,1865,1866],{"class":116}," asyncmy",[103,1868,1553],{"class":116},[14,1870,1871,1874,1875,1877,1878,1880,1881,1884,1885,1887,1888,1891],{},[65,1872,1873],{},"Managed MySQL cloud considerations:"," AWS RDS MySQL and Google Cloud SQL both set ",[24,1876,1611],{}," to values well below MySQL's built-in 8-hour default. RDS MySQL defaults to 28800 seconds but many DBA teams lower it to 600–1800 seconds for cost reasons. Always verify the actual ",[24,1879,1611],{}," with ",[24,1882,1883],{},"SHOW VARIABLES LIKE 'wait_timeout';"," and set ",[24,1886,1607],{}," to at most half that value. On Cloud SQL, the proxy already handles reconnection to some extent, but SQLAlchemy's pool is unaware of the proxy's behaviour — ",[24,1889,1890],{},"pool_pre_ping=True"," remains essential.",[389,1893,1895],{"id":1894},"sqlite-aiosqlite-for-local-and-test-environments","SQLite: aiosqlite for local and test environments",[14,1897,1898,1899,1902],{},"aiosqlite wraps Python's built-in ",[24,1900,1901],{},"sqlite3"," module with asyncio, using a background thread per connection to avoid blocking the event loop (SQLite is not natively async — its C library is single-threaded). This means aiosqlite is fundamentally a synchronous executor wrapped in async sugar; it never achieves true non-blocking I\u002FO at the OS level. That is fine for local development and test workloads where the database is in-memory or on local disk and I\u002FO latency is measured in microseconds, not milliseconds.",[14,1904,1905],{},"For production workloads, SQLite with aiosqlite is appropriate only for:",[59,1907,1908,1911,1914,1917],{},[62,1909,1910],{},"Single-process embedded applications where the database file and the application process are co-located",[62,1912,1913],{},"Low-write, read-mostly workloads (SQLite's write lock is database-wide, not row-level)",[62,1915,1916],{},"Desktop or CLI tools where no network database is available",[62,1918,1919],{},"Edge deployments on Cloudflare Workers or similar runtimes that expose an SQLite-compatible API (e.g., D1 via a compatibility shim)",[14,1921,1922],{},[65,1923,1527],{},[94,1925,1927],{"className":1530,"code":1926,"language":1532,"meta":99,"style":99},"pip install aiosqlite sqlalchemy[asyncio]\n",[24,1928,1929],{"__ignoreMap":99},[103,1930,1931,1933,1935,1938],{"class":105,"line":106},[103,1932,1544],{"class":455},[103,1934,1547],{"class":116},[103,1936,1937],{"class":116}," aiosqlite",[103,1939,1553],{"class":116},[94,1941,1943],{"className":96,"code":1942,"language":98,"meta":99,"style":99},"import asyncio\nfrom sqlalchemy import select\nfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession\nfrom sqlalchemy.pool import StaticPool\n\n\nasync def demo_sqlite() -> None:\n    \"\"\"\n    In-memory SQLite with StaticPool: all sessions share one connection,\n    so schema created in one session is visible in subsequent sessions.\n    \"\"\"\n    engine = create_async_engine(\n        \"sqlite+aiosqlite:\u002F\u002F\",\n        connect_args={\"check_same_thread\": False},\n        poolclass=StaticPool,\n        echo=True,\n    )\n\n    async with engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n\n    factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)\n\n    async with factory() as session:\n        async with session.begin():\n            session.add(User(email=\"alice@example.com\", tenant_id=1))\n\n    async with factory() as session:\n        result = await session.execute(select(User).where(User.tenant_id == 1))\n        users = result.scalars().all()\n        print(f\"Retrieved {len(users)} users from in-memory SQLite\")\n\n    await engine.dispose()\n\n\nasyncio.run(demo_sqlite())\n",[24,1944,1945,1951,1961,1971,1981,1985,1989,2005,2010,2015,2020,2024,2032,2039,2056,2065,2076,2080,2084,2096,2102,2106,2128,2132,2144,2154,2177,2181,2193,2212,2220,2242,2246,2252,2256,2260],{"__ignoreMap":99},[103,1946,1947,1949],{"class":105,"line":106},[103,1948,229],{"class":221},[103,1950,404],{"class":225},[103,1952,1953,1955,1957,1959],{"class":105,"line":113},[103,1954,222],{"class":221},[103,1956,411],{"class":225},[103,1958,229],{"class":221},[103,1960,416],{"class":225},[103,1962,1963,1965,1967,1969],{"class":105,"line":120},[103,1964,222],{"class":221},[103,1966,226],{"class":225},[103,1968,229],{"class":221},[103,1970,232],{"class":225},[103,1972,1973,1975,1977,1979],{"class":105,"line":127},[103,1974,222],{"class":221},[103,1976,1146],{"class":225},[103,1978,229],{"class":221},[103,1980,1151],{"class":225},[103,1982,1983],{"class":105,"line":133},[103,1984,124],{"emptyLinePlaceholder":123},[103,1986,1987],{"class":105,"line":139},[103,1988,124],{"emptyLinePlaceholder":123},[103,1990,1991,1993,1995,1998,2001,2003],{"class":105,"line":144},[103,1992,626],{"class":221},[103,1994,629],{"class":221},[103,1996,1997],{"class":455}," demo_sqlite",[103,1999,2000],{"class":225},"() -> ",[103,2002,746],{"class":266},[103,2004,749],{"class":225},[103,2006,2007],{"class":105,"line":150},[103,2008,2009],{"class":116},"    \"\"\"\n",[103,2011,2012],{"class":105,"line":156},[103,2013,2014],{"class":116},"    In-memory SQLite with StaticPool: all sessions share one connection,\n",[103,2016,2017],{"class":105,"line":161},[103,2018,2019],{"class":116},"    so schema created in one session is visible in subsequent sessions.\n",[103,2021,2022],{"class":105,"line":167},[103,2023,2009],{"class":116},[103,2025,2026,2028,2030],{"class":105,"line":173},[103,2027,755],{"class":225},[103,2029,244],{"class":221},[103,2031,247],{"class":225},[103,2033,2034,2037],{"class":105,"line":178},[103,2035,2036],{"class":116},"        \"sqlite+aiosqlite:\u002F\u002F\"",[103,2038,255],{"class":225},[103,2040,2041,2044,2046,2048,2050,2052,2054],{"class":105,"line":184},[103,2042,2043],{"class":260},"        connect_args",[103,2045,244],{"class":221},[103,2047,1186],{"class":225},[103,2049,1189],{"class":116},[103,2051,1192],{"class":225},[103,2053,315],{"class":266},[103,2055,1197],{"class":225},[103,2057,2058,2061,2063],{"class":105,"line":190},[103,2059,2060],{"class":260},"        poolclass",[103,2062,244],{"class":221},[103,2064,1207],{"class":225},[103,2066,2067,2070,2072,2074],{"class":105,"line":195},[103,2068,2069],{"class":260},"        echo",[103,2071,244],{"class":221},[103,2073,291],{"class":266},[103,2075,255],{"class":225},[103,2077,2078],{"class":105,"line":201},[103,2079,692],{"class":225},[103,2081,2082],{"class":105,"line":613},[103,2083,124],{"emptyLinePlaceholder":123},[103,2085,2086,2088,2090,2092,2094],{"class":105,"line":618},[103,2087,775],{"class":221},[103,2089,778],{"class":221},[103,2091,781],{"class":225},[103,2093,784],{"class":221},[103,2095,787],{"class":225},[103,2097,2098,2100],{"class":105,"line":623},[103,2099,793],{"class":221},[103,2101,796],{"class":225},[103,2103,2104],{"class":105,"line":643},[103,2105,124],{"emptyLinePlaceholder":123},[103,2107,2108,2110,2112,2114,2116,2118,2120,2122,2124,2126],{"class":105,"line":654},[103,2109,807],{"class":225},[103,2111,244],{"class":221},[103,2113,812],{"class":225},[103,2115,815],{"class":260},[103,2117,244],{"class":221},[103,2119,820],{"class":225},[103,2121,823],{"class":260},[103,2123,244],{"class":221},[103,2125,315],{"class":266},[103,2127,322],{"class":225},[103,2129,2130],{"class":105,"line":660},[103,2131,124],{"emptyLinePlaceholder":123},[103,2133,2134,2136,2138,2140,2142],{"class":105,"line":672},[103,2135,775],{"class":221},[103,2137,778],{"class":221},[103,2139,839],{"class":225},[103,2141,784],{"class":221},[103,2143,844],{"class":225},[103,2145,2146,2149,2151],{"class":105,"line":683},[103,2147,2148],{"class":221},"        async",[103,2150,778],{"class":221},[103,2152,2153],{"class":225}," session.begin():\n",[103,2155,2156,2159,2162,2164,2167,2169,2171,2173,2175],{"class":105,"line":689},[103,2157,2158],{"class":225},"            session.add(User(",[103,2160,2161],{"class":260},"email",[103,2163,244],{"class":221},[103,2165,2166],{"class":116},"\"alice@example.com\"",[103,2168,27],{"class":225},[103,2170,860],{"class":260},[103,2172,244],{"class":221},[103,2174,865],{"class":266},[103,2176,680],{"class":225},[103,2178,2179],{"class":105,"line":695},[103,2180,124],{"emptyLinePlaceholder":123},[103,2182,2183,2185,2187,2189,2191],{"class":105,"line":709},[103,2184,775],{"class":221},[103,2186,778],{"class":221},[103,2188,839],{"class":225},[103,2190,784],{"class":221},[103,2192,844],{"class":225},[103,2194,2195,2198,2200,2202,2205,2207,2210],{"class":105,"line":718},[103,2196,2197],{"class":225},"        result ",[103,2199,244],{"class":221},[103,2201,703],{"class":221},[103,2203,2204],{"class":225}," session.execute(select(User).where(User.tenant_id ",[103,2206,666],{"class":221},[103,2208,2209],{"class":266}," 1",[103,2211,680],{"class":225},[103,2213,2214,2216,2218],{"class":105,"line":723},[103,2215,850],{"class":225},[103,2217,244],{"class":221},[103,2219,715],{"class":225},[103,2221,2222,2224,2226,2228,2231,2233,2235,2237,2240],{"class":105,"line":728},[103,2223,873],{"class":266},[103,2225,459],{"class":225},[103,2227,878],{"class":221},[103,2229,2230],{"class":116},"\"Retrieved ",[103,2232,884],{"class":266},[103,2234,887],{"class":225},[103,2236,890],{"class":266},[103,2238,2239],{"class":116}," users from in-memory SQLite\"",[103,2241,322],{"class":225},[103,2243,2244],{"class":105,"line":752},[103,2245,124],{"emptyLinePlaceholder":123},[103,2247,2248,2250],{"class":105,"line":772},[103,2249,901],{"class":221},[103,2251,904],{"class":225},[103,2253,2254],{"class":105,"line":790},[103,2255,124],{"emptyLinePlaceholder":123},[103,2257,2258],{"class":105,"line":799},[103,2259,124],{"emptyLinePlaceholder":123},[103,2261,2262],{"class":105,"line":804},[103,2263,2264],{"class":225},"asyncio.run(demo_sqlite())\n",[36,2266,2268],{"id":2267},"hybrid-architectures-migration-strategies","Hybrid Architectures & Migration Strategies",[389,2270,2272],{"id":2271},"running-postgres-in-production-with-sqlite-in-tests","Running Postgres in production with SQLite in tests",[14,2274,2275],{},"The most common multi-backend configuration is Postgres for production and SQLite for fast, dependency-free local tests. This is a legitimate and widely used architecture — but it requires discipline about which SQLAlchemy constructs you use.",[14,2277,2278],{},[65,2279,2280],{},"Safe for cross-backend use:",[59,2282,2283,2297,2308,2321],{},[62,2284,2285,27,2287,27,2290,27,2293,2296],{},[24,2286,379],{},[24,2288,2289],{},"insert()",[24,2291,2292],{},"update()",[24,2294,2295],{},"delete()"," with standard column types",[62,2298,2299,27,2302,27,2305],{},[24,2300,2301],{},"func.count()",[24,2303,2304],{},"func.sum()",[24,2306,2307],{},"func.coalesce()",[62,2309,2310,2311,27,2314,27,2317,2320],{},"ORM relationships (",[24,2312,2313],{},"selectinload",[24,2315,2316],{},"joinedload",[24,2318,2319],{},"subqueryload",")",[62,2322,2323,2325,2326,1083,2328,2320],{},[24,2324,1035],{}," type from ",[24,2327,1082],{},[24,2329,1086],{},[14,2331,2332],{},[65,2333,2334],{},"Postgres-only (avoid in portable models):",[59,2336,2337,2352,2360,2369],{},[62,2338,2339,27,2341,27,2344,27,2346,27,2349],{},[24,2340,1060],{},[24,2342,2343],{},"TSVECTOR",[24,2345,1040],{},[24,2347,2348],{},"HSTORE",[24,2350,2351],{},"INET",[62,2353,2354,27,2357],{},[24,2355,2356],{},"func.array_agg()",[24,2358,2359],{},"func.json_build_object()",[62,2361,2362,2365,2366,2320],{},[24,2363,2364],{},"ON CONFLICT DO UPDATE"," (",[24,2367,2368],{},"insert().on_conflict_do_update()",[62,2370,2371],{},"Window functions using Postgres-specific frame syntax",[94,2373,2375],{"className":96,"code":2374,"language":98,"meta":99,"style":99},"# conftest.py — pytest setup that works with both backends\nimport asyncio\nimport pytest\nimport pytest_asyncio\nfrom sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession\nfrom sqlalchemy.pool import StaticPool\n\n\nDATABASE_URL = \"sqlite+aiosqlite:\u002F\u002F\"   # override per environment\n\n\n@pytest_asyncio.fixture(scope=\"session\")\ndef event_loop():\n    loop = asyncio.new_event_loop()\n    yield loop\n    loop.close()\n\n\n@pytest_asyncio.fixture(scope=\"session\")\nasync def engine():\n    _engine = create_async_engine(\n        DATABASE_URL,\n        connect_args={\"check_same_thread\": False},\n        poolclass=StaticPool,\n    )\n    async with _engine.begin() as conn:\n        await conn.run_sync(Base.metadata.create_all)\n    yield _engine\n    async with _engine.begin() as conn:\n        await conn.run_sync(Base.metadata.drop_all)\n    await _engine.dispose()\n\n\n@pytest_asyncio.fixture\nasync def session(engine):\n    factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)\n    async with factory() as _session:\n        yield _session\n        await _session.rollback()   # undo changes between tests\n",[24,2376,2377,2382,2388,2395,2402,2412,2422,2426,2430,2444,2448,2452,2469,2480,2490,2498,2503,2507,2511,2525,2536,2545,2552,2568,2576,2580,2593,2599,2606,2618,2625,2632,2636,2640,2645,2657,2679,2692,2700],{"__ignoreMap":99},[103,2378,2379],{"class":105,"line":106},[103,2380,2381],{"class":109},"# conftest.py — pytest setup that works with both backends\n",[103,2383,2384,2386],{"class":105,"line":113},[103,2385,229],{"class":221},[103,2387,404],{"class":225},[103,2389,2390,2392],{"class":105,"line":120},[103,2391,229],{"class":221},[103,2393,2394],{"class":225}," pytest\n",[103,2396,2397,2399],{"class":105,"line":127},[103,2398,229],{"class":221},[103,2400,2401],{"class":225}," pytest_asyncio\n",[103,2403,2404,2406,2408,2410],{"class":105,"line":133},[103,2405,222],{"class":221},[103,2407,226],{"class":225},[103,2409,229],{"class":221},[103,2411,232],{"class":225},[103,2413,2414,2416,2418,2420],{"class":105,"line":139},[103,2415,222],{"class":221},[103,2417,1146],{"class":225},[103,2419,229],{"class":221},[103,2421,1151],{"class":225},[103,2423,2424],{"class":105,"line":144},[103,2425,124],{"emptyLinePlaceholder":123},[103,2427,2428],{"class":105,"line":150},[103,2429,124],{"emptyLinePlaceholder":123},[103,2431,2432,2435,2438,2441],{"class":105,"line":156},[103,2433,2434],{"class":266},"DATABASE_URL",[103,2436,2437],{"class":221}," =",[103,2439,2440],{"class":116}," \"sqlite+aiosqlite:\u002F\u002F\"",[103,2442,2443],{"class":109},"   # override per environment\n",[103,2445,2446],{"class":105,"line":161},[103,2447,124],{"emptyLinePlaceholder":123},[103,2449,2450],{"class":105,"line":167},[103,2451,124],{"emptyLinePlaceholder":123},[103,2453,2454,2457,2459,2462,2464,2467],{"class":105,"line":173},[103,2455,2456],{"class":455},"@pytest_asyncio.fixture",[103,2458,459],{"class":225},[103,2460,2461],{"class":260},"scope",[103,2463,244],{"class":221},[103,2465,2466],{"class":116},"\"session\"",[103,2468,322],{"class":225},[103,2470,2471,2474,2477],{"class":105,"line":178},[103,2472,2473],{"class":221},"def",[103,2475,2476],{"class":455}," event_loop",[103,2478,2479],{"class":225},"():\n",[103,2481,2482,2485,2487],{"class":105,"line":184},[103,2483,2484],{"class":225},"    loop ",[103,2486,244],{"class":221},[103,2488,2489],{"class":225}," asyncio.new_event_loop()\n",[103,2491,2492,2495],{"class":105,"line":190},[103,2493,2494],{"class":221},"    yield",[103,2496,2497],{"class":225}," loop\n",[103,2499,2500],{"class":105,"line":195},[103,2501,2502],{"class":225},"    loop.close()\n",[103,2504,2505],{"class":105,"line":201},[103,2506,124],{"emptyLinePlaceholder":123},[103,2508,2509],{"class":105,"line":613},[103,2510,124],{"emptyLinePlaceholder":123},[103,2512,2513,2515,2517,2519,2521,2523],{"class":105,"line":618},[103,2514,2456],{"class":455},[103,2516,459],{"class":225},[103,2518,2461],{"class":260},[103,2520,244],{"class":221},[103,2522,2466],{"class":116},[103,2524,322],{"class":225},[103,2526,2527,2529,2531,2534],{"class":105,"line":623},[103,2528,626],{"class":221},[103,2530,629],{"class":221},[103,2532,2533],{"class":455}," engine",[103,2535,2479],{"class":225},[103,2537,2538,2541,2543],{"class":105,"line":643},[103,2539,2540],{"class":225},"    _engine ",[103,2542,244],{"class":221},[103,2544,247],{"class":225},[103,2546,2547,2550],{"class":105,"line":654},[103,2548,2549],{"class":266},"        DATABASE_URL",[103,2551,255],{"class":225},[103,2553,2554,2556,2558,2560,2562,2564,2566],{"class":105,"line":660},[103,2555,2043],{"class":260},[103,2557,244],{"class":221},[103,2559,1186],{"class":225},[103,2561,1189],{"class":116},[103,2563,1192],{"class":225},[103,2565,315],{"class":266},[103,2567,1197],{"class":225},[103,2569,2570,2572,2574],{"class":105,"line":672},[103,2571,2060],{"class":260},[103,2573,244],{"class":221},[103,2575,1207],{"class":225},[103,2577,2578],{"class":105,"line":683},[103,2579,692],{"class":225},[103,2581,2582,2584,2586,2589,2591],{"class":105,"line":689},[103,2583,775],{"class":221},[103,2585,778],{"class":221},[103,2587,2588],{"class":225}," _engine.begin() ",[103,2590,784],{"class":221},[103,2592,787],{"class":225},[103,2594,2595,2597],{"class":105,"line":695},[103,2596,793],{"class":221},[103,2598,796],{"class":225},[103,2600,2601,2603],{"class":105,"line":709},[103,2602,2494],{"class":221},[103,2604,2605],{"class":225}," _engine\n",[103,2607,2608,2610,2612,2614,2616],{"class":105,"line":718},[103,2609,775],{"class":221},[103,2611,778],{"class":221},[103,2613,2588],{"class":225},[103,2615,784],{"class":221},[103,2617,787],{"class":225},[103,2619,2620,2622],{"class":105,"line":723},[103,2621,793],{"class":221},[103,2623,2624],{"class":225}," conn.run_sync(Base.metadata.drop_all)\n",[103,2626,2627,2629],{"class":105,"line":728},[103,2628,901],{"class":221},[103,2630,2631],{"class":225}," _engine.dispose()\n",[103,2633,2634],{"class":105,"line":752},[103,2635,124],{"emptyLinePlaceholder":123},[103,2637,2638],{"class":105,"line":772},[103,2639,124],{"emptyLinePlaceholder":123},[103,2641,2642],{"class":105,"line":790},[103,2643,2644],{"class":455},"@pytest_asyncio.fixture\n",[103,2646,2647,2649,2651,2654],{"class":105,"line":799},[103,2648,626],{"class":221},[103,2650,629],{"class":221},[103,2652,2653],{"class":455}," session",[103,2655,2656],{"class":225},"(engine):\n",[103,2658,2659,2661,2663,2665,2667,2669,2671,2673,2675,2677],{"class":105,"line":804},[103,2660,807],{"class":225},[103,2662,244],{"class":221},[103,2664,812],{"class":225},[103,2666,815],{"class":260},[103,2668,244],{"class":221},[103,2670,820],{"class":225},[103,2672,823],{"class":260},[103,2674,244],{"class":221},[103,2676,315],{"class":266},[103,2678,322],{"class":225},[103,2680,2681,2683,2685,2687,2689],{"class":105,"line":832},[103,2682,775],{"class":221},[103,2684,778],{"class":221},[103,2686,839],{"class":225},[103,2688,784],{"class":221},[103,2690,2691],{"class":225}," _session:\n",[103,2693,2694,2697],{"class":105,"line":847},[103,2695,2696],{"class":221},"        yield",[103,2698,2699],{"class":225}," _session\n",[103,2701,2702,2704,2707],{"class":105,"line":870},[103,2703,793],{"class":221},[103,2705,2706],{"class":225}," _session.rollback()   ",[103,2708,2709],{"class":109},"# undo changes between tests\n",[14,2711,2712,2713,2715,2716,1347],{},"For the full pytest-asyncio integration, scope pitfalls, and the ",[24,2714,1123],{}," shared-connection pattern in detail, see the guide on ",[18,2717,1346],{"href":1345},[389,2719,2721],{"id":2720},"environment-specific-engine-configuration-summary","Environment-specific engine configuration summary",[14,2723,2724],{},"A practical pattern for multi-environment projects is to centralise engine creation in one factory function that reads from environment variables and returns the correct engine for the current environment:",[94,2726,2728],{"className":96,"code":2727,"language":98,"meta":99,"style":99},"import os\nfrom sqlalchemy.ext.asyncio import create_async_engine, AsyncEngine\nfrom sqlalchemy.pool import StaticPool, NullPool\n\n\ndef build_engine() -> AsyncEngine:\n    \"\"\"\n    Return an appropriately configured AsyncEngine based on DATABASE_URL.\n    Handles Postgres (asyncpg\u002Fpsycopg3), MySQL (aiomysql\u002Fasyncmy),\n    and SQLite (aiosqlite) from a single factory.\n    \"\"\"\n    url = os.environ.get(\"DATABASE_URL\", \"sqlite+aiosqlite:\u002F\u002F\")\n\n    if url.startswith(\"sqlite+aiosqlite:\u002F\u002F\"):\n        # In-memory SQLite: StaticPool required; no pool sizing needed\n        is_memory = url == \"sqlite+aiosqlite:\u002F\u002F\"\n        return create_async_engine(\n            url,\n            connect_args={\"check_same_thread\": False},\n            poolclass=StaticPool if is_memory else StaticPool,\n            echo=os.environ.get(\"SQL_ECHO\", \"false\").lower() == \"true\",\n        )\n\n    if url.startswith(\"mysql+\"):\n        # MySQL \u002F MariaDB: explicit recycle to handle wait_timeout\n        return create_async_engine(\n            url,\n            pool_size=int(os.environ.get(\"DB_POOL_SIZE\", \"10\")),\n            max_overflow=int(os.environ.get(\"DB_MAX_OVERFLOW\", \"5\")),\n            pool_pre_ping=True,\n            pool_recycle=int(os.environ.get(\"DB_POOL_RECYCLE\", \"1800\")),\n            echo=os.environ.get(\"SQL_ECHO\", \"false\").lower() == \"true\",\n        )\n\n    # PostgreSQL (asyncpg or psycopg3) — QueuePool default\n    return create_async_engine(\n        url,\n        pool_size=int(os.environ.get(\"DB_POOL_SIZE\", \"20\")),\n        max_overflow=int(os.environ.get(\"DB_MAX_OVERFLOW\", \"10\")),\n        pool_pre_ping=True,\n        pool_recycle=int(os.environ.get(\"DB_POOL_RECYCLE\", \"1800\")),\n        echo=os.environ.get(\"SQL_ECHO\", \"false\").lower() == \"true\",\n    )\n\n\n# Usage: same import in dev, CI, and production.\n# Dev:        DATABASE_URL=sqlite+aiosqlite:\u002F\u002F\n# CI:         DATABASE_URL=postgresql+asyncpg:\u002F\u002Fci:ci@localhost\u002Fci_db\n# Production: DATABASE_URL=postgresql+asyncpg:\u002F\u002Fapp:secret@db:5432\u002Fprod\nengine = build_engine()\n",[24,2729,2730,2737,2748,2759,2763,2767,2777,2781,2786,2791,2796,2800,2820,2824,2836,2841,2856,2863,2868,2885,2907,2935,2940,2944,2955,2960,2966,2970,2993,3014,3025,3046,3068,3072,3076,3081,3087,3092,3112,3131,3142,3161,3183,3187,3192,3197,3203,3209,3215,3221],{"__ignoreMap":99},[103,2731,2732,2734],{"class":105,"line":106},[103,2733,229],{"class":221},[103,2735,2736],{"class":225}," os\n",[103,2738,2739,2741,2743,2745],{"class":105,"line":113},[103,2740,222],{"class":221},[103,2742,226],{"class":225},[103,2744,229],{"class":221},[103,2746,2747],{"class":225}," create_async_engine, AsyncEngine\n",[103,2749,2750,2752,2754,2756],{"class":105,"line":120},[103,2751,222],{"class":221},[103,2753,1146],{"class":225},[103,2755,229],{"class":221},[103,2757,2758],{"class":225}," StaticPool, NullPool\n",[103,2760,2761],{"class":105,"line":127},[103,2762,124],{"emptyLinePlaceholder":123},[103,2764,2765],{"class":105,"line":133},[103,2766,124],{"emptyLinePlaceholder":123},[103,2768,2769,2771,2774],{"class":105,"line":139},[103,2770,2473],{"class":221},[103,2772,2773],{"class":455}," build_engine",[103,2775,2776],{"class":225},"() -> AsyncEngine:\n",[103,2778,2779],{"class":105,"line":144},[103,2780,2009],{"class":116},[103,2782,2783],{"class":105,"line":150},[103,2784,2785],{"class":116},"    Return an appropriately configured AsyncEngine based on DATABASE_URL.\n",[103,2787,2788],{"class":105,"line":156},[103,2789,2790],{"class":116},"    Handles Postgres (asyncpg\u002Fpsycopg3), MySQL (aiomysql\u002Fasyncmy),\n",[103,2792,2793],{"class":105,"line":161},[103,2794,2795],{"class":116},"    and SQLite (aiosqlite) from a single factory.\n",[103,2797,2798],{"class":105,"line":167},[103,2799,2009],{"class":116},[103,2801,2802,2805,2807,2810,2813,2815,2818],{"class":105,"line":173},[103,2803,2804],{"class":225},"    url ",[103,2806,244],{"class":221},[103,2808,2809],{"class":225}," os.environ.get(",[103,2811,2812],{"class":116},"\"DATABASE_URL\"",[103,2814,27],{"class":225},[103,2816,2817],{"class":116},"\"sqlite+aiosqlite:\u002F\u002F\"",[103,2819,322],{"class":225},[103,2821,2822],{"class":105,"line":178},[103,2823,124],{"emptyLinePlaceholder":123},[103,2825,2826,2829,2832,2834],{"class":105,"line":184},[103,2827,2828],{"class":221},"    if",[103,2830,2831],{"class":225}," url.startswith(",[103,2833,2817],{"class":116},[103,2835,465],{"class":225},[103,2837,2838],{"class":105,"line":190},[103,2839,2840],{"class":109},"        # In-memory SQLite: StaticPool required; no pool sizing needed\n",[103,2842,2843,2846,2848,2851,2853],{"class":105,"line":195},[103,2844,2845],{"class":225},"        is_memory ",[103,2847,244],{"class":221},[103,2849,2850],{"class":225}," url ",[103,2852,666],{"class":221},[103,2854,2855],{"class":116}," \"sqlite+aiosqlite:\u002F\u002F\"\n",[103,2857,2858,2861],{"class":105,"line":201},[103,2859,2860],{"class":221},"        return",[103,2862,247],{"class":225},[103,2864,2865],{"class":105,"line":613},[103,2866,2867],{"class":225},"            url,\n",[103,2869,2870,2873,2875,2877,2879,2881,2883],{"class":105,"line":618},[103,2871,2872],{"class":260},"            connect_args",[103,2874,244],{"class":221},[103,2876,1186],{"class":225},[103,2878,1189],{"class":116},[103,2880,1192],{"class":225},[103,2882,315],{"class":266},[103,2884,1197],{"class":225},[103,2886,2887,2890,2892,2895,2898,2901,2904],{"class":105,"line":623},[103,2888,2889],{"class":260},"            poolclass",[103,2891,244],{"class":221},[103,2893,2894],{"class":225},"StaticPool ",[103,2896,2897],{"class":221},"if",[103,2899,2900],{"class":225}," is_memory ",[103,2902,2903],{"class":221},"else",[103,2905,2906],{"class":225}," StaticPool,\n",[103,2908,2909,2912,2914,2917,2920,2922,2925,2928,2930,2933],{"class":105,"line":643},[103,2910,2911],{"class":260},"            echo",[103,2913,244],{"class":221},[103,2915,2916],{"class":225},"os.environ.get(",[103,2918,2919],{"class":116},"\"SQL_ECHO\"",[103,2921,27],{"class":225},[103,2923,2924],{"class":116},"\"false\"",[103,2926,2927],{"class":225},").lower() ",[103,2929,666],{"class":221},[103,2931,2932],{"class":116}," \"true\"",[103,2934,255],{"class":225},[103,2936,2937],{"class":105,"line":654},[103,2938,2939],{"class":225},"        )\n",[103,2941,2942],{"class":105,"line":660},[103,2943,124],{"emptyLinePlaceholder":123},[103,2945,2946,2948,2950,2953],{"class":105,"line":672},[103,2947,2828],{"class":221},[103,2949,2831],{"class":225},[103,2951,2952],{"class":116},"\"mysql+\"",[103,2954,465],{"class":225},[103,2956,2957],{"class":105,"line":683},[103,2958,2959],{"class":109},"        # MySQL \u002F MariaDB: explicit recycle to handle wait_timeout\n",[103,2961,2962,2964],{"class":105,"line":689},[103,2963,2860],{"class":221},[103,2965,247],{"class":225},[103,2967,2968],{"class":105,"line":695},[103,2969,2867],{"class":225},[103,2971,2972,2975,2977,2979,2982,2985,2987,2990],{"class":105,"line":709},[103,2973,2974],{"class":260},"            pool_size",[103,2976,244],{"class":221},[103,2978,517],{"class":266},[103,2980,2981],{"class":225},"(os.environ.get(",[103,2983,2984],{"class":116},"\"DB_POOL_SIZE\"",[103,2986,27],{"class":225},[103,2988,2989],{"class":116},"\"10\"",[103,2991,2992],{"class":225},")),\n",[103,2994,2995,2998,3000,3002,3004,3007,3009,3012],{"class":105,"line":718},[103,2996,2997],{"class":260},"            max_overflow",[103,2999,244],{"class":221},[103,3001,517],{"class":266},[103,3003,2981],{"class":225},[103,3005,3006],{"class":116},"\"DB_MAX_OVERFLOW\"",[103,3008,27],{"class":225},[103,3010,3011],{"class":116},"\"5\"",[103,3013,2992],{"class":225},[103,3015,3016,3019,3021,3023],{"class":105,"line":723},[103,3017,3018],{"class":260},"            pool_pre_ping",[103,3020,244],{"class":221},[103,3022,291],{"class":266},[103,3024,255],{"class":225},[103,3026,3027,3030,3032,3034,3036,3039,3041,3044],{"class":105,"line":728},[103,3028,3029],{"class":260},"            pool_recycle",[103,3031,244],{"class":221},[103,3033,517],{"class":266},[103,3035,2981],{"class":225},[103,3037,3038],{"class":116},"\"DB_POOL_RECYCLE\"",[103,3040,27],{"class":225},[103,3042,3043],{"class":116},"\"1800\"",[103,3045,2992],{"class":225},[103,3047,3048,3050,3052,3054,3056,3058,3060,3062,3064,3066],{"class":105,"line":752},[103,3049,2911],{"class":260},[103,3051,244],{"class":221},[103,3053,2916],{"class":225},[103,3055,2919],{"class":116},[103,3057,27],{"class":225},[103,3059,2924],{"class":116},[103,3061,2927],{"class":225},[103,3063,666],{"class":221},[103,3065,2932],{"class":116},[103,3067,255],{"class":225},[103,3069,3070],{"class":105,"line":772},[103,3071,2939],{"class":225},[103,3073,3074],{"class":105,"line":790},[103,3075,124],{"emptyLinePlaceholder":123},[103,3077,3078],{"class":105,"line":799},[103,3079,3080],{"class":109},"    # PostgreSQL (asyncpg or psycopg3) — QueuePool default\n",[103,3082,3083,3085],{"class":105,"line":804},[103,3084,712],{"class":221},[103,3086,247],{"class":225},[103,3088,3089],{"class":105,"line":832},[103,3090,3091],{"class":225},"        url,\n",[103,3093,3094,3097,3099,3101,3103,3105,3107,3110],{"class":105,"line":847},[103,3095,3096],{"class":260},"        pool_size",[103,3098,244],{"class":221},[103,3100,517],{"class":266},[103,3102,2981],{"class":225},[103,3104,2984],{"class":116},[103,3106,27],{"class":225},[103,3108,3109],{"class":116},"\"20\"",[103,3111,2992],{"class":225},[103,3113,3114,3117,3119,3121,3123,3125,3127,3129],{"class":105,"line":870},[103,3115,3116],{"class":260},"        max_overflow",[103,3118,244],{"class":221},[103,3120,517],{"class":266},[103,3122,2981],{"class":225},[103,3124,3006],{"class":116},[103,3126,27],{"class":225},[103,3128,2989],{"class":116},[103,3130,2992],{"class":225},[103,3132,3133,3136,3138,3140],{"class":105,"line":898},[103,3134,3135],{"class":260},"        pool_pre_ping",[103,3137,244],{"class":221},[103,3139,291],{"class":266},[103,3141,255],{"class":225},[103,3143,3144,3147,3149,3151,3153,3155,3157,3159],{"class":105,"line":907},[103,3145,3146],{"class":260},"        pool_recycle",[103,3148,244],{"class":221},[103,3150,517],{"class":266},[103,3152,2981],{"class":225},[103,3154,3038],{"class":116},[103,3156,27],{"class":225},[103,3158,3043],{"class":116},[103,3160,2992],{"class":225},[103,3162,3163,3165,3167,3169,3171,3173,3175,3177,3179,3181],{"class":105,"line":912},[103,3164,2069],{"class":260},[103,3166,244],{"class":221},[103,3168,2916],{"class":225},[103,3170,2919],{"class":116},[103,3172,27],{"class":225},[103,3174,2924],{"class":116},[103,3176,2927],{"class":225},[103,3178,666],{"class":221},[103,3180,2932],{"class":116},[103,3182,255],{"class":225},[103,3184,3185],{"class":105,"line":918},[103,3186,692],{"class":225},[103,3188,3190],{"class":105,"line":3189},44,[103,3191,124],{"emptyLinePlaceholder":123},[103,3193,3195],{"class":105,"line":3194},45,[103,3196,124],{"emptyLinePlaceholder":123},[103,3198,3200],{"class":105,"line":3199},46,[103,3201,3202],{"class":109},"# Usage: same import in dev, CI, and production.\n",[103,3204,3206],{"class":105,"line":3205},47,[103,3207,3208],{"class":109},"# Dev:        DATABASE_URL=sqlite+aiosqlite:\u002F\u002F\n",[103,3210,3212],{"class":105,"line":3211},48,[103,3213,3214],{"class":109},"# CI:         DATABASE_URL=postgresql+asyncpg:\u002F\u002Fci:ci@localhost\u002Fci_db\n",[103,3216,3218],{"class":105,"line":3217},49,[103,3219,3220],{"class":109},"# Production: DATABASE_URL=postgresql+asyncpg:\u002F\u002Fapp:secret@db:5432\u002Fprod\n",[103,3222,3224,3226,3228],{"class":105,"line":3223},50,[103,3225,241],{"class":225},[103,3227,244],{"class":221},[103,3229,3230],{"class":225}," build_engine()\n",[14,3232,3233],{},"This pattern avoids scattering engine configuration across modules and makes the CI→production parity explicit in one function.",[389,3235,3237],{"id":3236},"migrating-from-synchronous-drivers-to-async","Migrating from synchronous drivers to async",[14,3239,3240,3241,27,3244,27,3247,3249,3250,27,3252,27,3254,3256],{},"The migration path from sync (",[24,3242,3243],{},"psycopg2",[24,3245,3246],{},"PyMySQL",[24,3248,1901],{},") to async (",[24,3251,67],{},[24,3253,76],{},[24,3255,85],{},") follows the same mechanical steps regardless of backend:",[3258,3259,3260,3263,3273,3287,3307,3320,3330],"ol",{},[62,3261,3262],{},"Install the async driver package alongside (or instead of) the sync driver.",[62,3264,3265,3266,3269,3270,3272],{},"Change ",[24,3267,3268],{},"create_engine()"," to ",[24,3271,45],{}," with the updated URL prefix.",[62,3274,3275,3276,380,3279,1880,3282,380,3284,1347],{},"Replace ",[24,3277,3278],{},"Session",[24,3280,3281],{},"sessionmaker",[24,3283,49],{},[24,3285,3286],{},"async_sessionmaker",[62,3288,3289,3290,3293,3294,27,3297,27,3300,27,3303,3306],{},"Add ",[24,3291,3292],{},"await"," to all ",[24,3295,3296],{},"session.execute()",[24,3298,3299],{},"session.commit()",[24,3301,3302],{},"session.rollback()",[24,3304,3305],{},"session.close()"," calls.",[62,3308,3309,3310,380,3313,3316,3317,3319],{},"Wrap ",[24,3311,3312],{},"session.add()",[24,3314,3315],{},"session.delete()"," in ",[24,3318,1104],{}," blocks.",[62,3321,3275,3322,3325,3326,3329],{},[24,3323,3324],{},"engine.dispose()"," (sync) with ",[24,3327,3328],{},"await engine.dispose()"," (async).",[62,3331,3332,3333,3269,3336,1347],{},"Move ",[24,3334,3335],{},"Base.metadata.create_all(engine)",[24,3337,3338],{},"async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all)",[14,3340,3341,3342,3345,3346,3349],{},"The most common omission in step 7 is forgetting ",[24,3343,3344],{},"run_sync"," — ",[24,3347,3348],{},"create_all"," is a synchronous function and must be wrapped:",[94,3351,3353],{"className":96,"code":3352,"language":98,"meta":99,"style":99},"# Sync (legacy)\nBase.metadata.create_all(engine)\n\n# Async (correct)\nasync with engine.begin() as conn:\n    await conn.run_sync(Base.metadata.create_all)\n",[24,3354,3355,3360,3365,3369,3374,3386],{"__ignoreMap":99},[103,3356,3357],{"class":105,"line":106},[103,3358,3359],{"class":109},"# Sync (legacy)\n",[103,3361,3362],{"class":105,"line":113},[103,3363,3364],{"class":225},"Base.metadata.create_all(engine)\n",[103,3366,3367],{"class":105,"line":120},[103,3368,124],{"emptyLinePlaceholder":123},[103,3370,3371],{"class":105,"line":127},[103,3372,3373],{"class":109},"# Async (correct)\n",[103,3375,3376,3378,3380,3382,3384],{"class":105,"line":133},[103,3377,626],{"class":221},[103,3379,778],{"class":221},[103,3381,781],{"class":225},[103,3383,784],{"class":221},[103,3385,787],{"class":225},[103,3387,3388,3390],{"class":105,"line":139},[103,3389,901],{"class":221},[103,3391,796],{"class":225},[14,3393,3394,3395,3399],{},"For dialect-specific quirks that emerge during migrations — particularly asyncpg prepared-statement conflicts with PgBouncer and MySQL charset encoding issues — the guide on ",[18,3396,3398],{"href":3397},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fdialect-specific-gotchas-and-driver-quirks\u002F","dialect-specific gotchas and driver quirks"," covers the exact error strings and fixes.",[36,3401,3403],{"id":3402},"production-pitfalls-anti-patterns","Production Pitfalls & Anti-Patterns",[59,3405,3406,3428,3456,3485,3498,3518],{},[62,3407,3408,3414,3415,3417,3418,1880,3421,3423,3424,3427],{},[65,3409,3410,3411,3413],{},"Using ",[24,3412,1115],{}," with an in-memory SQLite database."," Each pool checkout creates a new ",[24,3416,1901],{}," connection, which is a new blank database. Tables created in one connection do not exist in another. Always use ",[24,3419,3420],{},"poolclass=StaticPool",[24,3422,33],{}," so all sessions share one physical connection and thus one schema. Symptom: ",[24,3425,3426],{},"sqlalchemy.exc.OperationalError: (sqlite3.OperationalError) no such table: users"," on the second session.",[62,3429,3430,3436,3437,3439,3440,3442,3443,1020,3446,3449,3450,3453,3454,1347],{},[65,3431,3432,3433,3435],{},"Forgetting ",[24,3434,1607],{}," with MySQL\u002FMariaDB."," Managed MySQL instances (Cloud SQL, RDS, PlanetScale) often set ",[24,3438,1611],{}," between 600 and 3600 seconds. Without ",[24,3441,1607],{},", connections idle in SQLAlchemy's pool past that timeout are silently closed server-side. The next query raises ",[24,3444,3445],{},"asyncmy.errors.OperationalError: (2013, 'Lost connection to MySQL server during query')",[24,3447,3448],{},"aiomysql.OperationalError: (2006, \"MySQL server has gone away\")",". Fix: ",[24,3451,3452],{},"pool_recycle=1800"," (half the expected timeout) and ",[24,3455,1890],{},[62,3457,3458,3465,3466,3468,3469,3472,3473,1020,3475,3477,3478,3481,3482,3484],{},[65,3459,3460,3461,3464],{},"Omitting ",[24,3462,3463],{},"charset=utf8mb4"," in MySQL URLs."," MySQL's default charset is ",[24,3467,1814],{}," on older installations and ",[24,3470,3471],{},"utf8mb3"," on some cloud defaults. Storing emoji, CJK characters, or any 4-byte Unicode codepoints in a ",[24,3474,1814],{},[24,3476,3471],{}," column raises ",[24,3479,3480],{},"Incorrect string value",". Always append ",[24,3483,1822],{}," to your MySQL connection URL.",[62,3486,3487,3490,3491,3494,3495,3497],{},[65,3488,3489],{},"Assuming SQLite concurrency for write-heavy tests."," SQLite acquires a database-wide write lock per transaction. If multiple pytest workers (via ",[24,3492,3493],{},"pytest-xdist",") share one SQLite file, they will deadlock on concurrent writes. Use an in-memory database per worker (",[24,3496,33],{},") instead of sharing a file.",[62,3499,3500,3506,3507,3510,3511,3513,3514,3517],{},[65,3501,3502,3503,3505],{},"Using asyncpg without ",[24,3504,1890],{}," behind a cloud load balancer."," AWS ALB, Google Cloud LB, and Azure Front Door all drop idle TCP connections after 350–600 seconds. asyncpg raises ",[24,3508,3509],{},"asyncpg.exceptions.ConnectionDoesNotExistError"," on the first query to a stale connection. Set ",[24,3512,1890],{}," to issue a lightweight ",[24,3515,3516],{},"SELECT 1"," before handing a connection to the application.",[62,3519,3520,3523,3524,3527,3528,3531],{},[65,3521,3522],{},"Running asyncpg in a forked process without NullPool."," asyncpg connections are not fork-safe. A parent process that holds open asyncpg connections before ",[24,3525,3526],{},"os.fork()"," (common with Gunicorn workers, Celery prefork) leaves children with corrupted connection state. Use ",[24,3529,3530],{},"poolclass=NullPool"," and create a fresh engine inside each worker process, or defer engine creation to post-fork hooks.",[36,3533,3535],{"id":3534},"frequently-asked-questions","Frequently Asked Questions",[14,3537,3538,3541],{},[65,3539,3540],{},"Which async driver should I use for PostgreSQL in production?","\nasyncpg for the vast majority of OLTP workloads — it delivers the highest throughput through binary protocol and native-async scheduling. Switch to psycopg3 if you need first-class COPY streaming, are migrating an existing psycopg2 codebase, or need cleaner PgBouncer transaction-mode compatibility without disabling the prepared-statement cache entirely.",[14,3543,3544,3547,3548,27,3550,27,3552,27,3554,3556,3557,1079,3559,1090,3561,3563,3564,3566],{},[65,3545,3546],{},"Can I use the same SQLAlchemy models with both PostgreSQL and SQLite?","\nYes, with discipline. Avoid Postgres-specific types (",[24,3549,1060],{},[24,3551,1040],{},[24,3553,2343],{},[24,3555,2348],{},") in model definitions. Use ",[24,3558,1035],{},[24,3560,1082],{},[24,3562,1086],{},". Avoid ",[24,3565,2364],{}," and other Postgres-only DML extensions. Stick to these constraints and your models and queries will run identically against both backends.",[14,3568,3569,3576,3577,3579,3580,3582,3583,3586,3587,3590,3591,46,3593,1347],{},[65,3570,3571,3572,3575],{},"Why does ",[24,3573,3574],{},"create_async_engine(\"sqlite+aiosqlite:\u002F\u002F\")"," produce empty tables on every query?","\nBecause ",[24,3578,1115],{}," (the default) creates new connections on each checkout, and each new connection to ",[24,3581,33],{}," is a new, empty, in-memory database. The tables you created via ",[24,3584,3585],{},"metadata.create_all"," existed only on the connection used in ",[24,3588,3589],{},"engine.begin()",". Fix: pass ",[24,3592,3420],{},[24,3594,3595],{},"connect_args={\"check_same_thread\": False}",[14,3597,3598,3601],{},[65,3599,3600],{},"Is aiosqlite suitable for production workloads?","\nFor single-process, low-write, embedded use cases — yes. For any multi-process or concurrent-write production service — no. SQLite's write lock is database-wide; under concurrent writes, transactions queue behind one another, killing throughput. For production multi-client workloads, use PostgreSQL with asyncpg or MySQL with aiomysql\u002Fasyncmy.",[14,3603,3604,3607],{},[65,3605,3606],{},"What is the difference between aiomysql and asyncmy?","\naiomysql is an asyncio port of PyMySQL with broad compatibility and a long track record. asyncmy is a newer pure-Python driver with better MySQL 8.x authentication support (caching_sha2_password) and active development. For new projects targeting MySQL 8.x or MariaDB 10.6+, asyncmy is the cleaner choice. For projects with existing aiomysql configuration and MySQL 5.7, stick with aiomysql unless you have a specific reason to migrate.",[36,3609,3611],{"id":3610},"related","Related",[59,3613,3614,3619,3625,3631,3637],{},[62,3615,3616,3618],{},[18,3617,21],{"href":20}," — parent reference covering the full async engine and session lifecycle.",[62,3620,3621,3624],{},[18,3622,3623],{"href":1345},"Using aiosqlite for Async Tests and Local Development"," — StaticPool configuration, pytest-asyncio fixture patterns, and Postgres parity differences.",[62,3626,3627,3630],{},[18,3628,3629],{"href":1508},"Choosing Between asyncpg and psycopg3 Async Drivers"," — deep dive on the two PostgreSQL driver options: binary protocol, PgBouncer compatibility, and COPY throughput.",[62,3632,3633,3636],{},[18,3634,3635],{"href":3397},"Dialect-Specific Gotchas and Driver Quirks"," — exact error strings and fixes for PgBouncer prepared-statement conflicts, MySQL charset errors, and SQLite threading issues.",[62,3638,3639,3643],{},[18,3640,3642],{"href":3641},"\u002Fasync-engines-dialects-and-connection-pooling\u002Fconfiguring-async-engines-and-connection-pools\u002F","Configuring Async Engines and Connection Pools"," — pool_size, max_overflow, pool_recycle, and pre-ping production tuning across all backends.",[3645,3646,3649],"figure",{"className":3647},[3648],"diagram",[3650,3651,3656,3657,3656,3661,3656,3656,3665,3656,3656,3673,3656,3678,3656,3681,3689,3693,3697,3656,3656,3701,3656,3707,3656,3710,3656,3656,3713,3656,3719,3656,3723,3656,3729,3656,3731,3656,3733,3656,3736,3656,3740,3656,3656,3744,3656,3748,3656,3751,3656,3755,3656,3757,3656,3760,3656,3763,3656,3766,3656,3656,3769,3656,3772,3656,3776,3656,3780,3656,3782,3656,3784,3656,3787,3656,3790,3656,3656,3794,3656,3797,3656,3801,3656,3805,3656,3807,3656,3810,3656,3813,3656,3816,3656,3656,3819,3656,3822,3656,3826,3656,3830,3656,3832,3656,3835,3656,3838,3656,3841,3656,3656,3844,3656,3847,3656,3851,3656,3855,3656,3860,3656,3862,3656,3866,3656,3869],"svg",{"viewBox":3652,"role":3653,"ariaLabel":3654,"xmlns":3655},"0 0 765 400","img","Async driver selection matrix by database backend, environment, and use case","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[3658,3659,3660],"title",{},"Async Driver Selection Matrix",[3662,3663,3664],"desc",{},"A grid showing which async driver to choose for each combination of database backend (PostgreSQL, MySQL\u002FMariaDB, SQLite), environment (production, testing), and primary workload type.",[3666,3667],"rect",{"x":3668,"y":3668,"width":3669,"height":3670,"fill":3671,"rx":3672},"0","765","400","#f1f9f6","8",[3666,3674],{"x":3668,"y":3668,"width":3675,"height":3676,"fill":3677,"rx":3672},"720","48","#0f766e",[3666,3679],{"x":3668,"y":3680,"width":3675,"height":279,"fill":3677},"38",[3682,3683,3688],"text",{"x":3684,"y":3685,"fill":3686,"style":3687},"100","30","#ffffff","text-anchor:middle;font-size:13px;font-weight:bold","\nBackend\n",[3682,3690,3692],{"x":3691,"y":3685,"fill":3686,"style":3687},"270","\nProduction Driver\n",[3682,3694,3696],{"x":3695,"y":3685,"fill":3686,"style":3687},"440","\nTest \u002F Local Driver\n",[3682,3698,3700],{"x":3699,"y":3685,"fill":3686,"style":3687},"610","\nPool Class\n",[105,3702],{"x1":3703,"y1":3676,"x2":3703,"y2":3704,"stroke":3705,"style":3706},"190","395","rgba(15,118,110,0.28)","stroke-width:1",[105,3708],{"x1":3709,"y1":3676,"x2":3709,"y2":3704,"stroke":3705,"style":3706},"360",[105,3711],{"x1":3712,"y1":3676,"x2":3712,"y2":3704,"stroke":3705,"style":3706},"540",[3666,3714],{"x":279,"y":3715,"width":3716,"height":3717,"fill":3686,"rx":3718},"53","700","52","4",[3682,3720,949],{"x":3684,"y":3721,"fill":3722,"style":3687},"72","#113f39",[3682,3724,3728],{"x":3684,"y":3725,"fill":3726,"style":3727},"90","#3f4f4b","text-anchor:middle;font-size:12px","(OLTP \u002F API)",[3682,3730,67],{"x":3691,"y":3721,"fill":3677,"style":3687},[3682,3732,26],{"x":3691,"y":3725,"fill":3726,"style":3727},[3682,3734,3735],{"x":3695,"y":3721,"fill":3726,"style":3727},"asyncpg or aiosqlite",[3682,3737,3739],{"x":3695,"y":3725,"fill":3726,"style":3738},"text-anchor:middle;font-size:11px","fast tests use SQLite",[3682,3741,3743],{"x":3699,"y":3742,"fill":3726,"style":3727},"81","QueuePool (default)",[3666,3745],{"x":279,"y":3746,"width":3716,"height":3717,"fill":3747,"rx":3718},"110","#e8f5f2",[3682,3749,949],{"x":3684,"y":3750,"fill":3722,"style":3687},"129",[3682,3752,3754],{"x":3684,"y":3753,"fill":3726,"style":3727},"147","(bulk \u002F COPY)",[3682,3756,70],{"x":3691,"y":3750,"fill":3677,"style":3687},[3682,3758,3759],{"x":3691,"y":3753,"fill":3726,"style":3727},"postgresql+psycopg:\u002F\u002F",[3682,3761,3762],{"x":3695,"y":3750,"fill":3726,"style":3727},"psycopg3 or aiosqlite",[3682,3764,3765],{"x":3695,"y":3753,"fill":3726,"style":3738},"native COPY support",[3682,3767,3743],{"x":3699,"y":3768,"fill":3726,"style":3727},"138",[3666,3770],{"x":279,"y":3771,"width":3716,"height":3717,"fill":3686,"rx":3718},"167",[3682,3773,3775],{"x":3684,"y":3774,"fill":3722,"style":3687},"186","MySQL 5.7 \u002F 8.x",[3682,3777,3779],{"x":3684,"y":3778,"fill":3726,"style":3727},"204","(wide compat)",[3682,3781,76],{"x":3691,"y":3774,"fill":3677,"style":3687},[3682,3783,30],{"x":3691,"y":3778,"fill":3726,"style":3727},[3682,3785,3786],{"x":3695,"y":3774,"fill":3726,"style":3727},"aiomysql or aiosqlite",[3682,3788,3789],{"x":3695,"y":3778,"fill":3726,"style":3738},"recycle \u003C wait_timeout",[3682,3791,3793],{"x":3699,"y":3792,"fill":3726,"style":3727},"195","QueuePool + recycle",[3666,3795],{"x":279,"y":3796,"width":3716,"height":3717,"fill":3747,"rx":3718},"224",[3682,3798,3800],{"x":3684,"y":3799,"fill":3722,"style":3687},"243","MySQL 8 \u002F",[3682,3802,3804],{"x":3684,"y":3803,"fill":3726,"style":3727},"261","MariaDB 10.6+",[3682,3806,79],{"x":3691,"y":3799,"fill":3677,"style":3687},[3682,3808,3809],{"x":3691,"y":3803,"fill":3726,"style":3727},"mysql+asyncmy:\u002F\u002F",[3682,3811,3812],{"x":3695,"y":3799,"fill":3726,"style":3727},"asyncmy or aiosqlite",[3682,3814,3815],{"x":3695,"y":3803,"fill":3726,"style":3738},"sha2_password auth",[3682,3817,3793],{"x":3699,"y":3818,"fill":3726,"style":3727},"252",[3666,3820],{"x":279,"y":3821,"width":3716,"height":3717,"fill":3686,"rx":3718},"281",[3682,3823,3825],{"x":3684,"y":3824,"fill":3722,"style":3687},"300","SQLite (file)",[3682,3827,3829],{"x":3684,"y":3828,"fill":3726,"style":3727},"318","embedded \u002F CLI",[3682,3831,85],{"x":3691,"y":3824,"fill":3677,"style":3687},[3682,3833,3834],{"x":3691,"y":3828,"fill":3726,"style":3727},"sqlite+aiosqlite:\u002F\u002F\u002F…",[3682,3836,3837],{"x":3695,"y":3824,"fill":3726,"style":3727},"aiosqlite (same)",[3682,3839,3840],{"x":3695,"y":3828,"fill":3726,"style":3738},"single-process only",[3682,3842,1123],{"x":3699,"y":3843,"fill":3726,"style":3727},"309",[3666,3845],{"x":279,"y":3846,"width":3716,"height":3717,"fill":3747,"rx":3718},"338",[3682,3848,3850],{"x":3684,"y":3849,"fill":3722,"style":3687},"357","SQLite (memory)",[3682,3852,3854],{"x":3684,"y":3853,"fill":3726,"style":3727},"375","tests \u002F CI only",[3682,3856,3859],{"x":3691,"y":3849,"fill":3857,"style":3858},"#c0392b","text-anchor:middle;font-size:13px","Not for production",[3682,3861,33],{"x":3691,"y":3853,"fill":3726,"style":3727},[3682,3863,3865],{"x":3695,"y":3849,"fill":3677,"style":3864},"text-anchor:middle;font-size:12px;font-weight:bold","aiosqlite (preferred)",[3682,3867,3868],{"x":3695,"y":3853,"fill":3726,"style":3738},"fastest test setup",[3682,3870,3872],{"x":3699,"y":3871,"fill":3857,"style":3727},"366","StaticPool (required)",[3874,3875,3876],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}",{"title":99,"searchDepth":113,"depth":113,"links":3878},[3879,3880,3884,3888,3893,3898,3899,3900],{"id":38,"depth":113,"text":39},{"id":372,"depth":113,"text":373,"children":3881},[3882,3883],{"id":391,"depth":120,"text":392},{"id":929,"depth":120,"text":930},{"id":1097,"depth":113,"text":1098,"children":3885},[3886,3887],{"id":1108,"depth":120,"text":1109},{"id":1350,"depth":120,"text":1351},{"id":1463,"depth":113,"text":1464,"children":3889},[3890,3891,3892],{"id":1467,"depth":120,"text":1468},{"id":1590,"depth":120,"text":1591},{"id":1894,"depth":120,"text":1895},{"id":2267,"depth":113,"text":2268,"children":3894},[3895,3896,3897],{"id":2271,"depth":120,"text":2272},{"id":2720,"depth":120,"text":2721},{"id":3236,"depth":120,"text":3237},{"id":3402,"depth":113,"text":3403},{"id":3534,"depth":113,"text":3535},{"id":3610,"depth":113,"text":3611},"Choosing the right async database driver is one of the first architecture decisions you make when building an async Python service, and the answer is backend-specific. This guide — part of the Async Engines, Dialects, and Connection Pooling reference — maps each major backend to its mature async driver options, explains the URL scheme differences (postgresql+asyncpg:\u002F\u002F, mysql+aiomysql:\u002F\u002F, sqlite+aiosqlite:\u002F\u002F), and gives you the practical knowledge to pick the right driver for production, staging, and test environments.","md",{"date":3904},"2026-06-18","\u002Fasync-engines-dialects-and-connection-pooling\u002Fselecting-async-drivers-for-sqlite-mysql-and-postgres",{"title":5,"description":3901},"async-engines-dialects-and-connection-pooling\u002Fselecting-async-drivers-for-sqlite-mysql-and-postgres\u002Findex","h0uxZnhZI5h3B_lcng8VKcVH-D4-GIwD3kIfFVabk98",1781810028984]