今天的好奇心解决主题是 Django的request.session.get()会导致性能下降吗? 🎯

在进行Django开发时,通常会多次调用 request.session.get('key_name')。但是,突然有这样的想法。

"如果会话最终存储在数据库中,那么每次调用 request.session.get() 时都发生数据库查询吗?"

"或者,Django是否进行了聪明的处理,以避免额外的查询?"

为了解决这个疑问,我深入研究了Django的 SessionMiddleware源码,并通过实验进行了验证。 🚀

1️⃣ Django如何创建request.session?

我们来看看Django在创建request对象时何时加载会话。

Django session management process

我们通常在 视图函数 中这样获取会话数据。

session_username = request.session.get('username')

但是,如果每次使用 request.session.get('username') 时Django都要 发送数据库查询,那么将对性能造成很大影响吧?

🔍 让我们看看Django的内部!

实际上,Django通过 SessionMiddleware 中间件,在请求到达时 预先加载会话数据。这个过程只发生一次数据库查询,之后 会话数据保持在request对象中

查看Django的 SessionMiddleware 代码,可以看到以下处理。

class SessionMiddleware(MiddlewareMixin):
    def process_request(self, request):
        session_key = request.COOKIES.get(settings.SESSION_COOKIE_NAME)
        request.session = self.SessionStore(session_key)

📌 这里的重要点!

  • ✅ 从 request.COOKIES 中获取会话密钥(session_key)。
  • ✅ 调用 self.SessionStore(session_key),会话数据 仅从数据库中获取一次并保存在request.session中
  • ✅ 也就是说,即使在视图函数中多次调用 request.session.get(),也不会发生额外的数据库查询! 🎯

2️⃣ request.session.get()会产生数据库查询吗?

现在回到核心问题。

"在视图中调用 request.session.get('username') 会导致数据库查询吗?"

🚀 简单地说,调用 request.session.get() 并不会产生额外的数据库查询。

原因很简单。
✔ Django在 请求到达时加载会话数据一次,然后存储到 request.session中。
✔ 而后调用 request.session.get() 只是 获取已加载到内存中的数据,因此不需要额外的数据库查询。

实际上,从数据库中查询会话的时刻是在请求开始时仅一次。 这时执行的SQL查询如下:

SELECT session_data FROM django_session WHERE session_key = 'xyz123' LIMIT 1;

 换句话说,✅ 在视图中多次调用 request.session.get() 时,Django不会执行额外的数据库查询!
      ✅ 从数据库中获取数据仅在请求第一次发生时执行一次!

3️⃣ 在模板中使用会话数据时也会产生查询吗?

"在视图中查询会话数据后传递给模板,模板中使用时也会产生额外的数据库查询吗?"

例如,假设在视图中这样传递会话数据。

def my_view(request):
    session_username = request.session.get('username')  # 发生数据库查询吗? ❌
    return render(request, 'my_template.html', {'session_username': session_username})

然后在模板中使用如下。

<p>登录为: {{ session_username }}</p>

在这种情况下也不会产生额外的数据库查询!

session_username已经从request.session获取的值,因此Django在传递给模板时不需要额外的数据库查询。

✔ Django像 request.session 进行 内存缓存管理,因此在模板中也可以安全使用。


4️⃣ 修改会话数据时会存储到数据库吗?

"那如果像这样修改数据 request.session['username'] = 'new_value' 呢?"

🚀 在这种情况下,与数据库的更改被存储,并且会产生额外的数据库查询!

如果像下面这样修改会话数据,

request.session['username'] = 'new_value'

Django会在发送响应时将 修改后的会话数据存储到数据库。这时执行的SQL查询如下:

UPDATE django_session SET session_data = 'new_value' WHERE session_key = 'xyz123';

读取会话数据不会产生数据库查询,但修改会话时Django会为存储数据执行数据库查询。


5️⃣ 根据会话存储方式(后端)而异的差异

Django允许更改会话存储方式(后端)。需要注意的是,根据后端 可能会影响查询的发生。

会话后端 说明 是否发生数据库查询
django.contrib.sessions.backends.db 基于数据库的默认会话 ✅ 请求时发生一次
django.contrib.sessions.backends.cache 基于缓存的会话(Redis,Memcached) ❌ 无数据库查询
django.contrib.sessions.backends.cached_db 缓存+数据库会话(无缓存时查询数据库) 🚀 如果缓存中没有则发生
django.contrib.sessions.backends.signed_cookies 基于 cookie 的会话 ❌ 无数据库查询

🚀 也就是说,通过使用 cachesigned_cookies 后端,可以在不进行数据库查询的情况下获取会话。 ✔ 在性能关键的情况下,考虑使用 CACHED_DB_SESSION 也是一个好方法。


🎯 结论

  • ✅ Django通过 在请求之初通过 SessionMiddleware 预加载会话数据并将其保存在 request.session 对象中。
  • ✅ 因此,在视图中调用 request.session.get('username') 不会产生额外的数据库查询。
  • 在模板中使用会话数据时也不会产生额外的数据库查询。
  • ✅ 但是,如果修改了会话值,Django在发送响应时会将修改后的数据存储到数据库。
  • ✅ 根据Django的会话后端设置,可能会影响查询发生的情况,因此使用基于缓存的(cachecached_db)可以减少数据库负担。

🔥 下期预告: request 对象何时消失?

那么 request 对象何时会消失呢?

  • 它是在请求结束时仍然占用内存吗?

  • 系统会自动清理,还是我们需要手动整理呢?

🚀 在下一期,我们将探讨Django的 request 对象的生命周期和内存管理! 敬请期待! 😃