Skip to content

Commit 40adbb0

Browse files
Merge pull request dimitri-yatsenko#62 from dimitri-yatsenko/claude/improve-transactions-section-GJsFb
Clarify when explicit transactions are needed
2 parents c788c45 + fa80491 commit 40adbb0

File tree

1 file changed

+2
-99
lines changed

1 file changed

+2
-99
lines changed

book/40-operations/040-transactions.ipynb

Lines changed: 2 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -453,110 +453,13 @@
453453
"cell_type": "markdown",
454454
"id": "cell-best-practices",
455455
"metadata": {},
456-
"source": [
457-
"## Best Practices\n",
458-
"\n",
459-
"### Keep Transactions Short\n",
460-
"\n",
461-
"Long-running transactions can:\n",
462-
"- Lock database resources, blocking other users\n",
463-
"- Increase the risk of deadlocks\n",
464-
"- Consume server memory for maintaining transaction state\n",
465-
"\n",
466-
"```python\n",
467-
"# Good: Short, focused transaction\n",
468-
"with conn.transaction:\n",
469-
" Table.insert(prepared_data)\n",
470-
"\n",
471-
"# Bad: Long transaction with computation inside\n",
472-
"with conn.transaction:\n",
473-
" data = fetch_from_external_api() # Could take minutes!\n",
474-
" processed = heavy_computation(data) # Even longer!\n",
475-
" Table.insert(processed)\n",
476-
"```\n",
477-
"\n",
478-
"**Better approach:** Do computation outside the transaction, then insert:\n",
479-
"\n",
480-
"```python\n",
481-
"# Prepare data outside transaction\n",
482-
"data = fetch_from_external_api()\n",
483-
"processed = heavy_computation(data)\n",
484-
"\n",
485-
"# Quick transaction for database operations only\n",
486-
"with conn.transaction:\n",
487-
" Table.insert(processed)\n",
488-
"```\n",
489-
"\n",
490-
"### Avoid User Interaction Inside Transactions\n",
491-
"\n",
492-
"Never wait for user input while holding a transaction open:\n",
493-
"\n",
494-
"```python\n",
495-
"# Bad: User could take coffee break!\n",
496-
"with conn.transaction:\n",
497-
" Table.insert(data)\n",
498-
" confirm = input(\"Confirm? (y/n)\") # DON'T DO THIS\n",
499-
" if confirm != 'y':\n",
500-
" raise Exception(\"Cancelled\")\n",
501-
"```\n",
502-
"\n",
503-
"### Handle Deadlocks Gracefully\n",
504-
"\n",
505-
"When multiple transactions compete for the same resources, deadlocks can occur.\n",
506-
"MySQL automatically detects and resolves deadlocks by rolling back one transaction.\n",
507-
"\n",
508-
"```python\n",
509-
"import time\n",
510-
"\n",
511-
"def insert_with_retry(data, max_retries=3):\n",
512-
" \"\"\"Insert data with deadlock retry logic.\"\"\"\n",
513-
" for attempt in range(max_retries):\n",
514-
" try:\n",
515-
" with dj.conn().transaction:\n",
516-
" Table.insert(data)\n",
517-
" return # Success\n",
518-
" except Exception as e:\n",
519-
" if \"Deadlock\" in str(e) and attempt < max_retries - 1:\n",
520-
" time.sleep(0.1 * (2 ** attempt)) # Exponential backoff\n",
521-
" continue\n",
522-
" raise\n",
523-
"```\n",
524-
"\n",
525-
"### Use Transactions for Related Inserts\n",
526-
"\n",
527-
"When inserting data that spans multiple tables and must be consistent:\n",
528-
"\n",
529-
"```python\n",
530-
"# Insert related data atomically\n",
531-
"with dj.conn().transaction:\n",
532-
" Subject.insert1(subject_data)\n",
533-
" Session.insert(session_data) # References Subject\n",
534-
" Recording.insert(recording_data) # References Session\n",
535-
"```"
536-
]
456+
"source": "## Best Practices\n\n### When to Use Explicit Transactions\n\nA single `insert()` or `update1()` call is already atomic—it either fully succeeds or fully fails.\nExplicit transactions using `conn.transaction` are only needed when **multiple operations** must succeed or fail together:\n\n```python\n# No need for explicit transaction - single insert is already atomic\nTable.insert(data)\n\n# Explicit transaction needed - multiple operations must be atomic\nwith dj.conn().transaction:\n Table1.insert(data1)\n Table2.insert(data2)\n Table3.update1(data3)\n```\n\n### Transactions and Long Computations\n\nWhen computations take a long time, there's a tension between keeping transactions short (to avoid blocking) and maintaining data integrity (ensuring inputs haven't changed).\n\nDataJoint's `make()` method handles this through a three-part pattern that refetches and verifies input data inside the transaction.\nSee {doc}`055-make` for details on implementing safe computations.\n\n### Avoid User Interaction Inside Transactions\n\nNever wait for user input while holding a transaction open:\n\n```python\n# Bad: User could take coffee break!\nwith conn.transaction:\n Table.insert(data)\n confirm = input(\"Confirm? (y/n)\") # DON'T DO THIS\n if confirm != 'y':\n raise Exception(\"Cancelled\")\n```\n\n### Handle Deadlocks Gracefully\n\nWhen multiple transactions compete for the same resources, deadlocks can occur.\nMySQL automatically detects and resolves deadlocks by rolling back one transaction.\n\n```python\nimport time\n\ndef safe_operation(max_retries=3):\n \"\"\"Execute operations with deadlock retry logic.\"\"\"\n for attempt in range(max_retries):\n try:\n with dj.conn().transaction:\n Table1.insert(data1)\n Table2.insert(data2)\n return # Success\n except Exception as e:\n if \"Deadlock\" in str(e) and attempt < max_retries - 1:\n time.sleep(0.1 * (2 ** attempt)) # Exponential backoff\n continue\n raise\n```"
537457
},
538458
{
539459
"cell_type": "markdown",
540460
"id": "cell-summary",
541461
"metadata": {},
542-
"source": [
543-
"## Summary\n",
544-
"\n",
545-
"Transactions are fundamental to maintaining data integrity in DataJoint:\n",
546-
"\n",
547-
"| Feature | Description |\n",
548-
"|---------|-------------|\n",
549-
"| `conn.transaction` | Context manager for explicit transactions |\n",
550-
"| Automatic rollback | On exception, all changes are undone |\n",
551-
"| `make()` wrapping | Computed table computations are automatically transactional |\n",
552-
"| Master-part atomicity | Insert master and parts together safely |\n",
553-
"\n",
554-
"**Key takeaways:**\n",
555-
"1. Use transactions when multiple operations must succeed or fail together\n",
556-
"2. Keep transactions short to avoid blocking other users\n",
557-
"3. DataJoint's `populate()` automatically handles transactions for you\n",
558-
"4. Master-part relationships provide built-in transactional semantics"
559-
]
462+
"source": "## Summary\n\nTransactions are fundamental to maintaining data integrity in DataJoint:\n\n| Feature | Description |\n|---------|-------------|\n| Single operations | Already atomic—no explicit transaction needed |\n| `conn.transaction` | Context manager for grouping multiple operations |\n| Automatic rollback | On exception, all changes are undone |\n| `make()` wrapping | Computed table computations are automatically transactional |\n| Master-part atomicity | Insert master and parts together safely |\n\n**Key takeaways:**\n1. Use explicit transactions only when multiple operations must succeed or fail together\n2. Single inserts and updates are already atomic\n3. DataJoint's `populate()` automatically handles transactions for you\n4. Master-part relationships provide built-in transactional semantics\n5. For long computations, see {doc}`055-make` for safe patterns"
560463
}
561464
],
562465
"metadata": {

0 commit comments

Comments
 (0)